はじめに
この記事は、SLP KBIT Advent Calendar 2022の23日目の記事になります。
この記事ではカメラから取得した画像をもとに、dlibやnumpy、openCVを用いて顔の向きを求めます。顔の向きは、線で示しており角度を求めるには追加で計算を行う必要があります。
開発環境
openCV 4.6.0.66
numpy 1.23.1
dlib 19.24.0
解説
コードの要点のみ解説します。
ここでは、カメラの姿勢を計算しています。solvePnPではカメラの位置、姿勢を3次元の座標と2次元の座標の対応から求めれます。
success, vector_rotation, vector_translation = cv2.solvePnP(figure_points_3D, image_points_2D, matrix_camera, distortion_coeffs, flags=0)
projectPointsでは、3次元の点をカメラ視点に投影します。(3次元の点を2次元の点に変換)
nose_end_point2D, jacobian = cv2.projectPoints(np.array([(0.0, 0.0, 1000.0)]), vector_rotation, vector_translation, matrix_camera, distortion_coeffs)
これは、顔のモデル座標を登録しています。projectPointsで投影する点は、3次元で鼻の頂点の正面になります。従って、顔の向きに伴ってnose_end_point2Dに入る点は移動します。
figure_points_3D = np.array([
(0.0, 0.0, 0.0), # Nose tip
(0.0, -330.0, -65.0), # Chin
(-225.0, 170.0, -135.0), # Left eye left corner
(225.0, 170.0, -135.0), # Right eye right corner
(-150.0, -150.0, -125.0), # Left Mouth corner
(150.0, -150.0, -125.0) # Right mouth corner
])
point1は鼻の頂点、point2はnose_end_point2Dに入る点です。その2点を結んだ線を描画します。この線が顔の向きを示すものとなります。
cv2.line(img, point1, point2, (255,255,255), 2)
コード
import cv2
import numpy as np
import dlib
from imutils import face_utils
WIDTH = 1920
HEIGHT = 1080
face_detector = dlib.get_frontal_face_detector()
predictor_path = 'shape_predictor_68_face_landmarks.dat'
face_predictor = dlib.shape_predictor(predictor_path)
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
while True:
ret,img = cap.read()
img_gry = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
size = img.shape
faces = face_detector(img_gry,1)
for face in faces:
landmark = face_predictor(img_gry,face)
landmark = face_utils.shape_to_np(landmark)
image_points_2D = np.array([
(landmark[30]), # Nose tip
(landmark[8]), # Chin
(landmark[45]), # Left eye corner
(landmark[36]), # Right eye corner
(landmark[54]), # Left mouth
(landmark[48]) # Right mouth
], dtype="double")
figure_points_3D = np.array([
(0.0, 0.0, 0.0), # Nose tip
(0.0, -330.0, -65.0), # Chin
(-225.0, 170.0, -135.0), # Left eye left corner
(225.0, 170.0, -135.0), # Right eye right corne
(-150.0, -150.0, -125.0), # Left Mouth corner
(150.0, -150.0, -125.0) # Right mouth corner
])
distortion_coeffs = np.zeros((4,1))
focal_length = size[1]
center = (size[1]/2, size[0]/2)
matrix_camera = np.array(
[[focal_length, 0, center[0]],
[0, focal_length, center[1]],
[0, 0, 1]], dtype = "double"
)
success, vector_rotation, vector_translation = cv2.solvePnP(figure_points_3D, image_points_2D, matrix_camera, distortion_coeffs, flags=0)
nose_end_point2D, jacobian = cv2.projectPoints(np.array([(0.0, 0.0, 1000.0)]), vector_rotation, vector_translation, matrix_camera, distortion_coeffs)
for p in image_points_2D:
cv2.circle(img, (int(p[0]), int(p[1])), 3, (0,0,255), -1)
point1 = ( int(image_points_2D[0][0]), int(image_points_2D[0][1]))
point2 = ( int(nose_end_point2D[0][0][0]), int(nose_end_point2D[0][0][1]))
cv2.line(img, point1, point2, (255,255,255), 2)
cv2.imshow("Final",img)
if cv2.waitKey(1) == 27:
break
cap.release()
cv2.destroyAllWindows()
おわりに
今回は顔の向きを求めるコードの解説を行いました。計算内容を調べるのが大変でしたので、自分が参考にしたサイトを置いておきます。興味がある人はのぞいてみてください。
参考