MediaPipe とは
MediaPipe は Google が公開しているマルチモーダルな機械学習基盤で、画像や映像から手、顔、体、姿勢などを精度よく、リアルタイムに検出できます。
今回はその中でも 「FaceMesh」 を使います。
顔に対して最大 468 点のランドマーク を返し、まばたき・目の開き・口の形状・表情などを細かく捉えることが可能です。
そのため、目の開閉や口の動きといった表情の微妙な変化を数値データとして扱いたいときに非常に有用です。
Mediapipe VS 目の奥が笑っていない人
小さな表情の変化を捉える技術を使い道としてよい題材を見つけてしまいました。
この顔を疲労度が高いと認識できるのかアプリをつくってみました。
使用する技術は Python, Mediapipe, OpenCV, Gradio です。
単なるランドマーク検出の紹介ではなく、EAR(Eye Aspect Ratio)、MAR(Mouth Aspect Ratio) を用いて疲労の兆候を抽出しています。
完成したもの
プロジェクトはこちらです。
画像から目と口を特定し必要な指標を計算します。
これを動画の各フレームに対して行い、累計からスコアを算出します。
この若者は疲れているという判定をしましたが、こちらのダンディーな男性は疲れていないという判定でした。
動画は Sora2 でつくりました。
実装のポイント
1. Gradio を使った UI 実装
Gradio は Python のコードだけで機械学習モデルや画像処理アプリのインターフェースを簡易に構築できるライブラリです。特に、以下のような特徴があり、データ分析・CV モデルの試作環境として相性が良いと考えています。
特徴1. ブラウザベースの UI を Python だけで構築できる
HTML/CSS/JavaScript を直接書かずに Web UI を作れます。画像アップロード、動画プレビュー、スライダー、ボタン、Markdown 表示などの UI コンポーネントを Python だけで定義できます。
例えば次のような UI も数行で構築できます。
combined_input = gr.Image(label="顔写真をアップロード", type="numpy")
combined_btn = gr.Button("分析を実行")
特徴2. Colab / ローカル環境ですぐ動く
機械学習の試作では動作環境の構築が面倒です。Gradio は Colab や ローカルの Python 環境で demo.launch() を実行するだけでブラウザに UI が立ち上がるため、環境構築の負担が小さく、動作確認を素早く行えます。
if __name__ == "__main__":
demo.launch()
2. EAR を計算する関数
疲れ(や眠気)は、目に関して以下の形で現れると言われます。
- まばたきが増える
- 目が開きにくくなる
- 目の縦幅が狭くなる
そのため、EAR(Eye Aspect Ratio) を使い、「目がどれくらい開いているか」 を定量化します。
EAR は目の縦幅と横幅を比較して算出します。
def calculate_eye_aspect_ratio(eye_points, image_shape):
h, w = image_shape[:2]
p1 = np.array([eye_points[0].x * w, eye_points[0].y * h])
p2 = np.array([eye_points[1].x * w, eye_points[1].y * h])
p3 = np.array([eye_points[2].x * w, eye_points[2].y * h])
p4 = np.array([eye_points[3].x * w, eye_points[3].y * h])
p5 = np.array([eye_points[4].x * w, eye_points[4].y * h])
p6 = np.array([eye_points[5].x * w, eye_points[5].y * h])
vertical_dist1 = np.linalg.norm(p2 - p6)
vertical_dist2 = np.linalg.norm(p3 - p5)
horizontal_dist = np.linalg.norm(p1 - p4)
ear = (vertical_dist1 + vertical_dist2) / (2.0 * horizontal_dist)
return ear
この比率を計算することで、
0.25 以上 → 目が開いている
0.20 以下 → 目が閉じ気味
という判定ができます。
3. MAR を計算する関数
MAR(Mouth Aspect Ratio)は口の縦横の比率です。
※MAR が高い=口が大きく開いている状態
長い時間もしくは頻繁な口の開いている状態は、疲労や眠気の兆候として扱えます。
MAR は口の縦幅と横幅の比率です。
def calculate_mouth_aspect_ratio(mouth_points, image_shape):
h, w = image_shape[:2]
left_point = np.array([mouth_points[0].x * w, mouth_points[0].y * h])
top_point = np.array([mouth_points[2].x * w, mouth_points[2].y * h])
right_point = np.array([mouth_points[6].x * w, mouth_points[6].y * h])
bottom_point = np.array([mouth_points[10].x * w, mouth_points[10].y * h])
vertical_dist = np.linalg.norm(top_point - bottom_point)
horizontal_dist = np.linalg.norm(left_point - right_point)
mar = vertical_dist / horizontal_dist
return mar
4. フレームごとの EAR/MAR の推移から疲労スコア算出
動画解析では、次のように EAR/MAR を履歴として保持します:
ear_history = deque(maxlen=window_size)
mar_history = deque(maxlen=window_size)
ウィンドウサイズは Gradio のスライダーで調整可能です。
直近 N フレームに対し、以下の割合を計算します:
low_ear_ratio = sum(1 for e in ear_history if e < ear_threshold) / len(ear_history)
high_mar_ratio = sum(1 for m in mar_history if m > mar_threshold) / len(mar_history)
最後に疲労スコアを算出します。係数については根拠がないですが、EAR や MAR は眠気や疲労の研究によく用いられます。
fatigue_score = int((low_ear_ratio * 70) + (high_mar_ratio * 30))
まばたきやあくびの回数も同時にカウントし、フレームに描画しています。
疲れている人の動画について
Sora2 入力したプロンプトは以下です。
一見普通に見えて笑顔がぎこちなく、目の奥が笑っていない、
そんな疲れた感じの40代のサラリーマンのインタビュー動画をつくってください。
正面を向いていて、胸より上と顔全体が移るくらいの範囲で、
表情がよく見えるようにしてください。





