📋 このステップで学ぶこと
- 姿勢推定(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と画像生成を学びます!