✅ はじめに|“見える動き”から“測れる動き”へ
Googleの骨格推定ライブラリ「MediaPipe」はとても強力なツールです。mediapipeには、特別なGPUなどが必要なくノートパソコンなどでも軽くて動作しやすく、かつ出力が早いという特徴があります。
今回の記事では、前回(http://ktr-project.net/?p=25)構築したPython環境を使って以下を実現します
✅ mediapipeを使って動画から人物の骨格を検出
✅ 骨格を描画した新しい動画を保存
✅ 各関節の ピクセル座標(x, y) をCSVで出力
✅ MediaPipeの座標とは?
MediaPipeのPoseモデルが出力する各関節の座標は、そのままの場合には基本的に0.0〜1.0の**相対座標(正規化)**です。そのためこれらの座標から距離などの空間座標を出力するためにはスケーリングなどの工夫が必要なります。したがってmediapipeのラフに動作分析を行う上では関節角度などの要素に限局されるという点には留意が必要です。
通常の光学式のモーションキャプチャでは直接メートル座標系で三次元座標が出力されるので、ここは大きな違いといえます。
軸 | 内容 | 範囲 | 基準 |
---|---|---|---|
x | 左右方向 | 0.0〜1.0 | 画像の横幅に対して |
y | 上下方向 | 0.0〜1.0 | 画像の縦の高さに対して |
これらをそのまま扱っても構いませんが、描画や分析にはピクセル値に変換した方が圧倒的に便利です。
本当はZ座標も出力されていますが、あくまでも最初の場合には、撮影した画角の平面に限局したほうがわかりやすいと思ったので外しています(奥行は私自身もあまり使いこなせていない(´;ω;`))。
✅ なぜピクセル座標にするの?
相対座標からピクセル座標に変換することで以下の利点があります。
理由 | 内容 |
---|---|
画像上に正確に描画できる | 投入した画像と同じピクセル座標系のため |
数値的に扱いやすい | 角度計算などであれば簡単にできる |
他の画像処理と連携しやすい | 物体検出などとも統一可能 |
特に一つ目の部分が重要で、相対座標系でそのままスティックピクチャを書くと、グラフ表示の設定次第では縦や横にひずんでしまうことが多くあります(私もよくミスりました)。
✅ ディレクトリ構成(例)
前回の記事と同様にこのような感じでフォルダをつくってみてください。フォルダの中に、解析したい動画(.mp4ファイル)と後述のコードがコピペされたファイル(.py)ファイルがあれば大丈夫です。
他の二つのファイルは、解析コードを回した後に自動で出力されます。
motion_pixel_project/
├─ sample_video.mp4 ← 解析する動画
├─ pose_overlay_output.mp4 ← 骨格描画済み動画
(コードを回した後に出力される)
├─ pose_output_pixel.csv ← ピクセル座標を記録したCSV
(コードを回した後に出力される)
└─ extract_pose_pixel.py ← 実行コード
✅ 実行コード全文(ピクセル座標出力)
以下を extract_pose_pixel.py
として保存し、Spyderなどで実行してください。
import cv2
import mediapipe as mp
import csv
# === 設定 ===
#この部分は各自の解析したファイルの名前に合わせて変えてください
#極論、各自で編集するのはこの名前の部分だけです。
input_path = "sample_video.mp4"
video_output_path = "pose_overlay_output.mp4"
csv_output_path = "pose_output_pixel.csv"
# === MediaPipe 初期化 ===
mp_pose = mp.solutions.pose
mp_drawing = mp.solutions.drawing_utils
pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.5)
# === 動画読み込み・出力設定 ===
cap = cv2.VideoCapture(input_path)
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(video_output_path, fourcc, fps, (width, height))
# === CSV 出力:ピクセル座標(x_px, y_px)形式 ===
with open(csv_output_path, mode="w", newline="") as f:
writer = csv.writer(f)
# ヘッダー:frame + x_px, y_px × 33点
header = ["frame"]
for i in range(33):
header += [f"x_{i}_px", f"y_{i}_px"]
writer.writerow(header)
frame_idx = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = pose.process(image_rgb)
if results.pose_landmarks:
# 骨格描画(MediaPipeのスタイルそのまま)
mp_drawing.draw_landmarks(
frame,
results.pose_landmarks,
mp_pose.POSE_CONNECTIONS
)
# ピクセル座標で保存
row = [frame_idx]
for lm in results.pose_landmarks.landmark:
x_px = lm.x * width
y_px = lm.y * height
row += [x_px, y_px]
writer.writerow(row)
print(f" フレーム {frame_idx}:保存完了(ピクセル座標)")
else:
print(f" フレーム {frame_idx}:骨格未検出")
out.write(frame)
frame_idx += 1
cap.release()
out.release()
print(f"\n 完了!\n・動画:{video_output_path}\n・CSV:{csv_output_path}(ピクセル座標)")
✅ 出力されるCSVの中身
うまくいけば、こんな感じで各関節位置の時系列座標が出力されます。行は動画のフレーム毎になっています。例えば30フレーム/秒の場合には、30行で1秒といった感じになります。
この辺のサンプリング周波数(≒1秒当たりのフレーム数)は計測したカメラの性能によって変わります。普通のハンディカムとかだと30フレーム/秒が多いですが、要確認です。
frame,x_0_px,y_0_px,x_1_px,y_1_px,...,x_32_px,y_32_px
0,332.1,180.5,340.2,183.1,...(33点×2=66列)
0~32まで数字が割り振られていますが、これは例えば…
x_0_px, y_0_px
:ランドマーク0(鼻)の位置(ピクセル)x_11_px, y_11_px
:左肩など…
こんな感じの解釈で大丈夫です。
今回はmediapipeの中のposeというものを使っています。各座標の関節位置については、mediapipeの公式ドキュメントに記載されています(参考URL:https://github.com/google-ai-edge/mediapipe/blob/master/docs/solutions/pose.md)。
また実際に出力される動画はこんな感じです。今回の例では上肢の粗大運動を想定しています。
※複数人が映っている場合には骨格推定が安定しない場合があります。動画を撮影する時点で、背景に人物が映らないように環境整備しておくと良いとおもいます。
✅ 次回予告|このCSVを使って動作をグラフ化・分析!
結局のところ、まだ「数字の時系列のピクセルデータが出力されたからって何になるねん」というのは解決していません。
数字の羅列だけでは意味をなさないのは自明で、これらのデータを数値客観化できる変数に変換する必要があります。
次回以降では、実際にmediapipeで得られたピクセル座標データから関節角度データに変換する処理の部分を説明していきたいと思います。
✅ ご質問・ご感想はXまで!
また、今後の更新を見逃したくない方はぜひXをフォローしてもらえれば幸いです(https://x.com/notifications)。また解析ネタの要望なども随時募集しています(私の知っている範囲には限られてしまいますが。。。)。
コメント