Help us understand the problem. What is going on with this article?

カメラ画像から虹彩検出をしてみた

はじめに

2020/1/19(土)に開催された第56回 コンピュータビジョン勉強会@関東の発表内容をまとめました。
当日の資料はこちらから見れます。

ソースコードは以下のGithub上に公開しています。
https://github.com/33taro/gaze_cv

虹彩検出の手順について

虹彩検出は大学の頃、研究していたテーマなので、進化したOpenCVならお手軽にできなかなぁと思い実施しています。
手順としては次の通りです。

1.カメラ画像から人物の顔と顔のランドマーク検出
2.顔のランドマークから目領域を切り出し
3.目領域を2値化して虹彩領域を抽出
4.抽出した虹彩領域から虹彩検出

虹彩検出手順.png

カメラ画像から人物の顔と顔のランドマーク検出

以前、別の記事で紹介した顔のランドマーク検出を利用して、虹彩(黒目の部分)を検出してみました。
詳細はそちらを参照してください。

顔のランドマークから目領域を切り出し

上記の記事でも紹介していますが、顔のランドマークはこちらにある学習済みモデルを利用しています。
そのため目領域は右目が「No.37~42」、左目が「No.43~48」となります。

目領域.png

こちらは実際のソースコードでは「tracking_system」ディレクトリの「eye_region_manager.py」に記載しています。

eye_region_manager.py
    def detect_eye_region(self, face_landmark):
        """
        ランドマークから目領域の座標を取得
        :param face_landmark:
        """
        # 右目切り出し
        self._right_eye_region = {'top_x': face_landmark[36][0], 'bottom_x': face_landmark[39][0],
                                  'top_y': face_landmark[37][1]
                                  if face_landmark[37][1] < face_landmark[38][1] else face_landmark[38][1],
                                  'bottom_y': face_landmark[41][1]
                                  if face_landmark[41][1] > face_landmark[40][1] else face_landmark[40][1]}

        # 左目切り出し
        self._left_eye_region = {'top_x': face_landmark[42][0], 'bottom_x': face_landmark[45][0],
                                 'top_y': face_landmark[43][1]
                                 if face_landmark[43][1] < face_landmark[45][1] else face_landmark[45][1],
                                 'bottom_y': face_landmark[47][1]
                                 if face_landmark[47][1] > face_landmark[46][1] else face_landmark[46][1]}

※配列は0番から始まるため、0~67となり番号がひとつずれます。
※目領域の上下y座標は、より長くなるように取得しています。

目領域を2値化して虹彩領域を抽出

目領域の2値化にはPタイル法を用いました。
これは2値化したい領域が画像領域の何割を占めるか割合で指定する手法です。
これにより明るさに左右されず虹彩が取得できました。
(経験則から虹彩は目領域の4割にしています)

目領域の2値化.png
※2値化前にノイズ削除のためにガウシアンフィルタで平滑化しています。

Pタイル法はOpenCVに実装されていないため、自作しました、
「utility」ディレクトリの「image_utility.py」に記載しています。

image_utility.py
# coding:utf-8

import cv2


def p_tile_threshold(img_gry, per):
    """
    Pタイル法による2値化処理
    :param img_gry: 2値化対象のグレースケール画像
    :param per: 2値化対象が画像で占める割合
    :return img_thr: 2値化した画像
    """

    # ヒストグラム取得
    img_hist = cv2.calcHist([img_gry], [0], None, [256], [0, 256])

    # 2値化対象が画像で占める割合から画素数を計算
    all_pic = img_gry.shape[0] * img_gry.shape[1]
    pic_per = all_pic * per

    # Pタイル法による2値化のしきい値計算
    p_tile_thr = 0
    pic_sum = 0

    # 現在の輝度と輝度の合計(高い値順に足す)の計算
    for hist in img_hist:
        pic_sum += hist

        # 輝度の合計が定めた割合を超えた場合処理終了
        if pic_sum > pic_per:
            break

        p_tile_thr += 1

    # Pタイル法によって取得したしきい値で2値化処理
    ret, img_thr = cv2.threshold(img_gry, p_tile_thr, 255, cv2.THRESH_BINARY)

    return img_thr

抽出した虹彩領域から虹彩検出

虹彩領域は取得できましたが、眉毛や瞼の影などがあり、虹彩だけをキレイに抽出できません。
そこで輪郭点追跡で黒色領域を取得し、各領域に足して外接円近似をし、半径が最大の円を虹彩としました。
※ただし外接円が大きすぎる場合、虹彩の候補から外します。

虹彩の外接円近似.png

一連の2値化~虹彩検出は「tracking_system」ディレクトリの「eye_system_manager.py」に記載しています。

eye_system_manager.py
    @staticmethod
    def _detect_iris(eye_img):
        # グレースケール化後、ガウシアンフィルタによる平滑化
        eye_img_gry = cv2.cvtColor(eye_img, cv2.COLOR_BGR2GRAY)
        eye_img_gau = cv2.GaussianBlur(eye_img_gry, (5, 5), 0)

        # Pタイル法による2値化
        eye_img_thr = p_tile_threshold(eye_img_gau, IRIS_PER)

        cv2.rectangle(eye_img_thr, (0, 0), (eye_img_thr.shape[1] - 1, eye_img_thr.shape[0] - 1), (255, 255, 255), 1)

        # 輪郭抽出
        contours, hierarchy = cv2.findContours(eye_img_thr, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        # 輪郭から最小外接円により虹彩を求める
        iris = {'center': (0, 0), 'radius': 0}
        for i, cnt in enumerate(contours):
            (x, y), radius = cv2.minEnclosingCircle(cnt)
            center = (int(x), int(y))
            radius = int(radius)

            # 半径が大きすぎる場合、虹彩候補から除外
            if eye_img_thr.shape[0] < radius*0.8:
                # # 虹彩候補の描画
                # cv2.circle(eye_img, center, radius, (255, 0, 0))
                continue

            # 最も半径が大きい円を虹彩と認定
            if iris['radius'] < radius:
                iris['center'] = center
                iris['radius'] = radius
                iris['num'] = i

        return iris

虹彩の検出結果

demo.gif

上図の通り、左右の目の虹彩はしっかりと取得できています。
しかし上下については課題が残っています。
ただし人が上下を見るときは虹彩を動かすより顔を動かす場合が多いです。
(実際に目だけで上下を見ると疲れます)
なので顔のランドマークから、顔の向きを検出してなんとかできないかなぁ、と考えています。

参考リンク

輪郭点追跡

最小外接円

Pタイル法

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした