Atom's tech blog

OpenCV、Dlibで顔のランドーマーク検出とまぶたを閉じる検出をやってみた

概要

OpenCVとDlibを使って顔のランドマーク68箇所を検出し、68箇所の座標位置を活用して下記3つを実施。

  • 目の中心位置を検出
  • 目の開閉を検出
  • 目、口部分をトリミング

コメント

  • 本ページに表示しているイメージは画像ファイルを読み込みし検出した結果。
  • ソースコードはカメラからキャプチャー後の検出となっているのでご注意を。
  • 目の中心位置検出、目の開閉状態検出は他サイト(Eye blink detection with OpenCV, Python, and dlib)で公開している情報を参考にしている。
  • ランドマーク68箇所検出用のモデルファイル(shape_predictor_68_face_landmarks.dat) (http://dlib.net/files)にあるのでダウンロードを。
  • その他にランドマーク5箇所検出用ファイルがあるが、精度を求めるなら68ポイントを使用した方が良さそう。

環境条件

ランドマーク68箇所だけのイメージ

f:id:iAtom:20201008164732p:plain

目を開いた状態のイメージ

  • 目の中心位置を赤色でプロット
  • ランドマーク68箇所のプロットと数字表示

f:id:iAtom:20201008164808p:plain

目と口をトリミング

f:id:iAtom:20201008164852p:plainf:id:iAtom:20201008165008p:plainf:id:iAtom:20201008165023p:plain

目を閉じた状態のイメージ

  • 目を閉じた事を(Close Eye !!!)を表示
  • ランドマーク68箇所のプロット

f:id:iAtom:20201008165150p:plain

ソースコード

・学習済みモデル(shape_predictor_68_face_landmarks.dat)は、実行するPythonファイルと同じ場所に配置。 ・サンプルソースは68箇所に数字を表示する処理をコメントアウトにしている。

#! /usr/bin/python
# -*- coding: utf-8 -*-
import cv2
import dlib
import numpy as np
import imutils
from imutils import face_utils
from scipy.spatial import distance
import time
import copy

face_parts_detector = dlib.shape_predictor("./shape_predictor_68_face_landmarks.dat")
detector = dlib.get_frontal_face_detector()

#カメラデバイスオープン
cap = cv2.VideoCapture(0)

# カメラフレームサイズをVGAに変更する
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # カメラ画像の横幅を640に設定
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # カメラ画像の縦幅を480に設定

# フレームサイズ取得
frame_width= cap.get(cv2.CAP_PROP_FRAME_WIDTH)
frame_height= cap.get(cv2.CAP_PROP_FRAME_HEIGHT)

##########################
# 目が閉じているか計算する   #
##########################
def calc_eye(eye):
    p2_p6 = distance.euclidean(eye[1], eye[5])
    p3_p5 = distance.euclidean(eye[2], eye[4])
    p1_p4 = distance.euclidean(eye[0], eye[3])
    EAR = (p2_p6 + p3_p5) / (2.0 * p1_p4)
    return(round(EAR,3))

###################
# 目の中心位置算出   #
###################
def eye_center(shape):
        eyel, eyer = np.array([0, 0]), np.array([0, 0])
        # 左目の位置
        for i in range(36, 42):
            eyel[0] += shape.part(i).x
            eyel[1] += shape.part(i).y
        # 右目の位置
        for i in range(42, 48):
            eyer[0] += shape.part(i).x
            eyer[1] += shape.part(i).y
        return eyel / 6, eyer / 6

##############
# メイン処理   #
##############
while True:
    #FPS算出のため、事前に時間を取得  
    tick = cv2.getTickCount()      
    # カメラキャプチャー
    ret, image = cap.read()
    # 画像リサイズ
    image = cv2.resize(image,dsize=(int(frame_width),int(frame_height)))  
    # リサイズした画像をコピー
    face_frame = copy.deepcopy(image)    
    # グレースケール
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

    # 時間取得
    t1 = time.time() 
    # 顔検出
    rects = detector(gray,1) 
    t2 = time.time()  

    # Dlibの顔検出箇所の矩型描画ループ
    for dets in rects:
        # オリジナル画像のランドマークを取得する   
        face_parts_dets = face_parts_detector(gray, dets)
        face_parts = face_utils.shape_to_np(face_parts_dets)

        # ランドマーク68箇所に数字表示変数を初期化
        idx=1
        for (xx, yy) in face_parts:
            # 顔の1箇所毎に合計68箇所に数字
        #    cv2.putText(image, str(idx), (xx,yy),
        #        fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
        #        fontScale=0.4,
        #        color=(100, 0, 0))
            # 顔の1箇所毎に合計68箇所にプロットする 
            cv2.circle(image, (xx,yy),2, (0,255,0), thickness= -1)

            ########################
            # 口部位を取得、表示と保存 #
            ########################
            (x, y, w, h) = cv2.boundingRect(np.array([face_parts[48:68]])) #口の部位のみ切り出し
            mouth = face_frame[y:y + h, x:x + w]
            mouth = cv2.resize(mouth,(200,100))
            # Window表示位置指定
            cv2.moveWindow("Mouth", 850,320)
            # 口の画像を表示            
            cv2.imshow('Mouth',mouth) 
            # 口の画像を保存            
            cv2.imwrite("./dlib_landmark_image_mouse.png",mouth)           

            ##########################
            # 左目部位を取得、表示と保存 #
            ##########################
            (x, y, w, h) = cv2.boundingRect(np.array([face_parts[42:48]]))  # 左目の部位のみ切り出し
            leftEye = face_frame[y:y + h, x:x + w]
            leftEye = cv2.resize(leftEye, (200, 80))
             # Window表示位置指定
            cv2.moveWindow("leftEye", 850,100)
            # 左目の画像を表示            
            cv2.imshow('leftEye',leftEye) 
            # 左目の画像を保存 
            cv2.imwrite("./dlib_landmark_image_leftEye.png",leftEye)

            ##########################
            # 右目部位を取得、表示と保存 #
            ##########################
            (x, y, w, h) = cv2.boundingRect(np.array([face_parts[36:42]]))  # 左目の部位のみ切り出し
            rightEye = face_frame[y:y + h, x:x + w]
            rightEye = cv2.resize(rightEye, (200, 80))
             # Window表示位置指定
            cv2.moveWindow("rightEye", 850,210)
            # 右目の画像を表示 
            cv2.imshow('rightEye',rightEye) 
            # 右目の画像を保存 
            cv2.imwrite("./dlib_landmark_image_rightEye.png",rightEye)     
    
            # ランドマーク68箇所に数字表示変数をインクリメント
            idx += 1

        ############ 左目 ###########
        left_eye = face_parts[42:48]
        left_eye_ear = calc_eye(left_eye)
        ############ 右目 ###########
        right_eye = face_parts[36:42]
        right_eye_ear = calc_eye(right_eye)

       # 両目閉じているかチェック
        if (left_eye_ear + right_eye_ear) < 0.50:
            # 目を閉じるメッセージを出力
            cv2.putText(image, "Close Eye !!!",(10,430), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)                  
        else:
            # 目中心位置算出とに描画
            center_right_eye,center_left_eye = eye_center(face_parts_dets)
            cv2.circle(image, (int(center_left_eye[0]),int(center_left_eye[1])), 3, (0, 0, 255), -1)            
            cv2.circle(image, (int(center_right_eye[0]),int(center_right_eye[1])), 3, (0, 0, 255), -1)  

    # FPS算出と表示用テキスト作成  
    fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
    # 検出時間を算出  
    detect_time = t2 - t1      
    # FPS表示  
    cv2.putText(image, "FPS:{}".format(int(fps)),(10,450), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)  
    # 顔検出時間表示
    cv2.putText(image, "DetectTime:{:.2f}".format(detect_time),(10,465), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)  
    # フレームサイズ表示
    cv2.putText(image, str(int(frame_width))+"*"+str(int(frame_height)),(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA)      

    # ウィンドウに表示  
    cv2.moveWindow("DLIB_Landmark",200,100) # Window表示位置指定
    cv2.imshow('DLIB_Landmark',image)

    # ESCキーで終了  
    k = cv2.waitKey(10) & 0xff   
    if k == 27:  
        break      

cap.release()
cv2.destroyAllWindows()