MediaPipeのPythonラッパーを使ってリアルタイムに顔特徴点を取得する

# はじめに
この記事は[顔学2020アドベントカレンダー](https://qiita.com/advent-calendar/2020/facial_study)の17日目の記事です．
今日は顔特徴点（Face Landmark）取得に利用できるMediaPipeのPythonラッパーが出ていたので，Webカメラで動作させてみたいと思います．

[MediaPipe FaceMesh](https://google.github.io/mediapipe/solutions/face_mesh)のコードを参考にしています．

# インストール

```python
> pip install mediapipe
```

# 顔特徴点の取得
取得した顔特徴点は3次元空間上の座標として渡されます．その特徴点をもとに（三角）ポリゴンメッシュを作成しています．RGBDカメラなどによる奥行き情報（Depth）がなくても，Webカメラの2次元画像から顔特徴点モデル（face landmark model）を作成することができます．

[Real-time Facial Surface Geometry from Monocular Video on Mobile GPUs](https://arxiv.org/abs/1907.06724)

![スクリーンショット 2020-12-10 17.24.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/927f4c67-20cc-541f-36a0-822ff2dbc755.png)


```python
import mediapipe as mp
import cv2 as cv

mp_drawing = mp.solutions.drawing_utils # 描画用のインスタンス
mp_face_mesh = mp.solutions.face_mesh # MLソリューションの顔メッシュインスタンス

face_mesh = mp_face_mesh.FaceMesh(
    static_image_mode=True,
    max_num_faces=1,
    min_detection_confidence=0.5)

drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)

image = cv.imread(PATH_TO_IMG) # 画像の読み込み
rgb_image = cv.cvtColor(image, cv.COLOR_BGR2RGB) # RGB形式に変換
results = face_mesh.process(rgb_image) # 顔メッシュを計算

annotated_image = image.copy() # 描画用の画像をコピーしておく

for face_landmarks in results.multi_face_landmarks: # 画像内の全ての顔の顔特徴点
  print(face_landmarks)
  ###################################
  # landmark {
  #  x: 0.4557078182697296
  #  y: 0.6814221143722534
  #  z: -0.022573839873075485
  # }
  # ...
  # landmark {
  #  x: 0.5611855983734131
  #  y: 0.48328155279159546
  #  z: 0.0023858787026256323
  # }
  ###################################
  mp_drawing.draw_landmarks(
    image=annotated_image,
    landmark_list=face_landmarks,
    connections=mp_face_mesh.FACE_CONNECTIONS,
    landmark_drawing_spec=drawing_spec,
    connection_drawing_spec=drawing_spec) # 特徴点の描画
cv.imwrite('face_mesh_result.png', annotated_image) # 保存
face_mesh.close() # インスタンスを終了させる
```

実行するとこんな感じで顔の上に顔特徴点を描画できる
![スクリーンショット 2020-12-10 17.30.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/e8770cf9-d5a4-8c7b-8e1c-8d8ea48197a7.png)![スクリーンショット 2020-12-10 17.31.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/b4c9400e-92d2-8c9c-2987-68482d63af80.png)![スクリーンショット 2020-12-10 17.30.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/a7ce3291-a217-0e44-2674-22d5cf11bf93.png)

# Webカメラでリアルタイムに顔特徴点を描画
[MediaPipe FaceMesh](https://google.github.io/mediapipe/solutions/face_mesh)のサンプルコードにfpsの計算・動画保存・スクリーンショット機能を追加したものです．

```python
import mediapipe as mp
import cv2 as cv

mp_drawing = mp.solutions.drawing_utils # 描画用のインスタンス
mp_face_mesh = mp.solutions.face_mesh # MLソリューションの顔メッシュインスタンス
face_mesh = mp_face_mesh.FaceMesh(
    min_detection_confidence=0.5, min_tracking_confidence=0.5)
drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)
cap = cv.VideoCapture(0)
w = int(cap.get(cv.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT))         
fourcc = cv.VideoWriter_fourcc('m', 'p', '4', 'v')  
video = cv.VideoWriter('face_mesh_video.mp4', fourcc, 30, (w, h))
while cap.isOpened():
    tick = cv.getTickCount()
    success, image = cap.read()
    if not success:
        print("Ignoring empty camera frame.")
        # If loading a video, use 'break' instead of 'continue'.
        continue

    # Flip the image horizontally for a later selfie-view display, and convert
    # the BGR image to RGB.
    image = cv.cvtColor(cv.flip(image, 1), cv.COLOR_BGR2RGB)
    # To improve performance, optionally mark the image as not writeable to
    # pass by reference.
    image.flags.writeable = False
    results = face_mesh.process(image)
    print(results.multi_face_landmarks[0])

    # Draw the face mesh annotations on the image.
    image.flags.writeable = True
    image = cv.cvtColor(image, cv.COLOR_RGB2BGR)
    if results.multi_face_landmarks:
        for face_landmarks in results.multi_face_landmarks:
            print(face_landmarks)
            mp_drawing.draw_landmarks(
            image=image,
            landmark_list=face_landmarks,
            connections=mp_face_mesh.FACE_CONNECTIONS,
            landmark_drawing_spec=drawing_spec,
            connection_drawing_spec=drawing_spec)
    fps = cv.getTickFrequency() / (cv.getTickCount() - tick) # fpsの計算
    cv.putText(
        image, 
        "FPS: " + str(int(fps)), 
        (image.shape[1] - 150, 40), 
        cv.FONT_HERSHEY_PLAIN, 
        2, 
        (0, 255, 0),
        2,
        cv.LINE_AA)
    cv.imshow('MediaPipe FaceMesh', image)
    video.write(image)
    if cv.waitKey(5) & 0xFF == 27: # escで終了
        break
    if cv.waitKey(5) & 0xFF == 32: # spaceでスクリーンショット
        dt = datetime.datetime.now()
        cv.imwrite(dt.isoformat() + ".png", image)
face_mesh.close()
cap.release()
```

![動作結果](https://drive.google.com/uc?export=view&id=1e_fVue4McQYzQEyxkNKg1jLvUnEINZP3)


動作環境はMacBookPro2017と標準搭載のカメラです．おおむねリアルタイム（fps30以上）で処理できていそうです．GPUで処理させたらもっとはやくなると思います．

# さいごに
今日は顔特徴点を取得する方法と，それをもとに特徴点を描画する方法を紹介しました．以前紹介したOpenFaceでも同じようなことができるのですが，MediaPipeのほうが新しい技術なのでその分動作が高速だったり精度がよかったりしそうですね．この辺は今後比較できたらと思っています．

MediaPipeにはこれ以外にも手指の認識や姿勢推定のモデルも対応していて，Web上でデモが試せます．こちらも手軽に試せるので是非やってみてください．

https://google.github.io/mediapipe/

![スクリーンショット 2020-12-10 18.33.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/bc47d0ca-cce0-2bb3-565a-461a18ea94bf.png)
![スクリーンショット 2020-12-10 18.33.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/bbbbfdf4-c28e-3f40-0aa5-c80618390883.png)



# 参考
- [MediaPipe FaceMesh](https://google.github.io/mediapipe/solutions/face_mesh)
- [Real-time Facial Surface Geometry from Monocular Video on Mobile GPUs](https://arxiv.org/abs/1907.06724)