元高専生のロボット作り

元高専生のロボット作り

主にプログラミング, 電子系について書きます。たまに機械系もやります。メモ代わりの記事ばっか書きます

pythonで顔向き推定をする

正しくは頭部姿勢推定ですね。
顔を認識して平行移動ベクトルと回転ベクトルを取得します。


こんな感じでリアルタイムで検出できる。
マスクしてると精度は悪い。
f:id:sgrsn1711:20200922180512p:plain



環境設定

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点だけにしています。

f:id:sgrsn1711:20190726111648p:plain




参考

Real-time facial landmark detection with OpenCV, Python, and dlib - PyImageSearch

Head Pose Estimation using OpenCV and Dlib | Learn OpenCV