STEP 21:姿勢推定(Pose Estimation)

🤸 STEP 21: 姿勢推定(Pose Estimation)

人体のキーポイント検出、OpenPose、MediaPipe Pose、3D姿勢推定を学び、
スポーツ分析やフィットネスアプリに応用します

📋 このステップで学ぶこと

  • 姿勢推定(Pose Estimation)の基礎とキーポイント
  • 2D姿勢推定と3D姿勢推定の違い
  • OpenPose(Part Affinity Fields)の仕組み
  • MediaPipe Poseによるリアルタイム姿勢推定
  • 関節角度の計算とフォーム分析
  • 3D姿勢推定の手法と応用
  • 実装:姿勢推定システムの構築

🎯 1. 姿勢推定の基礎

姿勢推定(Pose Estimation)は、画像や動画から人体の姿勢を推定する技術です。具体的には、人体の関節の位置(キーポイント)を検出します。

1-1. 姿勢推定とは

💡 姿勢推定の定義

目的:画像や動画から人体の姿勢を推定する
具体的には:人体の関節(キーポイント)の位置を検出する

出力:
・各キーポイントの座標(x, y)または(x, y, z)
・各キーポイントの信頼度スコア(0〜1)

例:
「右肩」の位置 = (250, 180)、信頼度 = 0.95
「右肘」の位置 = (280, 280)、信頼度 = 0.92

1-2. キーポイントとは

キーポイントは人体の特徴的な点(関節など)のことです。代表的なキーポイントの種類を見てみましょう。

【キーポイントの種類】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ COCO形式(17点) – 物体検出でよく使われる標準形式 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0: 鼻 / \ 1:左目 2:右目 | | 3:左耳 4:右耳 5:左肩 ─────── 6:右肩 │ │ 7:左肘 8:右肘 │ │ 9:左手首 10:右手首 11:左腰 ─────── 12:右腰 │ │ 13:左膝 14:右膝 │ │ 15:左足首 16:右足首 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ MediaPipe Pose(33点) – より詳細なキーポイント ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ COCOの17点に加えて: ・顔のキーポイント(目の内側・外側、口) ・手のキーポイント(親指、人差し指、小指) ・足のキーポイント(かかと、つま先) 合計33点で、より詳細な姿勢を表現可能 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ OpenPose(25点 + 手21点×2 + 顔70点) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 身体: 25点(COCOより詳細) 手: 各21点(指の関節まで検出) 顔: 70点(表情の詳細な分析が可能)

1-3. 2D姿勢推定と3D姿勢推定

【2D姿勢推定と3D姿勢推定の違い】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2D姿勢推定 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 出力: 画像平面上の(x, y)座標 ┌─────────────────────────────┐ │ │ │ ● │ ← 鼻(320, 100) │ /│\ │ │ ● │ ● │ ← 左肩(250, 180)、右肩(390, 180) │ │ │ │ ● │ │ / \ │ │ ● ● │ └─────────────────────────────┘ 特徴: ・画像の2次元座標のみ ・奥行き情報なし ・高速処理が可能 代表的手法: ・OpenPose ・MediaPipe Pose(2D出力) ・HRNet ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3D姿勢推定 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 出力: 3次元空間の(x, y, z)座標 z(奥行き) ↑ │ ● 鼻 │ /│\ │ ● │ ● 肩 │ │ │ ● └───────────→ x / ↙ y 特徴: ・奥行き(z座標)を含む ・より詳細な姿勢分析が可能 ・計算コストが高い 代表的手法: ・MediaPipe Pose(3D出力) ・VideoPose3D ・深度カメラ(Kinect、RealSense) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 使い分け ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2D姿勢推定が適している場合: ・リアルタイム処理が必要 ・フィットネスアプリ(基本的なフォームチェック) ・ジェスチャー認識 3D姿勢推定が適している場合: ・詳細な動作分析 ・VR/ARアプリケーション ・スポーツ科学(バイオメカニクス) ・医療リハビリ

1-4. 姿勢推定の応用分野

分野 応用例
スポーツ分析 フォームの自動分析(ゴルフスイング、野球の投球、テニス)、選手のパフォーマンス評価、怪我リスクの検出
フィットネス エクササイズのフォームチェック、スクワット・腕立て伏せのカウント、カロリー消費の推定
ヘルスケア リハビリテーションの進捗管理、歩行分析、転倒検出(高齢者見守り)
AR/VR・ゲーム 全身モーションキャプチャ、アバターの制御、ジェスチャー認識、VTuber
映像制作 モーションキャプチャ、3Dキャラクターのアニメーション生成
セキュリティ 異常行動の検出、不審者の動作分析

🔍 2. OpenPose(Part Affinity Fields)

OpenPoseは2017年にカーネギーメロン大学(CMU)が発表した、リアルタイム多人数姿勢推定の先駆的な手法です。

2-1. OpenPoseの特徴

【OpenPoseの概要】 開発: カーネギーメロン大学(CMU)、2017年 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 主な特徴 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. リアルタイム多人数姿勢推定 ・複数の人物を同時に検出可能 ・GPUを使えば30 FPS以上 2. ボトムアップアプローチ ・まずすべてのキーポイントを検出 ・その後、各人物に割り当て ・人数に依存しない処理時間 3. Part Affinity Fields(PAF) ・キーポイント間の関連付けに使用 ・複数人でも正確に骨格を構築 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 検出可能な要素 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 身体: 25点のキーポイント 手: 各21点(左右で42点) 顔: 70点 → 合計最大137点のキーポイントを検出可能

2-2. Part Affinity Fields(PAF)の仕組み

OpenPoseの核心技術であるPart Affinity Fields(PAF)について詳しく見ていきましょう。

【PAF(Part Affinity Fields)とは】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 問題: 複数人がいる場合のキーポイント割り当て ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 画像に3人いる場合: 肩の候補: 6個(左右×3人) 肘の候補: 6個(左右×3人) どの肩とどの肘が同じ人物か? → 単純な組み合わせだと 6×6 = 36通り! ●─────● 人物A / \ / \ ● ● ● ● ●─────● 人物B / \ / \ ● ● ● ● すべての組み合わせを試すと計算爆発! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ PAFの解決策: 2Dベクトル場を使う ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ アイデア: キーポイント間を結ぶ「方向」を画像上に記録する 例: 肩から肘への場合 肩の位置から肘の位置に向かうベクトルを その経路上のすべてのピクセルに割り当てる 肩 ● ↘ ↘ ← この経路上にベクトルを配置 ↘ ●肘 PAFマップ: 画像の各ピクセルに2D方向ベクトル(vx, vy)を持つ 肩から肘に向かう部分には、その方向のベクトルが配置 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 経路積分によるスコア計算 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 候補のペア(肩A, 肘B)が同じ人物かどうか判定: 1. 肩Aから肘Bを結ぶ直線を考える 2. その直線上でPAFベクトルを積分 3. 直線の方向とPAFベクトルの方向が一致していれば高スコア 正しいペア(同じ人物): 肩 ● → → → → ● 肘 PAFベクトルと直線の方向が一致 → スコア高い 間違ったペア(異なる人物): 肩A ● → → → → ● 肘B(別の人物) PAFベクトルの方向が違う(別の肘を向いている) → スコア低い ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 処理の全体像 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ1: 特徴抽出 入力画像 → CNN → 画像特徴 ステップ2: ヒートマップ生成 → 各キーポイントの位置候補を検出 → 「ここに肩がありそう」「ここに肘がありそう」 ステップ3: PAF生成 → キーポイント間の方向ベクトル場を生成 → 「肩から肘への方向」を画像上に配置 ステップ4: グラフマッチング → PAFのスコアを使って最適な組み合わせを選択 → 各人物のキーポイントを決定 出力: 各人物の骨格(キーポイント座標と接続)

2-3. OpenPoseの利点と欠点

利点 欠点
✓ 多人数対応(複数人を同時に検出可能) ✗ GPU必須(CPUでは数FPSと遅い)
✓ リアルタイム(GPU使用で30+ FPS) ✗ セットアップが複雑
✓ 高精度(ベンチマークで高性能) ✗ メモリ消費が大きい
✓ オープンソース(コード・モデル公開) ✗ 2Dのみ(3D情報は直接得られない)
✓ 手・顔も検出可能 ✗ 商用利用にはライセンス注意

📱 3. MediaPipe Pose(リアルタイム姿勢推定)

MediaPipe PoseはGoogleが2020年に発表した姿勢推定ライブラリです。CPUでもリアルタイム処理が可能で、モバイル端末でも動作します。

3-1. MediaPipe Poseの特徴

【MediaPipe Poseの概要】 開発: Google、2020年 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 主な特徴 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 超高速 ・CPUでも30+ FPS(リアルタイム処理) ・GPUがなくても動作 2. 簡単セットアップ ・pip install mediapipe で完了 ・複雑な環境構築不要 3. モバイル対応 ・スマートフォン、タブレットで動作 ・iOS、Android対応 4. 33点のキーポイント ・COCOの17点より詳細 ・手・足の先端まで検出 5. 2Dと3D両対応 ・pose_landmarks: 2D座標(画像座標) ・pose_world_landmarks: 3D座標(メートル単位) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 33点のキーポイント ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 顔(11点): 0: 鼻 1-4: 目(内側・外側) 5-6: 耳 7-10: 口 体幹(8点): 11-12: 肩(左・右) 23-24: 腰(左・右) 腕(6点): 13-14: 肘(左・右) 15-16: 手首(左・右) 17-22: 手の指先 脚(8点): 25-26: 膝(左・右) 27-28: 足首(左・右) 29-32: 足の指先・かかと ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 処理の流れ(2段階パイプライン) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ1: 人物検出(BlazePose Detector) 入力画像 → 人物のBounding Boxを検出 ステップ2: 姿勢推定(BlazePose Tracker) 人物領域 → 33点のキーポイントを検出 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 注意点 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ・単一人物のみ対応(複数人は検出不可) ・複数人が必要な場合はOpenPoseを使用

3-2. 実装:MediaPipe Poseの基本

MediaPipe Poseを使って画像から姿勢を推定してみましょう。

※ コードが横に長い場合は横スクロールできます

# =================================================== # MediaPipe Poseで姿勢推定 # =================================================== # インストール(初回のみ) # !pip install mediapipe opencv-python import cv2 import mediapipe as mp import numpy as np import matplotlib.pyplot as plt # =================================================== # 1. MediaPipeの初期化 # =================================================== # mp.solutions.pose: 姿勢推定モジュール mp_pose = mp.solutions.pose # mp.solutions.drawing_utils: 描画ユーティリティ # キーポイントと骨格を簡単に描画できる mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles print(“MediaPipeモジュールをロードしました”)

実行結果:

MediaPipeモジュールをロードしました
# =================================================== # 2. Poseオブジェクトの作成 # =================================================== # mp_pose.Pose(): 姿勢推定器を作成 # 各パラメータの意味: pose = mp_pose.Pose( # static_image_mode: True=画像モード、False=動画モード # 動画モードでは前フレームの情報を使って追跡(高速) static_image_mode=True, # model_complexity: モデルの複雑さ(0, 1, 2) # 0: 軽量(低精度、高速) # 1: バランス型(デフォルト) # 2: 高精度(低速) model_complexity=1, # enable_segmentation: True=人物のセグメンテーションも出力 enable_segmentation=False, # min_detection_confidence: 検出の信頼度閾値(0.0〜1.0) # この値以上の信頼度がないと検出しない min_detection_confidence=0.5, # min_tracking_confidence: トラッキングの信頼度閾値(動画モード時) min_tracking_confidence=0.5 ) print(“Poseオブジェクトを作成しました”)

実行結果:

Poseオブジェクトを作成しました
# =================================================== # 3. 画像の読み込みと姿勢推定 # =================================================== # 画像を読み込み # cv2.imread(): BGRカラーで読み込み image = cv2.imread(‘person_exercise.jpg’) # OpenCVはBGR、MediaPipeはRGBを期待するので変換 image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 画像サイズを取得(後で座標変換に使用) h, w, _ = image.shape print(f”画像サイズ: {w}×{h}”) # pose.process(): 姿勢推定を実行 # 引数: RGB形式の画像 # 戻り値: 検出結果(ランドマーク情報を含む) results = pose.process(image_rgb) # 検出できたかチェック if results.pose_landmarks: print(“姿勢を検出しました!”) print(f”検出されたキーポイント数: {len(results.pose_landmarks.landmark)}”) else: print(“姿勢を検出できませんでした”)

実行結果:

画像サイズ: 640×480 姿勢を検出しました! 検出されたキーポイント数: 33
# =================================================== # 4. キーポイントの座標を取得 # =================================================== if results.pose_landmarks: # results.pose_landmarks.landmark: 33個のランドマークのリスト landmarks = results.pose_landmarks.landmark # 主要なキーポイントのインデックス # MediaPipeの公式ドキュメントで定義されている keypoint_names = { 0: ‘鼻’, 11: ‘左肩’, 12: ‘右肩’, 13: ‘左肘’, 14: ‘右肘’, 15: ‘左手首’, 16: ‘右手首’, 23: ‘左腰’, 24: ‘右腰’, 25: ‘左膝’, 26: ‘右膝’, 27: ‘左足首’, 28: ‘右足首’ } print(“\n主要なキーポイントの座標:”) print(“-” * 50) for idx, name in keypoint_names.items(): # landmark.x, landmark.y: 正規化座標(0〜1) # 実際のピクセル座標に変換するには画像サイズを掛ける landmark = landmarks[idx] # ピクセル座標に変換 x = int(landmark.x * w) y = int(landmark.y * h) # visibility: そのキーポイントが見えているかの信頼度(0〜1) # 1に近いほど確実に見えている visibility = landmark.visibility print(f”{name:8s}: ({x:4d}, {y:4d}), 信頼度: {visibility:.3f}”)

実行結果:

主要なキーポイントの座標: ————————————————– 鼻 : ( 320, 105), 信頼度: 0.998 左肩 : ( 250, 185), 信頼度: 0.997 右肩 : ( 390, 185), 信頼度: 0.996 左肘 : ( 200, 285), 信頼度: 0.992 右肘 : ( 440, 285), 信頼度: 0.991 左手首 : ( 180, 380), 信頼度: 0.945 右手首 : ( 460, 380), 信頼度: 0.948 左腰 : ( 270, 340), 信頼度: 0.989 右腰 : ( 370, 340), 信頼度: 0.988 左膝 : ( 260, 430), 信頼度: 0.985 右膝 : ( 380, 430), 信頼度: 0.984 左足首 : ( 250, 520), 信頼度: 0.972 右足首 : ( 390, 520), 信頼度: 0.971
# =================================================== # 5. 結果の可視化 # =================================================== # 描画用に画像をコピー image_with_pose = image_rgb.copy() if results.pose_landmarks: # mp_drawing.draw_landmarks(): キーポイントと骨格を描画 # 引数: # image: 描画先の画像 # landmark_list: ランドマークのリスト # connections: 接続情報(どのキーポイント同士を線で結ぶか) # landmark_drawing_spec: キーポイントの描画スタイル # connection_drawing_spec: 接続線の描画スタイル mp_drawing.draw_landmarks( image_with_pose, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, # 標準の骨格接続 landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style() ) # matplotlibで表示 plt.figure(figsize=(12, 8)) plt.imshow(image_with_pose) plt.title(‘MediaPipe Pose Detection’) plt.axis(‘off’) plt.tight_layout() plt.savefig(‘mediapipe_pose_result.png’, dpi=150, bbox_inches=’tight’) plt.show() print(“\nmediapipe_pose_result.png を保存しました”)
# =================================================== # 6. リソースの解放 # =================================================== # pose.close(): Poseオブジェクトを閉じてリソースを解放 pose.close() print(“リソースを解放しました”)
🎯 完成コード(画像の姿勢推定)

以下は上記のコードをまとめた完成版です:

# MediaPipe Pose – 画像の姿勢推定 完成コード import cv2 import mediapipe as mp import matplotlib.pyplot as plt # MediaPipeの初期化 mp_pose = mp.solutions.pose mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles # Poseオブジェクトを作成 pose = mp_pose.Pose( static_image_mode=True, model_complexity=1, min_detection_confidence=0.5 ) # 画像を読み込み image = cv2.imread(‘person_exercise.jpg’) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w, _ = image.shape # 姿勢推定を実行 results = pose.process(image_rgb) # 結果を描画 image_with_pose = image_rgb.copy() if results.pose_landmarks: mp_drawing.draw_landmarks( image_with_pose, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style() ) # キーポイント座標を表示 for idx, landmark in enumerate(results.pose_landmarks.landmark): x = int(landmark.x * w) y = int(landmark.y * h) print(f”キーポイント{idx}: ({x}, {y})”) # 表示 plt.figure(figsize=(12, 8)) plt.imshow(image_with_pose) plt.title(‘MediaPipe Pose’) plt.axis(‘off’) plt.savefig(‘pose_result.png’, dpi=150, bbox_inches=’tight’) plt.show() pose.close()

3-3. リアルタイム姿勢推定(Webカメラ)

Webカメラからの映像でリアルタイムに姿勢推定を行います。

# =================================================== # Webカメラでリアルタイム姿勢推定 # =================================================== import cv2 import mediapipe as mp # MediaPipeの初期化 mp_pose = mp.solutions.pose mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles # 動画モードでPoseを作成 # static_image_mode=Falseで動画モード(前フレームの情報を活用して高速化) pose = mp_pose.Pose( static_image_mode=False, # 動画モード model_complexity=1, smooth_landmarks=True, # ランドマークの平滑化(ブレ軽減) min_detection_confidence=0.5, min_tracking_confidence=0.5 ) # cv2.VideoCapture(0): Webカメラを開く # 0 = デフォルトカメラ、1 = 2番目のカメラ cap = cv2.VideoCapture(0) print(“Webカメラを開始しました”) print(“終了するには ‘q’ キーを押してください”) while cap.isOpened(): # フレームを取得 # success: 取得成功したかどうか(True/False) # frame: 取得したフレーム(BGR形式) success, frame = cap.read() if not success: print(“フレームの取得に失敗しました”) break # BGRからRGBに変換(MediaPipe用) frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 姿勢推定を実行 results = pose.process(frame_rgb) # 結果を描画 if results.pose_landmarks: mp_drawing.draw_landmarks( frame, # BGR形式のフレームに直接描画 results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style() ) # cv2.imshow(): ウィンドウに表示 cv2.imshow(‘MediaPipe Pose – Press Q to Quit’, frame) # cv2.waitKey(5): 5ミリ秒キー入力を待つ # 0xFF == ord(‘q’): ‘q’キーが押されたら if cv2.waitKey(5) & 0xFF == ord(‘q’): break # リソースを解放 cap.release() cv2.destroyAllWindows() pose.close() print(“終了しました”)

📐 4. 角度計算とフォーム分析

姿勢推定の重要な応用として、関節の角度を計算してフォームを分析する方法を学びます。

4-1. 3点から角度を計算する原理

【3点から角度を計算する方法】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 例: 肘の角度を計算する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3つのキーポイントを使用: A: 肩の位置 B: 肘の位置(角度を測る頂点) C: 手首の位置 A(肩) ● / / ← ベクトルBA / ● B(肘) ← ここの角度を測りたい \ \ ← ベクトルBC \ ● C(手首) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 計算手順 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ1: ベクトルを計算 ベクトルBA = A – B = (Ax – Bx, Ay – By) ベクトルBC = C – B = (Cx – Bx, Cy – By) ステップ2: 内積を計算 BA · BC = (Ax – Bx)(Cx – Bx) + (Ay – By)(Cy – By) ステップ3: ベクトルの大きさを計算 |BA| = √((Ax – Bx)² + (Ay – By)²) |BC| = √((Cx – Bx)² + (Cy – By)²) ステップ4: コサインを計算 cos(θ) = (BA · BC) / (|BA| × |BC|) ステップ5: 角度を計算 θ = arccos(cos(θ)) ※ arccosの結果はラジアンなので、度に変換する θ(度) = θ(ラジアン) × 180 / π ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 具体例 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 肩A = (250, 180) 肘B = (280, 280) 手首C = (310, 380) ベクトルBA = (250-280, 180-280) = (-30, -100) ベクトルBC = (310-280, 380-280) = (30, 100) 内積 = (-30)(30) + (-100)(100) = -900 – 10000 = -10900 |BA| = √(900 + 10000) = √10900 ≈ 104.4 |BC| = √(900 + 10000) = √10900 ≈ 104.4 cos(θ) = -10900 / (104.4 × 104.4) ≈ -1.0 θ ≈ 180度(腕がほぼ伸びている状態)

4-2. 実装:角度計算関数

# =================================================== # 3点から角度を計算する関数 # =================================================== import numpy as np def calculate_angle(a, b, c): “”” 3点から角度を計算する関数 引数: a: 点Aの座標 (x, y) – 例: 肩 b: 点Bの座標 (x, y) – 角度を測る頂点(例: 肘) c: 点Cの座標 (x, y) – 例: 手首 戻り値: 角度(度数法、0〜180度) 使用例: angle = calculate_angle(shoulder, elbow, wrist) “”” # リストやタプルをNumPy配列に変換 # NumPy配列にすることで、ベクトル演算が簡単にできる a = np.array(a) b = np.array(b) c = np.array(c) # ベクトルを計算 # ba: 点Bから点Aへのベクトル # bc: 点Bから点Cへのベクトル ba = a – b bc = c – b # 内積を計算 # np.dot(): 2つのベクトルの内積 dot_product = np.dot(ba, bc) # ベクトルの大きさ(ノルム)を計算 # np.linalg.norm(): ベクトルの大きさを計算 magnitude_ba = np.linalg.norm(ba) magnitude_bc = np.linalg.norm(bc) # コサインを計算 # ゼロ除算を防ぐため、大きさが0でないことを確認 if magnitude_ba == 0 or magnitude_bc == 0: return 0 cosine_angle = dot_product / (magnitude_ba * magnitude_bc) # 数値誤差で-1〜1の範囲を超えることがあるのでクリップ # np.clip(): 値を指定範囲に制限 cosine_angle = np.clip(cosine_angle, -1.0, 1.0) # アークコサインで角度(ラジアン)を計算 # np.arccos(): コサインから角度を計算 angle_radians = np.arccos(cosine_angle) # ラジアンから度に変換 # np.degrees(): ラジアンを度に変換 angle_degrees = np.degrees(angle_radians) return angle_degrees # テスト shoulder = (250, 180) elbow = (280, 280) wrist = (310, 380) angle = calculate_angle(shoulder, elbow, wrist) print(f”肘の角度: {angle:.1f}度”)

実行結果:

肘の角度: 180.0度

4-3. 実装:スクワットのフォーム分析

# =================================================== # スクワットのフォーム分析 # =================================================== import cv2 import mediapipe as mp import numpy as np def calculate_angle(a, b, c): “””3点から角度を計算””” a = np.array(a) b = np.array(b) c = np.array(c) ba = a – b bc = c – b cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) cosine_angle = np.clip(cosine_angle, -1.0, 1.0) return np.degrees(np.arccos(cosine_angle)) def analyze_squat(landmarks, h, w): “”” スクワットのフォームを分析 引数: landmarks: MediaPipeのランドマークリスト h, w: 画像の高さと幅 戻り値: 分析結果の辞書 “”” # 左脚のキーポイントを取得 # インデックスはMediaPipe Poseの定義に基づく left_hip = [landmarks[23].x * w, landmarks[23].y * h] left_knee = [landmarks[25].x * w, landmarks[25].y * h] left_ankle = [landmarks[27].x * w, landmarks[27].y * h] # 右脚のキーポイントを取得 right_hip = [landmarks[24].x * w, landmarks[24].y * h] right_knee = [landmarks[26].x * w, landmarks[26].y * h] right_ankle = [landmarks[28].x * w, landmarks[28].y * h] # 両膝の角度を計算 left_knee_angle = calculate_angle(left_hip, left_knee, left_ankle) right_knee_angle = calculate_angle(right_hip, right_knee, right_ankle) # 平均角度 avg_knee_angle = (left_knee_angle + right_knee_angle) / 2 # フォーム判定 if avg_knee_angle < 90: depth = "深い(Excellent!)" score = 100 elif avg_knee_angle < 110: depth = "適度(Good)" score = 80 elif avg_knee_angle < 130: depth = "浅め(もう少し深く)" score = 60 else: depth = "浅い(もっと深く!)" score = 40 return { 'left_knee_angle': left_knee_angle, 'right_knee_angle': right_knee_angle, 'avg_knee_angle': avg_knee_angle, 'depth': depth, 'score': score } # =================================================== # スクワット分析の実行 # =================================================== mp_pose = mp.solutions.pose pose = mp_pose.Pose(static_image_mode=True, model_complexity=1) # 画像を読み込み image = cv2.imread('squat.jpg') image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w, _ = image.shape # 姿勢推定 results = pose.process(image_rgb) if results.pose_landmarks: # スクワット分析 analysis = analyze_squat(results.pose_landmarks.landmark, h, w) print("=" * 50) print("スクワット フォーム分析") print("=" * 50) print(f"左膝の角度: {analysis['left_knee_angle']:.1f}度") print(f"右膝の角度: {analysis['right_knee_angle']:.1f}度") print(f"平均角度: {analysis['avg_knee_angle']:.1f}度") print(f"深さ評価: {analysis['depth']}") print(f"スコア: {analysis['score']}/100") pose.close()

実行結果:

================================================== スクワット フォーム分析 ================================================== 左膝の角度: 85.3度 右膝の角度: 87.1度 平均角度: 86.2度 深さ評価: 深い(Excellent!) スコア: 100/100

4-4. 実装:バイセップカールのカウント

# =================================================== # バイセップカール(上腕二頭筋のトレーニング)のカウント # =================================================== import cv2 import mediapipe as mp import numpy as np def calculate_angle(a, b, c): “””3点から角度を計算””” a = np.array(a) b = np.array(b) c = np.array(c) ba = a – b bc = c – b cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) cosine_angle = np.clip(cosine_angle, -1.0, 1.0) return np.degrees(np.arccos(cosine_angle)) # MediaPipeの初期化 mp_pose = mp.solutions.pose mp_drawing = mp.solutions.drawing_utils pose = mp_pose.Pose( static_image_mode=False, model_complexity=1, min_detection_confidence=0.5, min_tracking_confidence=0.5 ) # カウンター変数 count = 0 # カール回数 stage = “down” # 現在の状態(down: 腕が伸びている、up: 腕が曲がっている) # Webカメラを開く cap = cv2.VideoCapture(0) print(“バイセップカール カウンター”) print(“終了するには ‘q’ キーを押してください”) while cap.isOpened(): success, frame = cap.read() if not success: break # RGB変換 frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, _ = frame.shape # 姿勢推定 results = pose.process(frame_rgb) if results.pose_landmarks: landmarks = results.pose_landmarks.landmark # 右腕のキーポイントを取得 shoulder = [landmarks[12].x * w, landmarks[12].y * h] elbow = [landmarks[14].x * w, landmarks[14].y * h] wrist = [landmarks[16].x * w, landmarks[16].y * h] # 肘の角度を計算 elbow_angle = calculate_angle(shoulder, elbow, wrist) # カウントロジック # 腕が伸びている(角度 > 160度)→ down状態 # 腕が曲がっている(角度 < 40度)→ up状態 # down → up への遷移で1回とカウント if elbow_angle > 160: stage = “down” if elbow_angle < 40 and stage == "down": stage = "up" count += 1 print(f"カウント: {count}") # 骨格を描画 mp_drawing.draw_landmarks( frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS ) # 情報を画面に表示 cv2.putText(frame, f'Count: {count}', (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) cv2.putText(frame, f'Angle: {int(elbow_angle)}', (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) cv2.putText(frame, f'Stage: {stage}', (50, 140), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) cv2.imshow('Bicep Curl Counter', frame) if cv2.waitKey(5) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() pose.close() print(f"\n最終カウント: {count}回")

🌐 5. 3D姿勢推定

2D姿勢推定では画像平面上の座標しか得られませんが、3D姿勢推定では奥行き情報も取得できます。

5-1. 3D姿勢推定の手法

【3D姿勢推定の手法】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 手法1: 単眼カメラからの推定(2D→3D リフティング) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1枚の2D画像から3D姿勢を推定 処理の流れ: 2D画像 → 2D姿勢推定 → 深度推定 → 3D座標 代表的手法: ・VideoPose3D ・SimpleBaseline3D ・MediaPipe Pose(world_landmarks) 利点: ・通常のカメラで使用可能 ・追加のハードウェア不要 欠点: ・奥行きのあいまいさ(Depth Ambiguity) → 同じ2D投影に複数の3D姿勢が対応しうる ・精度に限界がある ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 手法2: ステレオカメラ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2台のカメラを使用し、三角測量で深度を計算 処理の流れ: カメラ1の画像 ─┐ ├→ 対応点検出 → 三角測量 → 3D座標 カメラ2の画像 ─┘ 利点: ・正確な深度情報 ・物理的に正しい3D座標 欠点: ・2台のカメラが必要 ・キャリブレーションが必要 ・セットアップが複雑 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 手法3: 深度カメラ(RGB-D) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 深度センサーを搭載したカメラを使用 代表的なデバイス: ・Microsoft Kinect ・Intel RealSense ・Apple TrueDepthカメラ(Face ID) 処理の流れ: RGB画像 + 深度マップ → 直接3D座標を取得 利点: ・直接深度を取得 ・高精度 欠点: ・特殊なハードウェアが必要 ・屋外では使用困難(赤外線が太陽光に干渉) ・測定距離に制限

5-2. MediaPipe Poseで3D座標を取得

MediaPipe Poseは2D座標に加えて、3D座標も出力できます。

# =================================================== # MediaPipe Poseで3D座標を取得 # =================================================== import cv2 import mediapipe as mp import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # MediaPipeの初期化 mp_pose = mp.solutions.pose pose = mp_pose.Pose( static_image_mode=True, model_complexity=2 # 高精度モデル(3D推定に有利) ) # 画像を読み込み image = cv2.imread(‘person.jpg’) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 姿勢推定を実行 results = pose.process(image_rgb) # =================================================== # 2Dと3Dの座標を比較 # =================================================== if results.pose_landmarks and results.pose_world_landmarks: print(“=” * 60) print(“2D座標 vs 3D座標の比較”) print(“=” * 60) # 2D座標: pose_landmarks # 画像の正規化座標(0〜1) landmarks_2d = results.pose_landmarks.landmark # 3D座標: pose_world_landmarks # 実世界座標(メートル単位、腰を原点とした相対座標) landmarks_3d = results.pose_world_landmarks.landmark # 主要なキーポイントを比較 keypoints = { 0: ‘鼻’, 11: ‘左肩’, 12: ‘右肩’, 23: ‘左腰’, 24: ‘右腰’ } for idx, name in keypoints.items(): lm_2d = landmarks_2d[idx] lm_3d = landmarks_3d[idx] print(f”\n{name}:”) print(f” 2D: ({lm_2d.x:.3f}, {lm_2d.y:.3f})”) print(f” 3D: ({lm_3d.x:.3f}, {lm_3d.y:.3f}, {lm_3d.z:.3f}) [メートル]”)

実行結果:

============================================================ 2D座標 vs 3D座標の比較 ============================================================ 鼻: 2D: (0.500, 0.218) 3D: (0.012, -0.523, -0.082) [メートル] 左肩: 2D: (0.391, 0.385) 3D: (0.152, -0.341, -0.045) [メートル] 右肩: 2D: (0.609, 0.385) 3D: (-0.148, -0.342, -0.048) [メートル] 左腰: 2D: (0.422, 0.708) 3D: (0.095, 0.000, -0.012) [メートル] 右腰: 2D: (0.578, 0.708) 3D: (-0.095, 0.000, -0.015) [メートル]
# =================================================== # 3D座標を3Dプロットで可視化 # =================================================== if results.pose_world_landmarks: # 3D座標を取得 landmarks_3d = results.pose_world_landmarks.landmark # 全キーポイントの座標をNumPy配列に変換 points_3d = [] for landmark in landmarks_3d: points_3d.append([landmark.x, landmark.y, landmark.z]) points_3d = np.array(points_3d) print(f”\n3D座標の配列サイズ: {points_3d.shape}”) # (33, 3) # 3Dプロットを作成 fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111, projection=’3d’) # キーポイントをプロット # scatter(): 点をプロット ax.scatter( points_3d[:, 0], # X座標 points_3d[:, 2], # Z座標(奥行き)を Y軸に -points_3d[:, 1], # Y座標(反転して上を正に)を Z軸に c=’red’, marker=’o’, s=50 ) # 骨格の接続線を描画 connections = mp_pose.POSE_CONNECTIONS for connection in connections: start_idx = connection[0] end_idx = connection[1] ax.plot( [points_3d[start_idx, 0], points_3d[end_idx, 0]], [points_3d[start_idx, 2], points_3d[end_idx, 2]], [-points_3d[start_idx, 1], -points_3d[end_idx, 1]], ‘blue’, linewidth=2 ) # 軸ラベルとタイトル ax.set_xlabel(‘X (左右)’) ax.set_ylabel(‘Z (前後)’) ax.set_zlabel(‘Y (上下)’) ax.set_title(‘3D Pose Estimation’) # アスペクト比を調整 ax.set_box_aspect([1, 1, 1]) plt.tight_layout() plt.savefig(‘3d_pose_result.png’, dpi=150, bbox_inches=’tight’) plt.show() print(“\n3d_pose_result.png を保存しました”) pose.close()

📝 練習問題

問題1:2D姿勢推定と3D姿勢推定の違い(基礎)

2D姿勢推定と3D姿勢推定の違いを、出力形式、計算コスト、応用分野の観点から説明してください。

解答:
【2D姿勢推定と3D姿勢推定の比較】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2D姿勢推定 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 出力形式: 画像平面上の(x, y)座標 例: 肘の位置 = (280, 300) 計算コスト: 低い(CPUでもリアルタイム処理可能) MediaPipe: 30+ FPS(CPU) 応用分野: ・フィットネスアプリ(基本的なフォームチェック) ・ジェスチャー認識 ・2Dアニメーション ・簡易的な動作分析 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3D姿勢推定 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 出力形式: 3次元空間の(x, y, z)座標 例: 肘の位置 = (0.15, -0.34, -0.05) メートル 計算コスト: 高い(GPUが推奨、または深度カメラが必要) 応用分野: ・VR/ARアプリケーション(アバター制御) ・3Dモーションキャプチャ ・スポーツ科学(バイオメカニクス) ・医療リハビリ(歩行分析) ・ロボティクス ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 使い分けの指針 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2Dで十分な場合: ・正面からの撮影が主 ・奥行き方向の動きが少ない ・リアルタイム性が重要 3Dが必要な場合: ・任意の角度からの撮影 ・奥行き方向の動きが重要 ・精密な動作分析が必要

問題2:Part Affinity Fields(PAF)の仕組み(中級)

OpenPoseで使用されるPart Affinity Fields(PAF)が、複数人の姿勢推定でどのように役立つか説明してください。

解答:
【Part Affinity Fields(PAF)の役割】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 問題: キーポイントの人物への割り当て ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 複数人がいる画像では、検出されたキーポイントを 正しい人物に割り当てる必要がある 例: 3人の場合 ・肩のキーポイント: 6個(左右×3人) ・肘のキーポイント: 6個 → どの肩とどの肘が同じ人物か? ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ PAFの解決策 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 2Dベクトル場の生成 ・画像の各ピクセルに方向ベクトルを割り当て ・キーポイント間(例: 肩→肘)の方向を記録 2. 経路積分によるスコア計算 ・候補のペア(肩A, 肘B)を評価 ・2点を結ぶ直線上でPAFベクトルを積分 ・直線の方向とPAFの方向が一致 → 高スコア 3. グラフマッチング ・スコアに基づいて最適な組み合わせを選択 ・各人物のキーポイントセットを決定 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 利点 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 効率的 ・すべての組み合わせを試す必要がない ・人数に依存しない処理時間 2. 遮蔽に強い ・部分的に隠れていてもPAFで関連付け可能 3. ボトムアップ方式 ・まず全キーポイントを検出 ・その後で人物に割り当て → トップダウン方式より効率的

問題3:角度計算の実装(中級)

3点(肩、肘、手首)から肘の角度を計算するコードの原理と実装を説明してください。

解答:
【角度計算の原理】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 数学的原理 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3点A(肩)、B(肘)、C(手首)から、 Bでの角度θを計算する ステップ1: ベクトルを計算 BA = A – B(肘から肩へのベクトル) BC = C – B(肘から手首へのベクトル) ステップ2: 内積の公式を使用 BA · BC = |BA| × |BC| × cos(θ) cos(θ) = (BA · BC) / (|BA| × |BC|) ステップ3: アークコサインで角度を求める θ = arccos(cos(θ))

実装コード:

import numpy as np def calculate_angle(a, b, c): “”” 3点から角度を計算 a: 点A(肩) b: 点B(肘)- 角度を測る頂点 c: 点C(手首) 戻り値: 角度(度) “”” # NumPy配列に変換 a = np.array(a) b = np.array(b) c = np.array(c) # ベクトルを計算 ba = a – b # 肘→肩 bc = c – b # 肘→手首 # コサインを計算 # 内積 / (ベクトルの大きさの積) cosine = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) # 数値誤差対策 cosine = np.clip(cosine, -1.0, 1.0) # 角度に変換(ラジアン→度) angle = np.degrees(np.arccos(cosine)) return angle # 使用例 shoulder = (250, 180) elbow = (280, 280) wrist = (310, 380) angle = calculate_angle(shoulder, elbow, wrist) print(f”肘の角度: {angle:.1f}度”)

問題4:フィットネスアプリの設計(上級)

スクワットのフォームを自動チェックし、回数をカウントするフィットネスアプリを設計してください。必要な技術、処理の流れ、判定基準を含めてください。

解答:
【スクワット フィットネスアプリの設計】 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 使用技術 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 姿勢推定: MediaPipe Pose → CPUでリアルタイム処理可能 → スマートフォンでも動作 カメラ: デバイスの内蔵カメラ → Webカメラ または スマホカメラ フレームワーク: OpenCV + MediaPipe ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 処理の流れ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ① カメラからフレームを取得(30 FPS) ↓ ② MediaPipe Poseで姿勢推定 ↓ ③ キーポイントを抽出 ・腰(左右) ・膝(左右) ・足首(左右) ↓ ④ 膝の角度を計算 ・左膝角度 = calculate_angle(左腰, 左膝, 左足首) ・右膝角度 = calculate_angle(右腰, 右膝, 右足首) ↓ ⑤ フォーム判定 ↓ ⑥ カウント判定 ↓ ⑦ 画面にフィードバック表示 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. フォーム判定基準 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 膝の角度: < 90度: 深い(Excellent) 90-110度: 適度(Good) 110-130度: 浅め(もう少し深く) > 130度: 浅い(もっと深く) 追加チェック(オプション): ・膝がつま先より前に出ていないか ・背中が丸まっていないか ・左右のバランスは取れているか ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. カウントロジック ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 状態管理: stage = “up”(立っている状態) stage = “down”(しゃがんでいる状態) 遷移ルール: 膝角度 > 160度 → stage = “up” 膝角度 < 100度 かつ stage == "up" → stage = "down"、count += 1 これにより: ・立つ → しゃがむ で1回とカウント ・浅いスクワットはカウントされない ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5. フィードバック表示 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 画面に表示する情報: ・現在の膝角度 ・フォーム評価(Excellent/Good/もっと深く) ・カウント数 ・骨格の描画(正しいフォームは緑、悪いフォームは赤) 音声フィードバック(オプション): ・「もっと深く」「Good!」などの音声ガイド ・カウント時に効果音

📝 STEP 21 のまとめ

✅ このステップで学んだこと

1. 姿勢推定の基礎
・キーポイント:人体の関節位置を検出
・2D vs 3D:用途に応じて使い分け

2. OpenPose
・Part Affinity Fields(PAF)で複数人対応
・ボトムアップアプローチで効率的

3. MediaPipe Pose
・CPUでリアルタイム処理(30+ FPS)
・33点のキーポイント検出
・2Dと3D両対応

4. 角度計算とフォーム分析
・3点から角度を計算(ベクトルの内積)
・スクワット、バイセップカールなどのフォームチェック

5. 3D姿勢推定
・単眼カメラ、ステレオカメラ、深度カメラ
・MediaPipeのworld_landmarksで3D座標取得

💡 重要ポイント

MediaPipe Poseの強み:
・CPUでもリアルタイム処理可能
・簡単なセットアップ(pip installのみ)
・モバイル対応
→ フィットネスアプリに最適

OpenPoseの強み:
・複数人の同時検出
・Part Affinity Fieldsによる高精度
→ スポーツ分析、監視カメラに最適

応用の可能性:
姿勢推定は、スポーツ、フィットネス、医療、エンターテイメントなど幅広い分野で活用されています。角度計算を組み合わせることで、フォームの自動チェックやトレーニングのカウントなど、実用的なアプリケーションを構築できます。

次のSTEP 22では、GANsと画像生成を学びます!

📝

学習メモ

コンピュータビジョン(CV) - Step 21

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE