mei_13のPython講座 ロゴ

【解説】MediaPipeとpandasを組み合わせた手の骨格データ解析入門




🐍 初心者歓迎!月額4,000円で質問し放題のPython講座
(1時間4,000円の伴走型ビデオチャット指導も受付中!)
🚀 講座の詳細を見る 📩 質問・お問い合わせ

MediaPipeとpandasを組み合わせた手の骨格データ解析入門


Hirokiのアイコン
【Hiroki】 Yukiさん、こんにちは!最近、画像認識やAIを使ったモーションキャプチャに興味があって、Googleの「MediaPipe」というライブラリを見つけたんです。Webカメラを使って手の動きをリアルタイムに検出できてすごく面白いなと思ったのですが、検出したたくさんの座標データをうまく整理して保存したり、後から分析したりするにはどうすればいいでしょうか?


Yukiのアイコン
【Yuki】 Hirokiくん、こんにちは。MediaPipeに興味を持つなんて、とてもセンスが良いですね...。MediaPipeは、高度な機械学習モデルを簡単に扱える素晴らしいライブラリだと思います。

ただ、Hirokiくんの言う通り、リアルタイムで検出されるデータは膨大で、そのままでは扱いにくいかもしれません。そこで、データ分析のデファクトスタンダードである「pandas」というライブラリを組み合わせるのが、とても良い解決策になると思います...。

今回は、MediaPipeで検出した手の骨格(ランドマーク)データを、pandasを使って綺麗な表形式(データフレーム)に整理し、CSVファイルとして保存する一連の流れを一緒に学んでいきましょう。

必要なライブラリのインストール


Hirokiのアイコン
【Hiroki】 MediaPipeとpandasを組み合わせるんですね!データを表形式にできたら、後からグラフにしたり、別のAIの学習データに使ったりもできそうでワクワクします。まずは何から始めればいいですか?


Yukiのアイコン
【Yuki】 そうですね、データの整理ができると、活用の幅がぐっと広がると思います...。まずは開発環境に必要なライブラリをインストールしましょう。

今回は以下の3つのライブラリを使用します。 1. mediapipe: 手の骨格を検出するため 2. pandas: データを表形式に整理・保存するため 3. opencv-python (OpenCV): Webカメラの映像を取得・描画するため

ターミナル、またはコマンドプロンプトで以下のコマンドを実行してください。

pip install mediapipe pandas opencv-python


Hirokiのアイコン
【Hiroki】 インストールできました!これで準備完了ですね。


Yukiのアイコン
【Yuki】 はい、準備はバッチリだと思います...。それでは、まずはMediaPipeがどのようなデータを返してくれるのか、その仕組みを少しだけお話ししますね。

MediaPipe Handsの仕組みとデータ構造


Yukiのアイコン
【Yuki】 MediaPipeで手を検出する機能は「MediaPipe Hands」と呼ばれています。これを使うと、片手あたり21個の関節点(ランドマーク)の、3次元座標(x, y, z)を取得することができます。

各ランドマークには、0から20までのIDが割り振られています。例えば、手首は「0」、親指の先端は「4」、人差し指の先端は「8」といった具合です。

それぞれの座標は以下のような意味を持っています。 - x: 画像の横方向の位置(0.0 〜 1.0 に正規化されています) - y: 画像の縦方向の位置(0.0 〜 1.0 に正規化されています) - z: カメラからの奥行き(手首を基準とした相対的な距離を表します)

これらの詳細な仕様については、MediaPipe Solutionsの公式ドキュメントで確認することができます。


Hirokiのアイコン
【Hiroki】 なるほど、手首や指先に番号が決まっていて、それぞれのx、y、zの場所が小数点で取得できるんですね。

でも、片手だけで21個の点があって、それぞれに3つの座標があるということは、1つのフレーム(画像1枚)あたり「21 × 3 = 63個」もの数字が出てくるということですか?


Yukiのアイコン
【Yuki】 その通りです、よく気づきましたね...。毎秒30フレームでカメラ映像を処理すると、1秒間に1890個もの数値データが生成されることになります。これを普通のテキストファイルに書き出すだけでは、後から分析するのはとても大変になってしまいますよね。

そこで、pandasの「DataFrame」という機能が役に立ちます。

検出した座標データをpandasのDataFrameに変換する


Hirokiのアイコン
【Hiroki】 いよいよpandasの出番ですね!具体的に、どうやってMediaPipeのデータをpandasに渡すのですか?


Yukiのアイコン
【Yuki】 はい、まずは簡単なプログラムを書いてみましょう。Webカメラから1フレームだけ画像を読み込み、そこから手の骨格を検出して、pandasの表形式にするコードを作成してみました。

コードのインデントはスペース4つで統一しています。じっくり見てみてくださいね...。

import cv2
import mediapipe as mp
import pandas as pd

# MediaPipeの設定
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

# カメラの起動(0番目のカメラ)
cap = cv2.VideoCapture(0)

# データを蓄積するためのリスト
data_list = []

print("カメラが起動しました。手をかざして、'q'キーを押してデータを1件取得してください。")

while cap.isOpened():
    success, image = cap.read()
    if not success:
        print("カメラからの映像を取得できませんでした。")
        break

    # MediaPipeでの処理のためにBGRからRGBに変換
    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    results = hands.process(image_rgb)

    # 画面に映像を表示
    cv2.imshow('MediaPipe Hands', image)

    # キー入力を待つ
    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        # 手が検出されている場合のみ処理
        if results.multi_hand_landmarks:
            for hand_landmarks in results.multi_hand_landmarks:
                # 21個のランドマークの座標を取り出して平坦なリストにする
                row = []
                for lm in hand_landmarks.landmark:
                    row.extend([lm.x, lm.y, lm.z])
                data_list.append(row)
            print("データを取得しました。処理を終了します。")
            break
        else:
            print("手が検出されませんでした。もう一度'q'を押してください。")

cap.release()
cv2.destroyAllWindows()

# 列名(カラム名)の作成
columns = []
for i in range(21):
    columns.extend([f'lm_{i}_x', f'lm_{i}_y', f'lm_{i}_z'])

# pandasのDataFrameに変換
df = pd.DataFrame(data_list, columns=columns)
print("\n--- 作成されたDataFrame ---")
print(df)


Hirokiのアイコン
【Hiroki】 おおっ、カメラが起動して、手を写した状態で「q」キーを押したら、画面にずらっと表のようなものが表示されました!

でも、このプログラムの中で、MediaPipeのデータをどのようにpandasに移しているのか、もう少し詳しく教えてほしいです。


Yukiのアイコン
【Yuki】 喜んでもらえてよかったです...。では、データの流れを細かく分解して説明しますね。

まず、MediaPipeが手を検出すると、results.multi_hand_landmarksの中にデータが入ります。 ここから、21個のランドマークの座標を取り出すために、以下の部分でループ処理を行っています。

row = []
for lm in hand_landmarks.landmark:
    row.extend([lm.x, lm.y, lm.z])

lm.x, lm.y, lm.z を1つのリストに追加しています。extendメソッドを使っているので、21個の点それぞれについてx, y, zが順番に並んだ、合計63個の要素を持つ「平坦な1次元のリスト」が作成されます。

そして、そのリストを外側の data_list に追加しているのですね。


Hirokiのアイコン
【Hiroki】 なるほど、1つの手につき、63個の数値が並んだリストが作られるわけですね。

カラム名(列名)を作っている部分の columns.extend([f'lm_{i}_x', f'lm_{i}_y', f'lm_{i}_z']) というのも、同じように「lm_0_x, lm_0_y, lm_0_z ...」と63個の名前を作っているということですか?


Yukiのアイコン
【Yuki】 その通りです。素晴らしい洞察力ですね...。 pd.DataFrame(data_list, columns=columns) とすることで、63個の数値データに対して、それぞれに分かりやすい「列名」を割り当てた表(DataFrame)を作成しているのです。

pandasの詳細な使い方については、pandas documentationも併せて参照すると、より理解が深まると思います...。

連続したフレームのデータをCSVファイルとして保存する


Hirokiのアイコン
【Hiroki】 仕組みがよく分かりました! 次は、1回だけでなく、カメラが動いている間、ずっとデータを取得し続けて、最後にCSVファイルとして保存したいです。どうすればいいでしょうか?


Yukiのアイコン
【Yuki】 動きを記録するとなると、複数フレームのデータを連続で保存する必要がありますね。

先ほどのコードを少し改良して、カメラが起動している間、1フレームごとにデータをどんどん data_list に追加していき、キーボードの「esc」キーを押して終了したときに、すべてのデータをCSVファイルに一括保存するプログラムを作ってみましょう。

こちらのコードを実行してみてください...。

import cv2
import mediapipe as mp
import pandas as pd

# MediaPipeの描画用ユーティリティと検出モデルの初期化
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands

hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.5
)

cap = cv2.VideoCapture(0)
data_list = []
frame_count = 0

print("録画を開始します。終了するには、画像ウィンドウを選択した状態で 'esc' キーを押してください。")

while cap.isOpened():
    success, image = cap.read()
    if not success:
        print("カメラ映像の読み込みに失敗しました。")
        break

    # 左右反転させて鏡のようにする
    image = cv2.flip(image, 1)

    # 処理のためにRGBに変換
    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    results = hands.process(image_rgb)

    # 画面に骨格を描画し、データを取得
    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            # 画面に骨格線を描画
            mp_drawing.draw_landmarks(
                image, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # 座標データの抽出
            row = [frame_count] # どのフレームのデータか分かるようにフレーム番号を先頭に追加
            for lm in hand_landmarks.landmark:
                row.extend([lm.x, lm.y, lm.z])
            data_list.append(row)

    # 画面の表示
    cv2.imshow('MediaPipe Record', image)
    frame_count += 1

    # 'esc' キー(キーコード27)でループを抜ける
    if cv2.waitKey(5) & 0xFF == 27:
        break

cap.release()
cv2.destroyAllWindows()

# データの保存処理
if len(data_list) > 0:
    # 列名の作成(先頭に'frame'列を追加)
    columns = ['frame']
    for i in range(21):
        columns.extend([f'lm_{i}_x', f'lm_{i}_y', f'lm_{i}_z'])

    # DataFrameの作成
    df = pd.DataFrame(data_list, columns=columns)

    # CSVファイルへの書き出し(インデックスは不要なのでFalseに)
    csv_filename = 'hand_pose_data.csv'
    df.to_csv(csv_filename, index=False)
    print(f"データを正常に保存しました。保存先: {csv_filename}")
    print(f"総フレーム数: {len(df)}")
else:
    print("データが取得されなかったため、保存しませんでした。")


Hirokiのアイコン
【Hiroki】 すごい!画面に自分の手の骨格が線でリアルタイムに描画されました! 動かした後に「esc」キーを押したら、同じフォルダに hand_pose_data.csv というファイルが作られました。

中身を見てみたら、コンマ区切りで数値がびっしり書き込まれています。これを使って、例えば「手を振る」動作とか「グー、チョキ、パー」の形を判別したりできるんですね。


Yukiのアイコン
【Yuki】 無事に保存できたようで良かったです...。

そうですね、このように時系列で保存された骨格データがあれば、「グーのときの関節の角度」や「パーのときの指先同士の距離」などを計算して、ジェスチャーを認識する独自のプログラムを作ることができると思います...。

今回のコードでは、後から分析しやすいように、データの先頭に frame(フレーム番号)の列を追加してみました。 これによって、「時間が経つにつれて座標がどう変化したか」という時系列の解析が簡単に行えるようになっています。

pandasを使った簡単なデータ分析の例


Hirokiのアイコン
【Hiroki】 せっかくデータを保存できたので、少しだけこのデータを使って遊んでみたいです。pandasを使って、何か簡単な分析はできますか?


Yukiのアイコン
【Yuki】 もちろんですよ。例えば、保存したCSVファイルを読み込んで、「人差し指の先端(ID: 8)」が画面の中でどれくらい激しく動いたかを計算してみましょう。

pandasを使えば、数行のコードでこのような集計や計算を行うことができます。

以下のコードを別のPythonファイルとして作成し、実行してみてください...。

import pandas as pd

# CSVファイルの読み込み
df = pd.read_csv('hand_pose_data.csv')

# 人差し指の先端(lm_8)のx座標とy座標の最大値、最小値、平均値を取得
index_finger_x = df['lm_8_x']
index_finger_y = df['lm_8_y']

print("--- 人差し指先端(ID: 8)の動きの範囲 ---")
print(f"X座標の範囲: {index_finger_x.min():.3f} から {index_finger_x.max():.3f} (平均: {index_finger_x.mean():.3f})")
print(f"Y座標の範囲: {index_finger_y.min():.3f} から {index_finger_y.max():.3f} (平均: {index_finger_y.mean():.3f})")

# フレームごとの移動距離(速度のようなもの)を計算してみる
# diff()メソッドで前の行との差分を計算できます
dx = index_finger_x.diff()
dy = index_finger_y.diff()

# 2次元の移動距離を計算 (平方根(dx^2 + dy^2))
distance = (dx**2 + dy**2)**0.5

print("\n--- 人差し指の移動速度(フレームあたり) ---")
print(f"平均移動距離: {distance.mean():.5f}")
print(f"最大移動距離: {distance.max():.5f}")


Hirokiのアイコン
【Hiroki】 わあ、すごい!人差し指がどれだけ動いたかが、数値としてしっかりと見えてきました。 diff() を使うだけで、1フレーム前の座標との引き算が簡単にできるんですね。forループをたくさん回さなくていいのが、pandasのすごいところだなと感じました。


Yukiのアイコン
【Yuki】 そうですね...。pandasは大量のデータ処理を内部で高速に行えるように設計されているので、今回のような何百フレームものデータであっても、一瞬で計算を終わらせることができます。

もし、さらに応用したい場合は、グラフ描画ライブラリである matplotlib などと組み合わせて、人差し指の軌跡を2次元のグラフに描いてみるのも面白いかもしれません...。

まとめと次のステップ


Hirokiのアイコン
【Hiroki】 MediaPipeで画像からデータを検出して、pandasでそれを整理して保存し、さらに分析する。この一連の流れがすごくスッキリ理解できました! これから、自分でジェスチャー判定のAIを作ってみたいという目標ができました。


Yukiのアイコン
【Yuki】 Hirokiくんなら、きっと素晴らしいプログラムを作ることができると思います...。

今回の組み合わせは、機械学習やデータサイエンスの分野でも非常によく使われる「基本の形」です。 MediaPipeは手だけでなく、全身のポーズ推定や顔の表情(フェイスメッシュ)の検出なども可能です。これらもすべて同じようにx, y, zの座標データとして出力されるため、今回のpandasとの連携方法を応用すれば、全身の動きの記録なども簡単に行うことができますよ。

ぜひ色々なアイデアに挑戦してみてくださいね。もし分からないことがあれば、いつでも聞いてください...。



ライブラリ特集一覧に戻る

この記事では基礎を解説しましたが、実務においては「もっと複雑なデータを扱いたい」「独自のシステムに組み込みたい」といった、個別の課題に直面することも多いはずです。

「自分で書く時間は最小限に抑え、プロの品質でツールを完成させたい」という方は、ぜひ一度ご相談ください。

「教わる」だけでなく「形にする」パートナーとして、フリーランスエンジニアのmei_13が最短ルートでの解決をサポートします。

➡ ココナラで制作・相談を依頼する(見積もり無料)


初心者から始められるPythonレッスン

プログラミング未経験者・初心者歓迎!
月額4,000円で質問し放題!!
● 完全オンライン
● 翌日までには必ず返信
● 挫折しない独自の学習メソッド
● 圧倒的高評価!!
テキストベースで時間を選ばない
4,000円/60分伴走型ビデオチャット指導も可能
● 高品質なサンプルコード
詳細はこちら

📩 質問・お問い合わせはこちら



AIアシスタント Yuki