ブログ名

head-pose-estimationを用いて顔の向きを推定する

概要

パソコンのカメラから人の顔を検出し、覗き見防止をするプログラムを作成していきます。 最初の目標はカメラに映った人の顔の向きを判定することです。 その顔向き判定アルゴリズムについて調査したので、今回の記事では、その部分に焦点を当てて解説していきます。

ソース

今回用いたソースコードはこちらのhead-pose-estimation (https://github.com/lincolnhard/head-pose-estimation )です。 実行するには、初めにhttp://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 からトレーニングモデルをダウンロードして解凍した後、shape_predictor_68_face_landmarks.datをvideo_test_shape.pyと同じディレクトリに貼り付けます。 最後にvideo_test_shape.pyを実行します。

実行すると、パソコンのカメラが起動し、自分の顔の方向が検出されます。下の画像はこのプログラムを実行したときのイメージです。

f:id:iTD_GRP:20191112215108p:plain

顔の決まった位置に68個の点が打たれ、顔の方向がわかりやすいように立方体が描かれます。ここで、顔を囲う立方体が大きくなりすぎるとエラーを起こすことがあるので、立方体を描く部分を無効化すると安定します。

#for start, end in line_pairs:
    #cv2.line(frame, reprojectdst[start], reprojectdst[end], (0, 0, 255))

左上に現れている値は顔の角度を表すもので、

  • X: 頷く方向の角度

  • Y: 首を左右に振るときの角度

  • Z: 首を傾げた角度

をそれぞれ表しています。

顔方向検出の仕組み

1.dlibを用いて顔を検出する

detector = dlib.get_frontal_face_detector()
face_rects = detector(frame, 0)

このコードを実行することで、画像(frame)から顔を検出し、その顔を囲む長方形のリストを得ることができます。

face_landmark_path = './shape_predictor_68_face_landmarks.dat'
predictor = dlib.shape_predictor(face_landmark_path)
if len(face_rect) > 0:
    shape = predictor(frame, face_rect[0])
    shape = face_utils.shape_to_np(shape)

顔を検出できたか(顔を囲む長方形がリストに入っているか)を確認し、リストの一番目に入っている顔を取り出して処理を行います。 行う処理は、検出した顔から下の図の68個の点を見つけ出し、その点の座標を番号順にリストにまとめる処理です。

f:id:iTD_GRP:20191112215158p:plain

dlib.shape_predictor()を用いるとdlib.full_object_detectionという型で結果が得られるので、のちの計算のためface_utils.shape_to_np()を用いて結果をnumpy配列に変換します。

2.cv2.solvePnP()を用いて顔の方向を推定する

image_pts = np.float32([shape[17], shape[21], shape[22], shape[26], shape[36], shape[39], shape[42], shape[45], shape[31], shape[35], shape[48], shape[54], shape[57], shape[8]])

見つけだした68個の点の内、次の14個を取り出します。

f:id:iTD_GRP:20191112215317p:plain

この14点の位置座標はすでにわかっているものとし、リストobject_ptsに格納されています。

object_pts = np.float32([[6.825897, 6.760612, 4.402142],
                         [1.330353, 7.122144, 6.903745],
                         [-1.330353, 7.122144, 6.903745],
                         [-6.825897, 6.760612, 4.402142],
                         [5.311432, 5.485328, 3.987654],
                         [1.789930, 5.393625, 4.413414],
                         [-1.789930, 5.393625, 4.413414],
                         [-5.311432, 5.485328, 3.987654],
                         [2.005628, 1.409845, 6.165652],
                         [-2.005628, 1.409845, 6.165652],
                         [2.774015, -2.080775, 5.048531],
                         [-2.774015, -2.080775, 5.048531],
                         [0.000000, -3.116408, 6.097667],
                         [0.000000, -7.415691, 4.070434]])

この座標をプロットすると下の画像のようになります。

f:id:iTD_GRP:20191112215339p:plain

K = [6.5308391993466671e+002, 0.0, 3.1950000000000000e+002, 
    0.0, 6.5308391993466671e+002, 2.3950000000000000e+002,
    0.0, 0.0, 1.0]
D = [7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000]

cam_matrix = np.array(K).reshape(3, 3).astype(np.float32)
dist_coeffs = np.array(D).reshape(5, 1).astype(np.float32)

_, rotation_vec, translation_vec = cv2.solvePnP(object_pts, image_pts, cam_matrix, dist_coeffs)

ここから顔の方向を推定していきます。cv2.solvePnP()は、物体の位置座標:object_pts、物体の画像上の座標:image_pts、カメラに関する2つのパラメータ(カメラ行列:コードのKcam_matrix、歪み係数ベクトル:コードのDdist_coeffs)の合計4つの引数をもとにカメラの姿勢を推定する関数です。カメラに関する性質がわかっているとして、この関数を用いると、座標空間の原点にある顔を撮影した画像から、撮影したときのカメラの座標と角度を求めることができます。

rotation_mat, _ = cv2.Rodrigues(rotation_vec)
pose_mat = cv2.hconcat((rotation_mat, translation_vec))
_, _, _, _, _, _, euler_angle = cv2.decomposeProjectionMatrix(pose_mat)

ここで求まるrotation_vecはカメラの角度を回転ベクトルを表したものなで、cv2.Rodrigues(rotation_vec)を用いて回転行列に変換します。 rotation_vec(3×3)とtranskation_vec(3×1)をcv2.hconcat()を用いて横に繋げて射影行列pose_mat(3×4)を作成します。最後に、cv2.decomposeProjectionMatrix()を用いて原点から見たカメラの三軸に対する角度をeuler_angleに代入して、結果を得ます。

まとめ

今回は画像から顔を検出し、顔の向きを求めるプログラムを開設しました。今後は検出した顔から、さらに黒目を検出し、視線がどこに向いているかを計算するプログラムを実行していきたいです。


目次へ戻る