MediaPipe(メディアパイプ)をPythonで使う手順は、2026年10月時点で「モデルファイルを取得し、Tasks APIのクラスに渡して推論する」の一本です。ネット上の解説の多くが使っている mp.solutions.hands のような書き方は、pipで入る0.10.30以降のパッケージから外れています。最新の1.1.0で古いサンプルを動かすと、AttributeError: module 'mediapipe' has no attribute 'solutions' で止まります。
この記事では、インストールから手・姿勢・顔の検出、Webカメラ映像の処理、ランドマークの保存、実行ファイルへのまとめ方までをコード付きで説明します。静止画・動画ファイルの推論と配布用コードは、mediapipe 1.1.0(Python 3.12・Linux x86_64のDockerコンテナ)で実行しました。LIVE_STREAMは動画ファイル入力に変更して検証しており、実機カメラの取得と画面表示は未確認です。旧コードの書き換え表と、OSやPythonの版によってpipが古い版を選んでしまう問題も扱います。MediaPipeの定義や商用利用の範囲を先に知りたい方は、MediaPipe(メディアパイプ)とは?Google製AIライブラリの機能・読み方・商用利用をわかりやすく解説を参照してください。
まとめ:MediaPipeの使い方の要点(1.1.0時点)
- 導入は
pip install mediapipeの1行です。最新版は1.1.0(2026年10月6日公開)で、PyPIの対応PythonはPython 3.9〜3.14です。 - 書き方はTasks APIです。
mp.solutionsは0.10.30(2025年12月16日)からpip版に含まれず、1.1.0でも戻っていません。 - 手順は「
.taskなどのモデルを取得→Optionsを作る→create_from_options→detect系メソッド」の4段です。 - 静止画はIMAGE、動画の順次処理はVIDEO、画面を止めたくないカメラ処理はLIVE_STREAMの各モードを使います。VIDEOとLIVE_STREAMではミリ秒のタイムスタンプを単調増加させます。
- Intel MacはPython 3.9〜3.12で0.10.21が入り、3.13以降では入りません。Windows ARM64は依存するOpenCVのwheelが無く、pipがOpenCVのソースビルドに進みます。
- GPUの指定が効くのはUbuntuだけです。WindowsとmacOS、物理GPUの無い仮想マシンはCPUで動かす前提で設計します。
MediaPipeの使い方の全体像:モデルファイルと3つの実行モード
MediaPipeは、Googleが公開しているオンデバイス推論のライブラリです。Pythonから使う入口は mp.tasks.vision 以下のタスククラスです。この記事で扱う手・姿勢・顔のタスクは、次の4段で動きます。
- 学習済みモデル(
.taskまたは.tflite)をダウンロードする BaseOptionsにモデルのパスを渡し、タスクごとのOptionsを作るcreate_from_optionsでタスクを生成する- 画像を
mp.Imageに包み、実行モードに合ったdetect系メソッドへ渡す
モデルはパッケージに同梱されていないので、使うタスクの分だけ取得します。主なタスクとモデルは次のとおりです。サイズは2026年10月14日に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はフレーム間の追跡結果を使うので、前のフレームより大きいタイムスタンプが必要です。
MediaPipeのインストール手順とOS・Python別に入るバージョン
venvへのpip導入とバージョン確認のコマンド
公式のSetup guide for Pythonが求める条件は、Python 3.9以降とpip 20.3以上です。1.1.0を入れると、依存パッケージとしてOpenCV(opencv-contrib-python 5.0.0.93)、NumPy、Matplotlib、sounddevice、flatbuffers、absl-pyも入ります。既存の環境を汚さないよう仮想環境に入れます。
python3 -m venv .venv
source .venv/bin/activate # Windows は .venv\Scripts\activate
python -m pip install --upgrade pip
python -m pip install "mediapipe==1.1.0"
python -c "import mediapipe as mp; print(mp.__version__)" # 1.1.0
# この記事のコードが使うモデル(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
同じ環境に 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の依存解決で各環境を指定し、ビルド済みのwheelだけで解決できる版を確かめた結果が次の表です(2026年10月14日時点)。
| 環境 | Python | 入る版 | mp.solutions |
|---|---|---|---|
| Windows x64 | 3.14 | 1.1.0 | なし |
| macOS Apple Silicon | 3.14 | 1.1.0 | なし |
| Linux x86_64(glibc 2.28以降) | 3.9・3.14 | 1.1.0 | なし |
| Linux aarch64(64bitのRaspberry Pi OSなど) | 3.13 | 1.1.0 | なし |
| Windows ARM64 | 3.13 | wheelだけでは不可 | - |
| macOS Intel | 3.12 | 0.10.21 | あり |
| macOS Intel | 3.13・3.14 | 入らない | - |
Intel Mac向けのwheelは0.10.21(2025年2月)が最後で、0.10.21はPython 3.9〜3.12向けにしか作られていません。依存もNumPy 1.26.4・OpenCV 4.11に下がります。「Intel Macでは旧サンプルが動くのに、Windowsの同僚の環境では動かない」という食い違いは、この版の差で起きます。チームで使うなら requirements.txt で版を固定し、Apple SiliconかLinuxにそろえるほうが切り分けが楽です。Pythonの版を切り替えるならpyenvでPythonのバージョンを切り替える手順|shimの仕組みとビルド失敗の対処が使えます。
Windows ARM64は、MediaPipe本体には0.10.35以降 win_arm64 のwheelがあるのに、依存先の opencv-contrib-python 5.0.0.93にARM64版のwheelがありません。そのままpipを実行するとOpenCVをソースからビルドしようとして、ビルド環境が無ければ失敗します。Snapdragon搭載のWindowsノートで試すなら、WSL2のLinux(aarch64)に入れるほうが早道です。
Linuxのaarch64版は0.10.18を最後にいったん途切れ、1.0.0で再び配布されました。Raspberry Piで1.x系を使うには64bit OSが必要です。AI処理用の拡張ボードとの組み合わせはラズベリーパイ(Raspberry Pi)でAIを動かす方法|AI HAT+・AI Kit・AI Cameraの選び方とセットアップにまとめています。
0.10.30以降のwheelは py3-none というタグで、Pythonのマイナー版ごとには作られていません。推論本体は同梱の libmediapipe.so などのネイティブライブラリで、Python側はそれを呼び出す薄い層です。1.0.1まではPyPIの分類子が3.9〜3.12でしたが、1.1.0のPyPIページでは3.9〜3.14に広がり、v1.1.0のリリースノートにもPython 3.13・3.14への対応が記録されています。
Dockerやサーバーで動かすときに追加するOSライブラリ
Dockerの python:3.12-slim のような最小イメージにpipで1.1.0だけを入れると、import mediapipe の時点で ImportError: libxcb.so.1: cannot open shared object file が出て止まります。依存の opencv-contrib-python がデスクトップ向けで、X11やOpenGLのライブラリを前提にしているためです。足りないものを足すたびに libGL.so.1、libgthread-2.0.so.0、MediaPipe本体が読む libGLESv2.so.2 とエラーが変わり、次の4パッケージを追加すると静止画・動画ファイルの推論が動きました。実機カメラの取得と画面表示には、別途デバイスと表示環境の設定が必要です。
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1 libglib2.0-0 libegl1 libgles2 \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir mediapipe==1.1.0
WORKDIR /app
画面表示が要らないサーバーなら、opencv-contrib-python を外して opencv-python-headless に入れ替える方法もあります。この場合に必要なOSライブラリは libegl1 と libgles2 だけで、手の検出と描画も動きました。ただし pip check が依存不足を警告し、pip install -U mediapipe でデスクトップ版のOpenCVが戻るので、入れ替えはDockerfileの手順に固定します。
GPU指定が効く環境とCPU前提で設計すべき環境
GPUを使うには BaseOptions に delegate=mp.tasks.BaseOptions.Delegate.GPU を指定します。1.1.0に同梱されたソースの説明には「GPU support is currently limited to Ubuntu platforms」とあり、効くのはUbuntuだけです。リリースノートにはLiteRTやNPUの指定に関する変更もありますが、Python版の Delegate で選べる値は1.1.0でも CPU と GPU だけでした。
物理GPUの無い仮想マシンやコンテナでGPUを指定すると、タスクの生成時に「GPU emulation detected, but not supported.」という RuntimeError で止まります。CIやクラウドのCPUインスタンスでは delegate を書かずに動かします。
MediaPipeのGPU処理はOpenGL ESを使う設計で、pip版にCUDAを指定する設定はありません。NVIDIAのGPUで速くしたい要件なら、ONNX RuntimeやTensorRTで動くモデルを選ぶほうが近道です。
mp.solutionsが使えない原因とTasks APIへの移行表
MediaPipe 1.0.1でもmp.solutionsを利用できない原因は、pipで配布されるパッケージから旧APIが外れていることです。PyPIのwheelを展開して mediapipe/python/solutions/ 以下のファイル数を数えると、0.10.21は22ファイル、0.10.30・0.10.35・1.0.0・1.0.1はいずれも0でした。1.1.0ではディレクトリ自体が無く、mediapipe/__init__.py が読み込むのは mediapipe.tasks.python と Image・ImageFormat だけです。
GitHubの google-ai-edge/mediapipe リポジトリには、v1.0.0のタグでも mediapipe/python/solutions のディレクトリが残っています。ソースを見て「まだ使える」と判断するとここで誤ります。pipで入るパッケージには入っていません。GoogleはSolutions guideで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 | - |
| PoseLandmark | vision.PoseLandmark | - |
HolisticLandmarkerは0.10.33でPython版に戻ったタスクです。顔が検出されないと落ちる不具合は1.1.0で修正され、顔の小さい pose.jpg でも顔0件・姿勢33点・両手21点ずつが例外なく返りました。描画用の drawing_utils・drawing_styles と関節番号の PoseLandmark は、1.x系では mp.tasks.vision の下にあります。0.10.21にはこの場所に無いので、Intel Macと他の環境の両方で動かすコードでは、番号を整数で書きOpenCVで線を引くほうが安全です。
Tasks API移行時のモデル指定・画像入力・結果形式の変更
# 旧:0.10.21以前(1.x系では 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.x系)
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.10.21で同じ画像を処理すると、人差し指の先端は正規化座標で (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年8か月更新の無い版に留まっても困らない社内検証に限ります。新規開発で0.10.21を選ぶ理由はありません。Raspberry PiなどのLinux aarch64やWindows ARM64には入らず、HolisticLandmarkerの修正のような不具合修正も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))
# 1.x系は vision.drawing_utils で骨格を描ける(0.10.21には無い)
for hand in result.hand_landmarks:
vision.drawing_utils.draw_landmarks(
bgr, hand, vision.HandLandmarksConnections.HAND_CONNECTIONS,
vision.drawing_styles.get_default_hand_landmarks_style(),
vision.drawing_styles.get_default_hand_connections_style())
cv2.imwrite("hands_out.jpg", bgr)
hands.jpg として保存したGoogleのサンプル画像(元の名前は woman_hands.jpg・640×960)で実行すると、2本の手が検出されます。この値は0.10.21で実行したときと1ピクセルも違いませんでした。
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 を使います。手の中心を原点にしたメートル単位の推定座標で、実測寸法と一致する保証はないため、寸法測定に使う場合は既知の長さとの誤差を確認します。num_hands の既定値は1なので、両手を扱うなら2を指定します。3つのしきい値の既定値はいずれも0.5です。min_hand_detection_confidence は手の検出を採用するかの値で、誤検出が多ければ上げ、取りこぼしが多ければ下げます。min_hand_presence_confidence と min_tracking_confidence はVIDEOとLIVE_STREAMで効き、下回ると軽い追跡をやめて検出からやり直します。上げすぎると再検出が増えて処理が重くなります。
ファイルから直接読むなら mp.Image.create_from_file("hands.jpg") でも同じ結果です。OpenCVで読んだ配列はBGR順なので、RGBへの変換を忘れると、例外は出ないまま検出された手が2本から1本に減りました。
姿勢推定(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 # 1.x系なら vision.PoseLandmark.LEFT_KNEE なども使える
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回続き、ValueError: Input timestamp must be monotonically increasing. で止まることがあります。Googleのサンプル画像 pose.jpg を ffmpeg -loop 1 -i pose.jpg -t 1 -r 30 -vf scale=1000:666 -pix_fmt yuv420p pose.mp4 で30フレームの動画にして実行したところ、左ひざの角度は175.6度で、脚がほぼ伸びた姿勢として検出されました。
角度はピクセル座標に直してから計算しています。正規化座標のまま計算すると、横長の画像では横方向が縮んで扱われ、角度がずれるためです。ただし求めているのは画像上の2次元投影角で、奥行きを含む実際の関節角度ではありません。カメラに対して斜めに立つと値が変わるので、比べる動画どうしは撮影方向をそろえます。ひざなら23・25・27番(左の腰・ひざ・足首)、ひじなら11・13・15番(左の肩・ひじ・手首)の組み合わせで同じ関数が使えます。
lite・full・heavyのモデルの選び方と処理時間
姿勢推定のモデルは3種類で、入力サイズはどれも検出器224×224、ランドマーク推定256×256と同じです。違うのはモデルの規模です。同じ静止画(1000×667)をIMAGEモードで各モデル5回ウォームアップした後、30回処理し、detect呼び出し時間の中央値を測りました。モデルの生成、画像の読み込み、描画の時間は含みません。環境はIntel Mac上の仮想マシン(4 vCPU・CPU推論)なので、見るのは3つの比率です。
| モデル | ファイルサイズ | 1枚あたり(中央値) | 左手首の座標 |
|---|---|---|---|
| lite | 5.8MB | 54.5ms | (0.698, 0.469) |
| full | 9.4MB | 65.3ms | (0.695, 0.473) |
| heavy | 30.7MB | 157.6ms | (0.699, 0.469) |
今回の静止画で比較した左手首のx・y座標は、3つのモデル間の差がそれぞれ0.005以内でした。全33点や別の画像で同じ差に収まることを示す結果ではありません。fullはliteより2割遅いだけなので、CPUでカメラ映像を処理するならliteから始め、手先や足先がぶれて要件を満たさないときにfullへ上げます。heavyはliteの約3倍の時間がかかるため、リアルタイム性を求めない録画の解析に回します。
画面を止めずに推論する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()
result_callback を渡し忘れると、生成の時点で ValueError: The vision task is in live stream mode, a user-defined result callback must be provided. になります。コールバックは推論側のスレッドから呼ばれるので、中で描画や保存のような重い処理をしないのが原則です。結果だけを変数に入れ、描画はメインのループで行います。
推論が追いつかない間のフレームは捨てられます。動画ファイルの60フレームを待たずに渡したところ、コールバックは6回でした。全フレームの記録が必要な用途はVIDEOモードを選びます。
顔検出(Face Detector)と顔ランドマークの使い分け
顔の位置だけ分かればよいならFaceDetector、目や口の形や表情まで扱うならFaceLandmarkerを使います。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 はピクセルの整数で返ります。同じ感覚で幅や高さを掛けると、画面の外に枠が描かれます。
表情係数(ブレンドシェイプ)は既定では返らず、FaceLandmarkerOptions で output_face_blendshapes=True を指定したときだけ出力されます。同じ画像をFaceLandmarkerにかけると478点と52個の係数が返り、最も大きいのは mouthSmileLeft の0.96でした。既定の blaze_face_short_range は近い距離の顔向けのモデルで、集合写真のように顔が小さく写る画像にはfull-range版(blaze_face_full_range.tflite)が用意されています。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()
入力動画は作業ディレクトリに pose.mp4 として用意するか、コードのファイル名を変更します。検証では前の章で作った30フレームの動画を使い、全フレームで検出できたため、見出し行とデータ30行・134列のCSVができました。人が映っていないフレームは書き込まず、フレーム番号を残して欠損が分かるようにしています。visibility はその点が画面内に見えている確からしさで、0.5未満の点を分析から外すだけでも、画面の端で手足が切れたときの外れ値を減らせます。
撮影位置が違う動画どうしを比べるなら、正規化座標ではなく pose_world_landmarks(左右の腰の中点を原点にしたメートル単位の座標)を保存します。動作の速さが違う2本の系列を並べて比べるには、PythonでのDTW(動的時間伸縮法)実装ガイド:numpyフルスクラッチとライブラリの使い分けで説明している時間軸の伸縮合わせが使えます。
PyInstallerで実行ファイルにまとめる手順
Pythonの無いPCに配るためにPyInstallerで1つの実行ファイルにする場合、モデルファイルもMediaPipeのネイティブ部分も自動では入りません。公式のSetup guide for Pythonは2026年8月の更新でこの手順を載せています。実行時のパスは、PyInstallerが展開先を示す sys._MEIPASS を基準に組み立てます。
import os
import sys
import mediapipe as mp
def resource_path(relative_path):
# PyInstallerで固めたときは展開先(sys._MEIPASS)を基準にする
base_path = getattr(sys, "_MEIPASS", os.path.abspath("."))
return os.path.join(base_path, relative_path)
vision = mp.tasks.vision
options = vision.HandLandmarkerOptions(
base_options=mp.tasks.BaseOptions(model_asset_path=resource_path("hand_landmarker.task")),
num_hands=2,
)
with vision.HandLandmarker.create_from_options(options) as landmarker:
result = landmarker.detect(mp.Image.create_from_file(sys.argv[1]))
print("hands:", len(result.hand_landmarks))
# 直前のPythonコードを app.py として保存し、仮想環境内で実行
python -m pip install "pyinstaller==6.22.3"
# macOS・Linux(Windowsは --add-data の区切りを ; にする)
pyinstaller --onefile app.py \
--add-data "hand_landmarker.task:." \
--collect-all mediapipe \
--hidden-import mediapipe.tasks.c
PyInstaller 6.22.3(Linux x86_64)で試すと、pyinstaller --onefile app.py だけで作った実行ファイルは、起動直後に ModuleNotFoundError: No module named 'mediapipe.tasks.c' で止まりました。上の3つのオプションを付けると hands: 2 と出て動き、ファイルは約175MBになりました。Linuxでのビルドには objdump(binutilsパッケージ)も必要です。
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 file with name: hand_detector.tflite |
別タスクのモデルを指定 | タスクに合う.taskを渡す |
Unable to open file at |
モデルのパス誤り | 絶対パスか作業場所を確認 |
a user-defined result callback must be provided |
LIVE_STREAMでコールバック無し | result_callbackを渡す |
No matching distribution found |
Intel MacのPython 3.13以降など | Python 3.12か別環境 |
libGL.so.1 などが開けない |
最小構成のLinux・Docker | OSライブラリを追加 |
GPU emulation detected |
物理GPUの無い環境でGPU指定 | delegateを外してCPUで |
No module named 'mediapipe.tasks.c' |
PyInstallerの収集漏れ | --collect-all を付ける |
終了時に Exception ignored in ... __del__ |
タスクを閉じていない | with文かclose() |
| 例外は出ないが検出が減る | BGRのまま渡している | cvtColorでRGBへ変換 |
| cv2の関数が見つからない | OpenCVパッケージの併存 | 全削除して1つ入れ直す |
モデルの取り違えは、エラー文の続きにバンドルの中身(姿勢のモデルなら pose_detector.tflite など)が出るので見分けられます。タスクを閉じずに終わると、結果は正しく出たあと終了時に TypeError: 'NoneType' object is not callable が表示されます。処理に影響はありませんが、ログ監視の誤検知の元になるのでタスクは必ず閉じます。
カメラが開けないときは、MediaPipeより先に cv2.VideoCapture(0).read() 単体で映像が取れるかを確かめます。macOSではターミナルやエディタにカメラの利用許可が必要で、許可が無いとフレームが空のまま返ります。OpenCV側の基本操作はOpenCVとは?Pythonでの使い方入門|pip installから画像処理・顔検出まで【5.0対応】で確認できます。用意されたタスクで精度が足りず、自社の製品や不良を見分けるモデルを作る段階になったら、画像認識AIモデル構築の支援のように学習データの設計から着手する開発方針に切り替えます。
MediaPipe(メディアパイプ)の使い方に関するよくある質問
MediaPipeは商用利用できますか?
できます。ライブラリ本体はApache License 2.0で、手・姿勢・顔の主なモデルのモデルカードもApache License 2.0です。再配布するときは、ライセンス文の同梱、著作権表示とNOTICEファイルの内容の保持、改変したファイルへの変更表示を守ります。もう1つ確認したいのがGitHubのREADMEにあるPrivacy Notice(2026年6月5日更新)です。画像などの入力は端末の外に出ませんが、Tasks APIは性能と利用状況のメトリクスをGoogleへ送り、法律が求める範囲で利用者の同意を得る責任はアプリ側にあると書かれています。詳しい範囲はMediaPipeとは?の解説記事にまとめています。
MediaPipeに必要なPythonのバージョンは?
公式ガイドの条件はPython 3.9以降で、1.1.0のPyPIの分類子には3.9〜3.14が書かれています。Intel Macだけは例外で、Python 3.9〜3.12では0.10.21が入り、3.13以降に対応する公式wheelはありません。
MediaPipeでリアルタイムの骨格検出はできますか?
できます。PoseLandmarkerをVIDEOモードかLIVE_STREAMモードで作り、Webカメラのフレームを順に渡します。4 vCPUの仮想マシンではliteモデルが1枚約55msでした。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以前の版へ固定することになります。