dlibとopencvを使って画像から瞳の位置を取得【python】
CPPXのXです。
dlibとopencvを使って瞳の位置を取得したいと思います。
環境
自分は、dlib導入時にpython3のboost入れ忘れてエラーが出たので、下記サイトを参考に解決しました。
importと前準備
importするものは以下です。
import dlib import cv2
顔器官の取得に学習が必要なため、学習済みモデルをダウンロードしておきます。
http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
何かしらで解凍しておいてください。
顔の検出器と、予測器を定義しておきます。
予測器の方には、先ほど解凍した学習済みモデルのパスを指定しています。
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
画像の用意
カメラを使ってやっていきたいと思います。
写っている顔の数は一人にしておいてください。
カメラが付いていない場合は、
frame = cv2.imread("画像のパス")
とやって、静止画像を代わりに使って進めていきます。
ではやっていきます。
まず、カメラから動画を取得します。
cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() cv2.imshow("me", frame) # 確認用 # ここに処理を追加していく ---- # ここまで ---- if cv2.waitKey(1) == 27: break cap.release() cv2.destroyAllWindows()
これでカメラから動画が取得できたかと思います。
カメラが無い方は、以下のようにします。
frame = cv2.imread("画像のパス") cv2.imshow("me", frame) # 確認用 # ここに処理を追加していく ---- # ここまで ---- cv2.waitKey(0) cv2.destroyAllWindows()
frameの中身はnumpy配列です。
色の並びはBGRで、dtypeはuint8でやっていきます。
確認ができたら、確認用と書いてある行は消しておいてください。
顔器官座標を取得
dets = detector(frame[:, :, ::-1])
顔を検出します。
今回は、写っている顔が1つの想定なので、以下のようにして顔器官を取得します。
dets = detector(frame[:, :, ::-1]) if len(dets) > 0: parts = predictor(frame, dets[0]).parts() # 確認用 --- img = frame * 0 for i in parts: cv2.circle(img, (i.x, i.y), 3, (255, 0, 0), -1) cv2.imshow("me", img) # 確認用 ここまで ---
実行すると、青の点で顔器官が表示されているかと思います。
こんな感じです。
partsの中に座標が入っており、座標の順番に関しては、以下で分かりやすく説明されていました。
http://mizutanikirin.net/unity-dlib-facelandmark-detector%E3%81%A7%E9%A1%94%E8%AA%8D%E8%AD%98
角座標へのアクアセスは、それぞれの要素の .x, .yで行えます。
# 例えば、鼻のてっぺん座標を取得したい場合 parts[33].x parts[33].y
確認ができたら、確認用コードは消しておいてください。
次は、瞳の位置を取得していきます。
瞳座標を取得
先ほど取得した顔器官座標を使っていきます。
方針としては、目の画像から黒い部分を抜き出して、その重心を瞳とします。
瞳座標の取得を、関数にまとめたいと思います。
引数の定義は以下です。
def eye_point(img, parts, left=True): pass
顔画像と、顔器官座標、左目or右目(Trueで左目)
です。
関数の中身を埋めていきます。
まず、目玉の座標を取得してしまいましょう。
if left: eyes = [ parts[36], min(parts[37], parts[38], key=lambda x: x.y), max(parts[40], parts[41], key=lambda x: x.y), parts[39], ] else: eyes = [ parts[42], min(parts[43], parts[44], key=lambda x: x.y), max(parts[46], parts[47], key=lambda x: x.y), parts[45], ]
左目が、36~39番目に入っているので、それを4点抜き出します。
目の端と(36, 39)、上瞼からより上にある方を(37 or 38)、下瞼からより下にある方を(40 or 41)。
四角で囲った時に、目玉全体が入るように座標を取得しておきます。
右目は左目の座標に+6して同様に取得します。
原点を目玉に合わせるために、座標を保存しておきます。
org_x = eyes[0].x org_y = eyes[1].y
目が閉じていたら瞳を検知できないので、目が閉じているかを判別します。
if is_close(org_y, eyes[2].y): return None
is_closeは後で定義します(定義はこちら)。
とりあえずコメントアウトしておいても構いません。
目玉をトリミングして二値化します。
eye = img[org_y:eyes[2].y, org_x:eyes[-1].x] # トリミング _, eye = cv2.threshold(cv2.cvtColor(eye, cv2.COLOR_RGB2GRAY), 30, 255, cv2.THRESH_BINARY_INV) # 二値化
重心を求めます。
center = get_center(eye) if center: return center[0] + org_x, center[1] + org_y return center
get_centerは後で定義します(定義はこちら)。
重心を求めないと瞳の座標が分からないので、次に進みます。
eye_pointの中身は以上です。
eye_pointの全体像を貼っておきます。
def eye_point(img, parts, left=True): if left: eyes = [ parts[36], min(parts[37], parts[38], key=lambda x: x.y), max(parts[40], parts[41], key=lambda x: x.y), parts[39], ] else: eyes = [ parts[42], min(parts[43], parts[44], key=lambda x: x.y), max(parts[46], parts[47], key=lambda x: x.y), parts[45], ] org_x = eyes[0].x org_y = eyes[1].y if is_close(org_y, eyes[2].y): return None eye = img[org_y:eyes[2].y, org_x:eyes[-1].x] _, eye = cv2.threshold(cv2.cvtColor(eye, cv2.COLOR_RGB2GRAY), 30, 255, cv2.THRESH_BINARY_INV) center = get_center(eye) if center: return center[0] + org_x, center[1] + org_y return center
画像の重心を求める
opencvがやってくれます。
def get_center(gray_img): moments = cv2.moments(gray_img, False) try: return int(moments['m10'] / moments['m00']), int(moments['m01'] / moments['m00']) except: return None
引数は、グレースケール画像です。
たまに0除算が発生してしまうので、雑に例外処理しています。
重心に関しては、詳しく説明しているサイトが色々あるので、説明を省きます。
これで取得した座標を瞳の位置とします。
ここまで実装したらとりあえず動くので、早く動かしたい方は確認用コードを追加して実行してみましょう。
目が閉じているか
def is_close(y0, y1): if abs(y0 - y1) < 10: return True return False
引数は、上瞼のy座標と、下瞼のy座標です。
上瞼と、下瞼のy座標の差で判別しています。
差が10未満なら とやっているので、画像の画素数によってうまく動きません。
うまくいかなかったら、ここの数値を色々と変えて試して見てください。
最後の確認用コード
def p(img, parts, eye): if eye[0]: cv2.circle(img, eye[0], 3, (255, 255, 0), -1) if eye[1]: cv2.circle(img, eye[1], 3, (255, 255, 0), -1) for i in parts: cv2.circle(img, (i.x, i.y), 3, (255, 0, 0), -1) cv2.imshow("me", img)
引数は、顔画像と、顔器官座標、瞳の座標(左, 右)です。
瞳座標を緑の点、顔器官座標を青の点で顔画像に重ねて表示しています。
メイン処理に下記を追加して実行すると、瞳の顔器官に加えて、瞳も表示されるかと思います。
left_eye = eye_point(frame, parts) right_eye = eye_point(frame, parts, False) p(frame * 0, parts, (left_eye, right_eye))
frame * 0のところで、0倍しなければ元の顔画像に重ねて点が表示されます。
実行結果を載せておきます。
おわり
コード載せておきます。