正しくは頭部姿勢推定ですね。
顔を認識して平行移動ベクトルと回転ベクトルを取得します。
こんな感じでリアルタイムで検出できる。
マスクしてると精度は悪い。
環境設定
Anacondaを使用している場合
$ conda install -c anaconda opencv $ pip install imutils $ conda install -c menpo dlib
検出器はここからダウンロードできるので、コードと同じディレクトリにおいてください。
TensorFace/shape_predictor_68_face_landmarks.dat at master · AKSHAYUBHAT/TensorFace · GitHub
コード
# coding: UTF-8 from imutils import face_utils import imutils import dlib import cv2 import numpy as np from math import * class FaceDetector(): def __init__(self, Port): # 顔検出器読み込み self.detector = dlib.get_frontal_face_detector() self.predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") self.capture = cv2.VideoCapture(Port) self.ret, self.capture_image = self.capture.read() self.size = self.capture_image.shape #カメラ行列とか self.focal_length = self.size[1] self.center = (self.size[1]/2, self.size[0]/2) self.camera_matrix = np.array( [[self.focal_length, 0, self.center[0]], [0, self.focal_length, self.center[1]], [0, 0, 1]], dtype = "double" ) #2D 顔器官点 初期化 self.image_points = np.array([[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]], dtype="double") #3D 顔器官点 self.model_points = np.array([ (0.0, 0.0, 0.0), #鼻先 (0,0 -270.0, -90.0), #顎 (-144.0, 105.0, -90.0), #左目左端 (144.0, 105.0, -90.0), #右目右端 (-99.0, -99.0, -45.0), #口左端 (99.0, -99.0, -45.0) #右目右端 ]) self.dist_coeffs = np.zeros((4,1)) self.rotation_vector = np.ndarray((3,1), dtype=np.float64) self.translation_vector = np.ndarray((3,1), dtype=np.float64) self.shape = np.ndarray((48,2), np.int32) # 顔検出関数 def detect_face(self): self.ret, self.capture_image = self.capture.read() self.frame = self.capture_image self.gray = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY) # 顔認識 self.rects = self.detector(self.gray, 0) # 顔器官点抽出(顔ごとに処理を行う) for rect in self.rects: # 顔器官点(48点)を取得 self.shape = self.predictor(self.gray, rect) self.shape = face_utils.shape_to_np(self.shape) # 顔器官点(48点)から必要な点(6点)を取得 self.image_points = np.array([ self.shape[33], # 鼻先 self.shape[8], # 顎 self.shape[45], # 左目左端 self.shape[36], # 右目右端 self.shape[54], # 口左端 self.shape[48] # 口右端 ], dtype="double") (self.success, self.rotation_vector, self.translation_vector) = cv2.solvePnP(self.model_points, self.image_points, self.camera_matrix, self.dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) # 角度取得 def get_radians(self): return self.rotation_vector # 特徴点を描画 def draw_face_contour(self): #顔器官点(48点)をframeに描画 for (x, y) in self.shape: cv2.circle(self.frame, (x, y), 1, (0, 0, 255), -1) #顔器官点(6点)をfremaに描画 for p in self.image_points: cv2.circle(self.frame, (int(p[0]), int(p[1])), 3, (0,0,255), -1) # 視線を描画 def draw_face_viewline(self): #3D点を画像に投影.これを使って線を描く (self.nose_end_point2D, self.jacobian) = cv2.projectPoints(np.array([(0.0, 0.0, 1000.0)]), self.rotation_vector, self.translation_vector, self.camera_matrix, self.dist_coeffs) p1 = ( int(self.image_points[0][0]), int(self.image_points[0][1])) p2 = ( int(self.nose_end_point2D[0][0][0]), int(self.nose_end_point2D[0][0][1])) cv2.line(self.frame, p1, p2, (255,0,0), 2) # 顔パーツを描画 def draw_faceparts(self): #顔パーツに分けて図形をframeに描画 colors = [(19, 199, 109), (79, 76, 240), (230, 159, 23), (168, 100, 168), (158, 163, 32), (163, 38, 32), (180, 42, 220), (180, 42, 220)] self.frame = face_utils.visualize_facial_landmarks(self.frame, self.shape, colors) def show(self): cv2.imshow("Output", self.frame) def main(): myCap = FaceDetector(0) while(True): myCap.detect_face() myCap.draw_faceparts() angles = myCap.get_radians() print(angles * 180/pi) myCap.draw_face_viewline() myCap.draw_face_contour() myCap.show() #キーを押したら終了 if cv2.waitKey(1) > 0: break if __name__ == "__main__": main()
補足説明
51行目この関数によってカメラ座標(Xc, Yc)上での顔の各特徴点座標を求めます。
face_utils.shape_to_np(self.shape)
次に62行目solvePnP関数によってカメラの位置、角度が求まります
cv2.solvePnP
solvePnPとはその名の通りPnP問題を解くための関数です。
PnP問題においては、ある物体のn点の既知の3次元座標(ワールド座標Xo, Yo, Zo)と、その物体を撮影したときのn点の既知のカメラ座標(Xc, Yc)があったときを考えます。
その幾何関係から、ワールド座標におけるカメラの姿勢(平行移動ベクトル及び回転ベクトル)を求めるという問題です。
点数が多いほど精度は上がりますが、多いと計算時間がかかるので、6点だけにしています。
参考
Real-time facial landmark detection with OpenCV, Python, and dlib - PyImageSearch