MediaPipe(メディアパイプ)をPythonで使う手順は、2026年9月時点で「モデルファイルを取得し、Tasks APIのクラスに渡して推論する」の一本になりました。ネット上の解説の多くが使っている mp.solutions.hands のような書き方は、pipで入る0.10.30以降のパッケージから外れています。最新の1.0.1で古いサンプルを動かすと、AttributeError: module 'mediapipe' has no attribute 'solutions' で止まります。
この記事では、インストールから手・姿勢・顔の検出、Webカメラ映像の処理、ランドマークの保存までをコード付きで説明します。コードはTasks APIの形が同じ0.10.21(Python 3.12・Intel Mac)で実行し、1.0.1については配布wheelに同梱されたソースでクラス名・引数・戻り値の名前を照合しています。旧コードの書き換え表と、OSやPythonの版によってpipが古い版を選んでしまう問題も扱います。MediaPipeの定義や商用利用の範囲を先に知りたい方は、MediaPipe(メディアパイプ)とは?Google製AIライブラリの機能・読み方・商用利用をわかりやすく解説を参照してください。
まとめ:MediaPipeの使い方の要点(1.0.1時点)
- 導入は
pip install mediapipeの1行です。最新版は1.0.1(2026年8月14日公開)です。 - 書き方はTasks APIです。
mp.solutionsは0.10.30(2025年12月16日)からpip版に含まれていません。 - 手順は「
.taskなどのモデルを取得→Optionsを作る→create_from_options→detect系メソッド」の4段です。 - 静止画はIMAGE、動画の順次処理はVIDEO、画面を止めたくないカメラ処理はLIVE_STREAMの各モードを使います。VIDEOとLIVE_STREAMではミリ秒のタイムスタンプを単調増加させます。
- Intel MacのPython 3.9〜3.12では、対応wheelのある最新版としてpipは0.10.21を選びます。Python 3.13以降では入りません。
- GPUの指定が効くのはUbuntuだけです。WindowsとmacOSはCPUで動かす前提で設計します。
MediaPipeの使い方の全体像:モデルファイルと3つの実行モード
MediaPipeは、Googleが公開しているオンデバイス推論のライブラリです。Pythonから使う入口は mp.tasks.vision 以下のタスククラスです。この記事で扱う手・姿勢・顔のタスクは、次の4段で動きます。
- 学習済みモデル(
.taskまたは.tflite)をダウンロードする BaseOptionsにモデルのパスを渡し、タスクごとのOptionsを作るcreate_from_optionsでタスクを生成する- 画像を
mp.Imageに包み、実行モードに合ったdetect系メソッドへ渡す
モデルはパッケージに同梱されていないので、使うタスクの分だけ取得します。この記事のコードが使う3つのモデルとサンプル画像の取得コマンドは、次の章のインストール手順にまとめました。主なタスクとモデルは次のとおりです。サイズは2026年9月27日にGoogleの配布元で確認した値です。
| 検出したいもの | クラス | モデルファイル | サイズ | 出力 |
|---|---|---|---|---|
| 手 | HandLandmarker | hand_landmarker.task | 7.8MB | 手ごとに21点・左右 |
| 全身の姿勢 | PoseLandmarker | pose_landmarker_lite.task | 5.8MB | 33点 |
| 顔の位置 | FaceDetector | blaze_face_short_range.tflite | 0.23MB | 矩形・キーポイント |
| 顔の形状・表情 | FaceLandmarker | face_landmarker.task | 3.8MB | 478点・表情係数52個(要設定) |
| 手のジェスチャー | GestureRecognizer | gesture_recognizer.task | 8.4MB | ジェスチャー名・21点 |
| 顔・手・姿勢を同時に | HolisticLandmarker | holistic_landmarker.task | 13.7MB | 478点・21点×2・33点 |
実行モードは3つあり、呼ぶメソッドが決まっています。モードとメソッドが食い違うと ValueError になります。
| RunningMode | 呼ぶメソッド | 向く入力 |
|---|---|---|
| IMAGE(既定) | detect(image) | 写真・静止画の一括処理 |
| VIDEO | detect_for_video(image, timestamp_ms) | 動画ファイル・記録用のカメラ処理 |
| LIVE_STREAM | detect_async(image, timestamp_ms) | 表示を止めたくないカメラ映像 |
GestureRecognizerだけはメソッド名がrecognize・recognize_for_video・recognize_asyncになります。VIDEOとLIVE_STREAMはフレーム間の追跡結果を使うので、前のフレームより大きいタイムスタンプが必要です。同じ値や小さい値を渡すと Input timestamp must be monotonically increasing. で止まります。
MediaPipeのインストール手順とOS・Python別に入るバージョン
venvへのpip導入とバージョン確認のコマンド
公式のPython向けセットアップガイドが求める条件は、Python 3.9以降とpip 20.3以上です。依存パッケージとしてOpenCV(opencv-contrib-python)、NumPy、Matplotlib、sounddeviceなども一緒に入るので、既存の環境を汚さないよう仮想環境に入れます。
python3 -m venv .venv
source .venv/bin/activate # Windows は .venv\Scripts\activate
python -m pip install --upgrade pip
python -m pip install mediapipe
python -c "import mediapipe as mp; print(mp.__version__)" # 1.0.1
# この記事のコードが使うモデル(macOS・Linuxのシェル。Windowsは各URLをブラウザで保存)
M=https://storage.googleapis.com/mediapipe-models
curl -LO $M/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task
curl -LO $M/pose_landmarker/pose_landmarker_lite/float16/latest/pose_landmarker_lite.task
curl -LO $M/face_detector/blaze_face_short_range/float16/latest/blaze_face_short_range.tflite
# 動作確認用のGoogleのサンプル画像(コードが読むファイル名で保存)
curl -L -o hands.jpg https://storage.googleapis.com/mediapipe-tasks/hand_landmarker/woman_hands.jpg
curl -L -o pose.jpg https://storage.googleapis.com/mediapipe-assets/pose.jpg
curl -L -o face.jpg https://storage.googleapis.com/mediapipe-assets/portrait.jpg
1.0.1が依存するOpenCVは opencv-contrib-python で、2026年9月時点では5.0系が入ります。同じ環境に opencv-python が先に入っていると、どちらも cv2 という同じ名前で読み込まれて衝突します。OpenCVの配布元も「4種類のパッケージから1つだけを選び、複数入れた場合はすべてアンインストールして1つを入れ直す」よう案内しています。condaとpipを混ぜている環境では、pipとcondaの違いと使い分け|Anaconda・Miniconda・商用ライセンスまで解説の考え方で入れる経路を1本にそろえてください。
OS・CPU・Python別にpipが選ぶMediaPipeの版
pip install mediapipe は、その環境に合うwheel(ビルド済みパッケージ)がある最新版を選びます。0.10.30からwheelの対象が絞られたため、環境によっては最新版が入りません。uvの依存解決で各環境を指定して確かめた結果が次の表です(2026年9月27日時点)。
| 環境 | Python | 入る版 | mp.solutions |
|---|---|---|---|
| Windows x64 | 3.13 | 1.0.1 | なし |
| macOS Apple Silicon | 3.13 | 1.0.1 | なし |
| Linux x86_64(glibc 2.28以降) | 3.13 | 1.0.1 | なし |
| Linux aarch64(64bitのRaspberry Pi OSなど) | 3.11 | 1.0.1 | なし |
| macOS Intel | 3.12 | 0.10.21 | あり |
| macOS Intel | 3.13 | 入らない | - |
Intel Mac向けのwheelは0.10.21(2025年2月)が最後で、0.10.21はPython 3.9〜3.12向けにしか作られていません。「Intel Macでは旧サンプルが動くのに、Windowsの同僚の環境では動かない」という食い違いは、この版の差で起きます。チームで使うなら requirements.txt で版を固定し、Apple SiliconかLinuxにそろえるほうが切り分けが楽です。Pythonの版を切り替えるならpyenvでPythonのバージョンを切り替える手順|shimの仕組みとビルド失敗の対処が使えます。
Linuxのaarch64版は0.10.18を最後にいったん途切れ、1.0.0で再び配布されました。Raspberry Piで1.0系を使うには64bit OSが必要です。AI処理用の拡張ボードとの組み合わせはラズベリーパイ(Raspberry Pi)でAIを動かす方法|AI HAT+・AI Kit・AI Cameraの選び方とセットアップにまとめています。
0.10.30以降のwheelは py3-none というタグで、Pythonのマイナー版ごとには作られていません。推論本体は同梱の libmediapipe.so などのネイティブライブラリで、Python側はそれを呼び出す薄い層です。そのためPython 3.13にもインストールはできますが、PyPIの分類子に書かれている対応版は3.9〜3.12です。3.13以降で不具合が出たら、まず3.12の環境で再現するか確かめてください。
GPU指定が効く環境とCPU前提で設計すべき環境
GPUを使うには BaseOptions(model_asset_path=..., delegate=mp.tasks.BaseOptions.Delegate.GPU) と指定します。ただし1.0.1に同梱されたソースの説明には「GPU support is currently limited to Ubuntu platforms」とあり、効くのはUbuntuだけです。Windows向けビルドについては、0.10.33のリリースノートにOpenGLによるGPU処理を自動で無効化する変更が記録されています。
「mediapipe cuda」で調べる方も多いのですが、MediaPipeのGPU処理はOpenGL ES(Linux・Androidでは3.1以上)を使う設計で、pip版にCUDAを指定する設定はありません。NVIDIAのGPUで推論を速くしたい要件なら、MediaPipeにこだわらずONNX RuntimeやTensorRTで動くモデルを選ぶほうが近道です。CPUのままでも、姿勢推定のliteモデルや手のランドマークは入力が256×256前後の小さなモデルなので、フレームを間引くなどして要件に合うか先に測ってください。
mp.solutionsが使えない原因とTasks APIへの移行表
「mediapipe 1.0.1 mp.solutions」で検索される方が多いので、原因を先に確定させます。PyPIのwheelを展開して mediapipe/python/solutions/ 以下のファイル数を数えると、0.10.21は22ファイル、0.10.30・0.10.35・1.0.0・1.0.1はいずれも0でした。1.0.1の mediapipe/__init__.py が読み込むのは mediapipe.tasks.python と Image・ImageFormat だけです。
GitHubの google-ai-edge/mediapipe リポジトリには、v1.0.0のタグでも mediapipe/python/solutions のディレクトリが残っています。ソースを見て「まだ使える」と判断するとここで誤ります。pipで入るパッケージには入っていません。GoogleはLegacy Solutionsのサポートを2023年3月1日に終えており、リリースノートでは削除が告知されないまま、0.10.30の配布物から外れた形です。
旧Solutionsから新タスクへの対応表
| 旧(mp.solutions) | 新(mp.tasks.vision) | モデル |
|---|---|---|
| hands.Hands | HandLandmarker | hand_landmarker.task |
| pose.Pose | PoseLandmarker | pose_landmarker_lite/full/heavy.task |
| face_detection.FaceDetection | FaceDetector | blaze_face_short_range.tflite |
| face_mesh.FaceMesh | FaceLandmarker | face_landmarker.task |
| holistic.Holistic | HolisticLandmarker | holistic_landmarker.task |
| selfie_segmentation | ImageSegmenter | selfie_segmenter.tflite |
| drawing_utils | vision.drawing_utils | - |
HolisticLandmarkerは0.10.33でPython版に戻ったタスクです。描画用の drawing_utils と drawing_styles は、1.0系では mp.tasks.vision の下にあります。0.10.21にはこの場所に無いので、両方の版で動かしたいコードでは、この記事の例のようにOpenCVで線を引くほうが安全です。
Tasks API移行時のモデル指定・画像入力・結果形式の変更
# 旧:0.10.21以前(1.0系では AttributeError)
hands = mp.solutions.hands.Hands(max_num_hands=2)
results = hands.process(rgb) # numpy配列をそのまま渡す
for hand in results.multi_hand_landmarks or []:
tip = hand.landmark[8]
# 新:Tasks API(0.10系後半〜1.0系)
options = mp.tasks.vision.HandLandmarkerOptions(
base_options=mp.tasks.BaseOptions(model_asset_path="hand_landmarker.task"),
num_hands=2,
)
landmarker = mp.tasks.vision.HandLandmarker.create_from_options(options)
result = landmarker.detect(mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb))
for hand in result.hand_landmarks: # 手ごとのランドマークのリスト
tip = hand[8]
変わるのは3点です。1つ目は、モデルのパスを自分で渡すこと。2つ目は、NumPy配列を mp.Image に包んで渡すこと。3つ目は結果の形で、results.multi_hand_landmarks[i].landmark[8] が result.hand_landmarks[i][8] になり、手ごとのランドマークがそのままリストで返ります。同じ画像で新旧のコードを実行すると、人差し指の先端は正規化座標で (0.724, 0.418) と (0.725, 0.420) になり、ほぼ同じ位置を返しました。
mediapipe==0.10.21に固定して旧コードを延命する判断
書き換える時間が無い既存システムは、pip install "mediapipe==0.10.21" で固定すれば mp.solutions のまま動きます。0.10.21のwheelがあるのは、Python 3.9〜3.12のWindows x64・macOS・Linux x86_64だけです。この記事で旧版の固定を勧めるのは、その条件に合い、1年半以上更新の無い版に留まっても困らない社内検証に限ります。新規開発で0.10.21を選ぶ理由はありません。Raspberry PiなどのLinux aarch64やWindows ARM64には入らず、新しいタスクや不具合修正も0.10.21には入らないためです。
手の検出(Hand Landmarker)の実装と21点ランドマークの読み方
静止画から手を検出して、人差し指の先端の位置をピクセルで出す最小構成です。with 文でタスクを作ると、抜けたときにネイティブ側の資源が解放されます。
import cv2
import mediapipe as mp
BaseOptions = mp.tasks.BaseOptions
vision = mp.tasks.vision
options = vision.HandLandmarkerOptions(
base_options=BaseOptions(model_asset_path="hand_landmarker.task"),
running_mode=vision.RunningMode.IMAGE,
num_hands=2,
)
with vision.HandLandmarker.create_from_options(options) as landmarker:
bgr = cv2.imread("hands.jpg")
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
result = landmarker.detect(image)
h, w = bgr.shape[:2]
for hand, handed in zip(result.hand_landmarks, result.handedness):
tip = hand[8] # 人差し指の先端
print(handed[0].category_name, round(handed[0].score, 2),
"index_tip(px)=", int(tip.x * w), int(tip.y * h))
hands.jpg として保存したGoogleのサンプル画像(元の名前は woman_hands.jpg・640×960)で実行すると、2本の手が検出されます。
Left 0.94 index_tip(px)= 41 743
Right 0.96 index_tip(px)= 463 403
result.hand_landmarks は手ごとに21点のリストで、各点の x・y は画像の幅と高さで割った0〜1の値です。ピクセルに戻すには幅と高さを掛けます。番号は0が手首、4が親指の先、8が人差し指の先、12が中指の先、20が小指の先です。z は手首を基準にした奥行きで、値が小さいほどカメラに近い点を表します。
実寸で距離を測りたいときは result.hand_world_landmarks を使います。こちらは手の中心を原点にしたメートル単位の3次元座標です。num_hands の既定値は1なので、両手を扱うなら2を指定します。3つのしきい値の既定値はいずれも0.5です。min_hand_detection_confidence は手の検出を採用するかの値で、誤検出が多ければ上げ、取りこぼしが多ければ下げます。min_hand_presence_confidence と min_tracking_confidence(前後のフレームの手の矩形のIoU)はVIDEOとLIVE_STREAMで効き、下回ると軽い追跡をやめて検出からやり直します。上げすぎると再検出が増えて処理が重くなります。
姿勢推定(Pose Landmarker)をWebカメラ・動画で動かす手順
VIDEOモードのフレームループと関節角度の計算
動画ファイルかWebカメラから1フレームずつ読み、全身の33点を検出して骨格の線と左ひざの角度を描くコードです。引数に動画ファイルのパスを渡すとその動画を、省略すると既定のWebカメラを処理します。
import math
import sys
import cv2
import mediapipe as mp
vision = mp.tasks.vision
LEFT_HIP, LEFT_KNEE, LEFT_ANKLE = 23, 25, 27 # 33点のインデックス
CONNECTIONS = vision.PoseLandmarksConnections.POSE_LANDMARKS
def angle(a, b, c, w, h):
"""b を頂点とする a-b-c の角度(度)。縦横比の歪みを避けるためピクセルで計算"""
ab = ((a.x - b.x) * w, (a.y - b.y) * h)
cb = ((c.x - b.x) * w, (c.y - b.y) * h)
cos = (ab[0] * cb[0] + ab[1] * cb[1]) / (math.hypot(*ab) * math.hypot(*cb) + 1e-9)
return math.degrees(math.acos(max(-1.0, min(1.0, cos))))
options = vision.PoseLandmarkerOptions(
base_options=mp.tasks.BaseOptions(model_asset_path="pose_landmarker_lite.task"),
running_mode=vision.RunningMode.VIDEO,
num_poses=1,
)
source = sys.argv[1] if len(sys.argv) > 1 else 0 # 0 = 既定のWebカメラ
cap = cv2.VideoCapture(source)
fps = cap.get(cv2.CAP_PROP_FPS) or 30
frame_index = 0
with vision.PoseLandmarker.create_from_options(options) as landmarker:
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
timestamp_ms = int(frame_index * 1000 / fps) # 単調増加させる
frame_index += 1
result = landmarker.detect_for_video(image, timestamp_ms)
if result.pose_landmarks:
lm = result.pose_landmarks[0]
h, w = frame.shape[:2]
for c in CONNECTIONS:
p, q = lm[c.start], lm[c.end]
cv2.line(frame, (int(p.x * w), int(p.y * h)),
(int(q.x * w), int(q.y * h)), (0, 255, 0), 2)
knee = angle(lm[LEFT_HIP], lm[LEFT_KNEE], lm[LEFT_ANKLE], w, h)
cv2.putText(frame, f"L knee {knee:.0f} deg", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
if frame_index == 1:
print("left knee angle:", round(knee, 1))
if source == 0:
cv2.imshow("pose", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
print("frames:", frame_index)
タイムスタンプはフレーム番号とFPSから計算しています。time.time() のような実時間を使うと、処理が速い環境で同じミリ秒が2回続き、先ほどの単調増加のエラーで止まることがあります。Googleのサンプル画像 pose.jpg を30フレームの動画にして実行したところ、左ひざの角度は174.8度で、脚がほぼ伸びた姿勢として検出されました。
角度はピクセル座標に直してから計算しています。正規化座標のまま計算すると、横長の画像では横方向が縮んで扱われ、角度がずれるためです。ただし求めているのは画像上の2次元投影角で、奥行きを含む実際の関節角度ではありません。カメラに対して斜めに立つと値が変わるので、比べる動画どうしは撮影方向をそろえます。ひざなら23・25・27番(左の腰・ひざ・足首)、ひじなら11・13・15番(左の肩・ひじ・手首)の組み合わせで同じ関数が使えます。
lite・full・heavyのモデルの選び方
姿勢推定のモデルは3種類で、入力サイズはどれも検出器224×224、ランドマーク推定256×256と同じです。違うのはモデルの規模で、ファイルサイズはliteが5.8MB、fullが9.4MB、heavyが30.7MBです。CPUでカメラ映像を処理するならliteから始め、手先や足先の位置がぶれて要件を満たさないときにfullへ上げます。heavyはリアルタイム性を求めない録画の解析向きです。
画面を止めずに推論するLIVE_STREAMモード
VIDEOモードは推論が終わるまで次のフレームを読めません。カメラ映像の表示を途切れさせたくないなら、推論を別スレッドで回すLIVE_STREAMモードを使います。結果はコールバック関数で受け取ります。
import time
import cv2
import mediapipe as mp
vision = mp.tasks.vision
latest = {}
last_ts = -1
def on_result(result, output_image, timestamp_ms):
# 推論スレッドから呼ばれる。重い処理は置かず結果だけ受け渡す
latest["hands"] = result.hand_landmarks
options = vision.HandLandmarkerOptions(
base_options=mp.tasks.BaseOptions(model_asset_path="hand_landmarker.task"),
running_mode=vision.RunningMode.LIVE_STREAM,
num_hands=2,
result_callback=on_result,
)
cap = cv2.VideoCapture(0)
with vision.HandLandmarker.create_from_options(options) as landmarker:
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
# 同じミリ秒が続いても必ず前回より大きい値にする
timestamp_ms = max(time.monotonic_ns() // 1_000_000, last_ts + 1)
last_ts = timestamp_ms
landmarker.detect_async(image, timestamp_ms)
cv2.putText(frame, f'{len(latest.get("hands", []))} hands', (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
cv2.imshow("hands", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
コールバックは推論側のスレッドから呼ばれるので、中で描画や保存のような重い処理をしないのが原則です。結果だけを変数に入れ、描画はメインのループで行います。推論が追いつかない間のフレームは捨てられるので、表示は滑らかでも、すべてのフレームに結果が付くわけではありません。全フレームの記録が必要な用途はVIDEOモードを選びます。
顔検出(Face Detector)と顔ランドマークの使い分け
顔の位置だけ分かればよいならFaceDetector、目や口の形や表情まで扱うならFaceLandmarkerを使います。表情係数は既定では返らず、FaceLandmarkerOptions で output_face_blendshapes=True を指定したときだけ出力されます。FaceDetectorのモデルは0.23MBと小さく、478点を出すFaceLandmarkerより軽く動きます。
import cv2
import mediapipe as mp
vision = mp.tasks.vision
options = vision.FaceDetectorOptions(
base_options=mp.tasks.BaseOptions(model_asset_path="blaze_face_short_range.tflite"),
min_detection_confidence=0.5,
)
with vision.FaceDetector.create_from_options(options) as detector:
bgr = cv2.imread("face.jpg")
image = mp.Image(image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB))
result = detector.detect(image)
for d in result.detections:
box = d.bounding_box # こちらは正規化されていないピクセル値
cv2.rectangle(bgr, (box.origin_x, box.origin_y),
(box.origin_x + box.width, box.origin_y + box.height), (0, 255, 0), 2)
print("score", round(d.categories[0].score, 2),
"box", box.origin_x, box.origin_y, box.width, box.height)
cv2.imwrite("face_out.jpg", bgr)
face.jpg として保存したGoogleのサンプル画像(元の名前は portrait.jpg)では、スコア0.92で顔が1つ検出され、矩形は左上 (283, 115)、幅と高さはともに234ピクセルでした。つまずきやすいのは座標の単位です。ランドマークが0〜1の正規化座標なのに対し、bounding_box はピクセルの整数で返ります。同じ感覚で幅や高さを掛けると、画面の外に枠が描かれます。
既定の blaze_face_short_range は近い距離の顔向けのモデルです。背面カメラで撮った集合写真のように顔が小さく写る画像には、full-range版(blaze_face_full_range.tflite・1.1MB)が用意されています。478点の顔ランドマークをブラウザで扱う方法はTensorFlow.jsでフェイストラッキングを実装する手順|478点ランドマークと旧APIからの移行で解説しています。
ランドマークをCSVに保存して分析へ渡す方法
フォームの比較や動作の分類に使うなら、推論結果を1フレーム1行でCSVに残しておくと後から何度でも分析できます。姿勢の33点について x・y・z・visibility を横に並べると、フレーム番号とタイムスタンプを合わせて134列になります。
import csv
import cv2
import mediapipe as mp
vision = mp.tasks.vision
options = vision.PoseLandmarkerOptions(
base_options=mp.tasks.BaseOptions(model_asset_path="pose_landmarker_lite.task"),
running_mode=vision.RunningMode.VIDEO,
)
cap = cv2.VideoCapture("pose.mp4")
fps = cap.get(cv2.CAP_PROP_FPS) or 30
header = ["frame", "timestamp_ms"] + [f"{axis}{i}" for i in range(33) for axis in ("x", "y", "z", "v")]
with open("pose.csv", "w", newline="") as f, \
vision.PoseLandmarker.create_from_options(options) as landmarker:
writer = csv.writer(f)
writer.writerow(header)
frame_index = 0
while True:
ok, frame = cap.read()
if not ok:
break
ts = int(frame_index * 1000 / fps)
image = mp.Image(image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
result = landmarker.detect_for_video(image, ts)
if result.pose_landmarks: # 検出できなかったフレームは書かない
row = [frame_index, ts]
for lm in result.pose_landmarks[0]:
row += [round(lm.x, 5), round(lm.y, 5), round(lm.z, 5), round(lm.visibility or 0.0, 3)]
writer.writerow(row)
frame_index += 1
cap.release()
人が映っていないフレームは書き込まず、フレーム番号を残して欠損が分かるようにしています。visibility はその点が画面内に見えている確からしさで、0.5未満の点を分析から外すだけでも、画面の端で手足が切れたときの外れ値を減らせます。
撮影位置が違う動画どうしを比べるなら、正規化座標ではなく pose_world_landmarks(左右の腰の中点を原点にしたメートル単位の座標)を保存します。動作の速さが違う2本の系列を並べて比べるには、PythonでのDTW(動的時間伸縮法)実装ガイド:numpyフルスクラッチとライブラリの使い分けで説明している時間軸の伸縮合わせが使えます。
MediaPipeでつまずきやすいエラーと原因の切り分け
| 症状・エラー | 原因 | 対処 |
|---|---|---|
has no attribute 'solutions' |
0.10.30以降を使用 | Tasks APIへ書き換え |
timestamp must be monotonically increasing |
同じか戻ったタイムスタンプ | フレーム番号から計算 |
Task is not initialized with the image mode |
モードとメソッドの不一致 | RunningModeを合わせる |
No matching distribution found |
Intel MacのPython 3.13など | Python 3.12か別環境 |
| 検出はされるが精度が低い | BGRのまま渡している | cvtColorでRGBへ変換 |
| cv2の関数が見つからない | OpenCVパッケージの併存 | 全削除して1つ入れ直す |
精度が低いときに最初に疑うのは色の順番です。OpenCVが読む画像はBGR順ですが、mp.ImageFormat.SRGB はRGB順を前提にしています。変換を忘れても例外は出ないため、気付かないまま「精度が悪いライブラリ」と判断されがちです。
カメラが開けないときは、MediaPipeより先に cv2.VideoCapture(0).read() 単体で映像が取れるかを確かめます。macOSではターミナルやエディタにカメラの利用許可が必要で、許可が無いとフレームが空のまま返ります。OpenCV側の基本操作はOpenCVをPythonで使う画像処理入門|インストールから顔検出まで実コードで解説で確認できます。
MediaPipe(メディアパイプ)の使い方に関するよくある質問
MediaPipeは商用利用できますか?
できます。ライブラリ本体はApache License 2.0で、商用製品にも組み込めます。再配布するときは、ライセンス文の同梱、著作権表示とNOTICEファイルの内容の保持、改変したファイルへの変更表示という第4条の条件を守ります。配布されている学習済みモデルにはモデルごとのモデルカードがあるので、出荷前に使うモデルの記載も確認してください。詳しい範囲はMediaPipeとは?の解説記事にまとめています。
MediaPipeに必要なPythonのバージョンは?
公式ガイドの条件はPython 3.9以降で、PyPIの分類子には3.9〜3.12が書かれています。1.0.1のwheelはPythonのマイナー版に依存しない形式なので3.13にも入りますが、問題が出たら3.12で切り分けるのが確実です。Intel Macだけは例外で、Python 3.9〜3.12では0.10.21が入り、3.13以降に対応する公式wheelはありません。
MediaPipeでリアルタイムの骨格検出はできますか?
できます。PoseLandmarkerをVIDEOモードかLIVE_STREAMモードで作り、Webカメラのフレームを順に渡します。CPUで動かすならliteモデルから始め、フレームの解像度を下げるか推論するフレームを間引いて速度を合わせます。
MediaPipeはCUDAでGPU推論できますか?
pip版にはCUDAを指定する設定がありません。BaseOptions の delegate でGPUを選べますが、効くのはUbuntuだけで、仕組みもOpenGL ESを使うものです。WindowsとmacOSではCPUで動きます。
Solutions APIとTasks APIの違いは?旧APIは今も使えますか?
Solutions API(mp.solutions)は2023年3月1日にサポートが終わった旧来の書き方で、モデルが同梱され、NumPy配列をそのまま渡せました。Tasks APIはモデルファイルを明示して mp.Image で渡す現在の書き方です。旧APIは0.10.30以降のpip版に含まれていないため、使い続けるにはmp.solutionsを含む0.10.21以前の版へ固定することになります。