はじめに
機械学習フレームワークであるMediaPipeを使ってみたので、Pythonで顔のランドマーク検出を実装する方法を紹介します。
MediaPipeとは
MediaPipeは、Googleが開発したオープンソースの機械学習フレームワークです。顔検出、手の検出、姿勢推定、物体検出など、様々なタスクに対応した事前学習済みモデルが提供されています。
ランドマークとは
ランドマークとは、本来は地理学において目印となる建造物などの特徴物を指す言葉です。
今回の文脈における「顔のランドマーク」とは、顔の重要な特徴点(目の位置、鼻の先端、口の輪郭、眉毛、顔の輪郭など)のことを指します。
使用モデル
今回は、MediaPipeのFace Landmarkerを使用します。Face Landmarkerは、顔のランドマーク検出モデルであり、1つの顔から478個のランドマークを検出します。これらのランドマークは3次元座標(x, y, z)で表現されます。
以下からモデルをインストールしました。
使用画像
AIに生成させた画像を使用して、顔のランドマークを検出します。
前提条件
筆者の環境
- OS: macOS Sequoia
- Python: 3.12.0
- パッケージマネージャー: uv
依存パッケージ
- mediapipe 0.10.0
- opencv-python 4.8.0
- numpy 1.24.0
主要なコードの説明
以下は、顔のランドマークを検出するPythonコードです:
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
def detect_face_landmarks(image_path):
model_path = 'face_landmarker.task'
# Face Landmarkerの設定
base_options = python.BaseOptions(model_asset_path=model_path)
options = vision.FaceLandmarkerOptions(
base_options=base_options,
output_face_blendshapes=True, # ブレンドシェイプを出力
num_faces=1
)
# Face Landmarkerを作成
detector = vision.FaceLandmarker.create_from_options(options)
# 画像を読み込む
cv_image = cv2.imread(image_path)
rgb_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_image)
# ランドマークを検出
detection_result = detector.detect(image)
return detection_result
1. Face Landmarkerの設定
options = vision.FaceLandmarkerOptions(
base_options=base_options,
output_face_blendshapes=True, # ブレンドシェイプを出力
num_faces=1 # 検出する顔の数
)
Face Landmarkerのオプションを設定します:
-
output_face_blendshapes: 表情の動きを表す52種類のブレンドシェイプ係数を取得します -
num_faces: 同時に検出する顔の数を指定します(今回は1つ)
その他のオプションについては、以下のリンクを参照してください。
ブレンドシェイプとは
ブレンドシェイプは、顔の表情を表す52種類のパラメータです。各パラメータは0.0〜1.0の値を取ります。
ブレンドシェイプの例:
-
mouthSmileLeft/Right: 笑顔(口角の上昇) -
eyeBlinkLeft/Right: まばたき -
jawOpen: 口の開き -
browInnerUp: 眉の内側を上げる
出力される52種類のパラメータに関する詳しい説明は公式サイトにありませんが、ブレンドシェイプのパラメータ名(mouthSmileLeft、eyeBlinkLeftなど)は、AppleのARKitのBlendShapeLocationと似た名前になっているため、以下に参考としてリンクを記載しておきます。
2. ランドマークの検出
cv_image = cv2.imread(image_path)
rgb_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_image)
detection_result = detector.detect(image)
画像から顔を検出し、478個のランドマーク座標を取得します。OpenCVで読み込んだ画像はBGR形式なので、RGB形式に変換してからMediaPipeに渡します。
実行結果
スクリプトを実行
$ uv run face_landmark_detection.py
スクリプトを実行すると、以下のように、値が大きいブレンドシェイプとランドマークを描画した画像が出力されます。
※実行したコードは補足に記載してあります。
出力テキスト
検出された顔の数: 1
=== ブレンドシェイプ (上位10個) ===
1. mouthSmileLeft: 0.6559
2. eyeSquintLeft: 0.5036
3. mouthSmileRight: 0.4768
4. eyeSquintRight: 0.4058
5. mouthPressLeft: 0.1983
6. eyeLookOutRight: 0.1290
7. eyeLookInLeft: 0.1175
8. eyeLookDownLeft: 0.1114
9. eyeLookUpRight: 0.1059
10. eyeLookDownRight: 0.1004
出力画像
最後に
本記事では、PythonでMediaPipeを使って顔のランドマーク検出を実装する方法を紹介しました。
MediaPipeを使用することで機械学習モデルの学習をせずに顔画像からランドマークやブレンドシェイプを取得することができました。
補足
使用したコード
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import numpy as np
def detect_face_landmarks(image_path):
# モデルファイルのパスを取得
model_path = 'face_landmarker.task'
# Face Landmarkerの設定
base_options = python.BaseOptions(model_asset_path=model_path)
options = vision.FaceLandmarkerOptions(
base_options=base_options,
output_face_blendshapes=True, # ブレンドシェイプを出力
num_faces=1 # 検出する顔の数
)
# Face Landmarkerを作成
detector = vision.FaceLandmarker.create_from_options(options)
# 画像を読み込む
cv_image = cv2.imread(image_path)
rgb_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_image)
# ランドマークを検出
detection_result = detector.detect(image)
return detection_result
def process_results(image_path, detection_result):
# OpenCVで画像を読み込む
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
if detection_result.face_landmarks:
print(f"\n検出された顔の数: {len(detection_result.face_landmarks)}")
# 最初の顔のランドマークを処理
face_landmarks = detection_result.face_landmarks[0]
# ランドマークを画像に描画
annotated_image = draw_landmarks(image_rgb, face_landmarks)
# ブレンドシェイプがあれば表示
if detection_result.face_blendshapes:
print_blendshapes(detection_result.face_blendshapes[0])
# 結果を保存
output_path = 'face_landmarks_output.png'
cv2.imwrite(output_path, cv2.cvtColor(annotated_image, cv2.COLOR_RGB2BGR))
else:
print("顔が検出されませんでした")
def draw_landmarks(image, face_landmarks):
annotated_image = image.copy()
height, width, _ = annotated_image.shape
# ランドマークを描画
for idx, landmark in enumerate(face_landmarks):
# 正規化座標をピクセル座標に変換
x = int(landmark.x * width)
y = int(landmark.y * height)
# ランドマークを円で描画
cv2.circle(annotated_image, (x, y), 1, (0, 255, 0), -1)
# 主要なランドマークを強調表示
# 左目、右目、鼻先、口の両端
key_points = [33, 263, 1, 61, 291] # MediaPipeのランドマークインデックス
for idx in key_points:
if idx < len(face_landmarks):
landmark = face_landmarks[idx]
x = int(landmark.x * width)
y = int(landmark.y * height)
cv2.circle(annotated_image, (x, y), 3, (255, 0, 0), -1)
return annotated_image
def print_blendshapes(face_blendshapes):
print("\n=== ブレンドシェイプ (上位10個) ===")
# スコアでソート
sorted_blendshapes = sorted(
face_blendshapes,
key=lambda x: x.score,
reverse=True
)
# 上位10個を表示
for i, blendshape in enumerate(sorted_blendshapes[:10]):
category_name = blendshape.category_name
score = blendshape.score
print(f"{i+1}. {category_name}: {score:.4f}")
def main():
# 画像ファイルのパス
image_path = 'face.jpeg'
# ランドマーク検出を実行
detection_result = detect_face_landmarks(image_path)
# 結果を処理して表示
process_results(image_path, detection_result)
if __name__ == "__main__":
main()

