このブログでは1年半ほど前に、MediaPipeでの画像認識を何本か書きました(Pythonで画像認識 MediaPipeを試すから始まる一連の記事です)。当時のコードはこんな書き出しでした。
import mediapipe as mp
with mp.solutions.pose.Pose(static_image_mode=True) as pose:
result = pose.process(image)
これを最新のMediaPipeで動かすと、こうなります。
AttributeError: module 'mediapipe' has no attribute 'solutions'
「バージョンを上げたら動かなくなった」という、いちばん気が滅入るやつです😳
実はこの検索、うちのブログにもよく届いています。face_landmarker.task というモデルファイル名そのままの検索や、「mediapipe solutions ない」という検索で、1年半前の記事が引っかかっている。でも当時の記事には答えが書いてありません。書いた時点では、まだ消えていなかったからです。
そこで今回は、4つのバージョンを実際に入れて、次の3つをはっきりさせます。
- いつ、何が消えたのか
- 代わりに必要になった
.taskファイルは、どこから入手して、どう選ぶのか - 乗り換えると、検出結果と速度はどう変わるのか
先に結論
mp.solutionsは 0.10.31(2025年12月18日)で完全に消えた。属性が無いだけでなく、モジュールごと入っていない- 同じ版で「モデルの同梱」もやめた。22個・29.2MB入っていたモデルが0個に。だから
.taskを自分で用意する必要がある - 検出結果はほぼ同じ(顔478点の平均のズレは画像の幅の0.173%)。手の検出は2.1倍速くなった
昔のコードが、そのままでは動きません
まず、何が起きているのかを整理します。MediaPipeには昔から2つのAPIがありました。
| 観点 | 旧:Solutions API | 新:Tasks API |
|---|---|---|
| 書き方 | mp.solutions.pose.Pose(...) | vision.PoseLandmarker.create_from_options(...) |
| モデル | パッケージに同梱(意識しなくてよい) | .task ファイルを自分で渡す |
| 対応 | Python中心 | Python / Android / iOS / Web で共通 |
| いまの状態 | 削除済み | これだけになった |
公式には数年前から「旧Solutionsのサポートは終わり、新しいSolutionへ移行する」と案内されていました。ただ、案内が出てからも実際にはしばらく動き続けていたので、多くのコードがそのまま残っています。そして、ある版から本当に消えました。
用語集
先に言葉を片付けておきます。
| 用語 | 平たく言うと | なぜ効くのか |
|---|---|---|
| MediaPipe | Googleの、映像から顔・手・姿勢などを見つけるための道具一式 | カメラ映像からアバターを動かす、といった用途の定番 |
| Solutions API(旧) | mp.solutions.pose のように呼ぶ昔の書き方 | モデルが同梱されていたので、インストールした瞬間から動いた |
| Tasks API(新) | vision.PoseLandmarker のように呼ぶ今の書き方 | ★モデルを外から渡す設計。だから .task ファイルの話が出てくる |
| ランドマーク | 顔や手の「特徴点」。目尻・指先などの座標 | 顔478点・手21点・姿勢33点。この座標を使ってアバター等を動かす |
| .task ファイル | Tasks APIに渡すモデルの入れ物 | ★実体はzipで、中に複数のモデルが入っている(後述) |
| tflite | TensorFlow Liteのモデル形式。軽く速く動かすための形 | .task の中身はこれ。1つのタスクに複数入ることがある |
| ブレンドシェイプ | 「口角を上げる」など表情の度合いを0〜1で表した値 | 52種類返る。アバターの表情を動かすのに使う |
| wheel(ホイール) | Pythonの配布用パッケージ(.whlファイル) | どのOS・どのPython向けに用意されているかが、ここで決まる |
今回の検証環境
Mac(Apple Silicon)だけで完結しています。バージョンごとに専用の仮想環境を4つ作り、混ざらないようにしました。
| 項目 | 内容 |
|---|---|
| マシン | MacBook Pro(Apple Silicon M4) |
| Python | 3.12.5(一部の確認だけ3.10.12も使用) |
| 検証した版 | mediapipe 0.10.21 / 0.10.31 / 0.10.35 / 1.0.0 |
| テスト画像 | MediaPipe公式のサンプル画像(portrait.jpg / right_hands.jpg / pose.jpg / business-person.png) |
| 計測方法 | 同じ画像を6回処理し、1回目を捨てて中央値 |
記事に載せる画像について
検出に使ったのは公式のサンプル写真ですが、この記事に載せるのは「検出された点」だけにしています。写真そのものは載せません。点だけでも、旧APIと新APIがどれくらい一致するかは十分に見て取れます。
★いつ消えたのか──4つの版を入れて確かめた
各版で hasattr(mp, "solutions") を見るだけでなく、モジュールを直接importできるかも確かめました。属性が無いだけなら別の呼び方で生き残っている可能性があるからです。
| 版 | 公開日 | mp.solutions | import mediapipe.python.solutions.pose |
|---|---|---|---|
| 0.10.21 | 2025-02-06 | あり(8種類) | 成功 |
| 0.10.31 | 2025-12-18 | なし | ModuleNotFoundError |
| 0.10.35 | 2026-04-27 | なし | ModuleNotFoundError |
| 1.0.0 | 2026-07-27 | なし | ModuleNotFoundError |
0.10.31 が分かれ目でした。0.10.21 には face_mesh / hands / pose / holistic / face_detection / selfie_segmentation / objectron / drawing_utils の8つが揃っていましたが、0.10.31 ではモジュールごと消えています。段階的な非推奨ではなく、まるごと削除です。
ついでに、新API側で消えたものもありました。HolisticLandmarker(顔・手・姿勢をまとめて取るタスク)と FaceStylizer / FaceAligner が 0.10.31 で無くなっています。旧APIの holistic を使っていた場合、新APIにも受け皿がないので、顔・手・姿勢を別々に呼ぶ形に書き換えることになります。
★同時に「モデルの同梱」もやめていた
ここが、.task ファイルの話につながる一番のポイントです。
インストールされたパッケージの中を歩いて、モデルファイル(.tflite / .task / .binarypb)を数えました。
| 版 | 同梱モデル | パッケージ全体 |
|---|---|---|
| 0.10.21 | 22個 / 29.2MB | 113.4 MB |
| 0.10.31 | 0個 | 59.8 MB |
| 0.10.35 | 0個 | 55.0 MB |
| 1.0.0 | 0個 | 103.9 MB |
0.10.21 に入っていたのは、こういう顔ぶれです。
modules/pose_landmark/pose_landmark_full.tflite 6.44 MB modules/hand_landmark/hand_landmark_full.tflite 5.48 MB modules/pose_detection/pose_detection.tflite 2.96 MB modules/iris_landmark/iris_landmark.tflite 2.64 MB modules/face_landmark/face_landmark_with_attention.tflite 2.50 MB modules/palm_detection/palm_detection_full.tflite 2.34 MB …(全22個・合計 29.2 MB)
「インストールしただけで動いた」のは、モデルが最初から入っていたからです。それが0個になった。だから新しいAPIでは、自分でモデルを用意して渡すことになります。
⚠ 旧APIは、足りないモデルを勝手にダウンロードしていました
今回 0.10.21 で model_complexity=0(lite)と 2(heavy)を指定したところ、コンソールにこう出ました。
Downloading model to …/site-packages/mediapipe/modules/pose_landmark/pose_landmark_lite.tflite
同梱されていなかったモデルを、実行時に取りに行って site-packages の中に書き込んでいたのです。手軽ではありますが、オフラインでは動かず、どのモデルが使われているかも見えにくい作りでした。新APIで「自分で渡す」形になったのは、この裏返しだと思います。
依存ライブラリも激減した(protobuf地獄の終わり)
ついでに気づいたのですが、依存ライブラリも大きく整理されていました。
| 版 | 依存の数 | 中身 |
|---|---|---|
| 0.10.21 | 11個 | absl-py, attrs, flatbuffers, jax, jaxlib, matplotlib, numpy<2, opencv-contrib-python, protobuf<5,>=4.25.3, sounddevice, sentencepiece |
| 0.10.31 | 4個 | absl-py, numpy, sounddevice, flatbuffers |
| 0.10.35 / 1.0.0 | 7個 | 上記+certifi, opencv-contrib-python, matplotlib |
これは地味に大きな改善です。というのも、eightの検証機ではまさにこの依存が原因で MediaPipe が壊れていました。
以前Neural Engineの記事を書いたときに Core ML の変換ツールを入れたのですが、そのとき protobuf が新しい版に上がりました。すると 0.10.21 が要求する protobuf<5 と噛み合わなくなり、importした瞬間にこうなります。
AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'
0.10.31 以降は protobuf も numpy<2 も要求しません。つまり、この手の衝突はバージョンを上げるだけで解消します。「壊れたから古い版に戻す」を繰り返していた人ほど、上げたほうが平和になります😊
.taskファイルの正体は zip だった
では、その .task ファイルとは何なのか。ダウンロードして中を覗いてみました。
実体は zip です。拡張子が違うだけで、普通に展開できます。
face_landmarker.task (3.76 MB) ├ face_landmarks_detector.tflite 2.55 MB … 478点を取る本体 ├ face_blendshapes.tflite 0.96 MB … 52種類の表情の度合い ├ face_detector.tflite 0.23 MB … まず顔の位置を見つける └ geometry_pipeline_metadata_landmarks.binarypb 0.02 MB … つなぎの設定
つまり .task は「1つの仕事に必要なモデル一式をまとめた箱」です。顔の検出は「①顔を見つける → ②細かい点を取る → ③表情を測る」の3段構えなので、3つのモデルが入っています。
手の場合はこうです。
hand_landmarker.task (7.82 MB) ├ hand_landmarks_detector.tflite 5.48 MB … 21点を取る └ hand_detector.tflite 2.34 MB … まず手のひらを見つける
面白かったのが、ジェスチャー認識の入れ子構造です。
gesture_recognizer.task (8.37 MB) ├ hand_landmarker.task 7.82 MB ← ★.taskの中に.taskが丸ごと入っている └ hand_gesture_recognizer.task 0.55 MB
手の検出をまるごと再利用して、その上に「グーかパーか」を判定する小さなモデルを乗せている、という構成が見て取れます。
この構造が分かると、次に気になるのは「では自分で作れるのか」です。結論から言うと作れました。詳しくは ★自分で .task を作ってみる で書きます。
中身は昔のモデルとほぼ同じもの
旧版に同梱されていたファイルと、.task の中身を並べると、名前も大きさもきれいに対応します(palm_detection_full.tflite 2,339,846バイト ↔ hand_detector.tflite 2,339,878バイト、など)。多くが32バイトだけ大きいのは、末尾にラベル等のメタデータが足されているためです(.tflite 自体も中がzipになっています)。
つまり「別物の新しいモデルに置き換わった」わけではなく、配り方が変わった、というのが実態に近いです。ただしバイト単位では一致しないので、再ビルドはされています。
★自分で .task を作ってみる
zipだと分かると、次に気になるのは「自分で作れるのか」です。作れました。しかも作り方はMediaPipe自身のソースに書いてありました。
mediapipe/tasks/python/metadata/metadata_writers/model_asset_bundle_utils.py
このファイルを読むと、決まりごとは3つでした。
| 決まり | ソースに書かれている理由 | 破るとどうなるか(実測) |
|---|---|---|
| 無圧縮であること | 作成時に ZIP_STORED 以外を弾いている | 圧縮して作ると読み込みで RuntimeError: Expected uncompressed zip archive. |
| 4バイト境界に揃える | 「mmapで直接読めるように」とコメントに明記 | 揃えなくても読み込めた(下記) |
| 最低2ファイル | 1つだと ValueError | 作成の時点で拒否される |
公式ファイル14エントリを調べたところ、すべて無圧縮で、すべて4の倍数の位置から始まっていました。仕様どおりです。
ただ、2番目については実際に試すと少し違いました。境界を揃えずに(データの開始位置が50バイト目という半端な位置で)作ったバンドルも、問題なく読み込めています。mmapで速く読むための最適化であって、読み込みの条件ではないようです。
作ってみる
作成用の関数は __init__.py から公開されていないので、直接読み込んで使います。
import importlib, zipfile
mab = importlib.import_module(
"mediapipe.tasks.python.metadata.metadata_writers.model_asset_bundle_utils")
def entries(path): # .task の中身を取り出す
with zipfile.ZipFile(path) as z:
return {i.filename: z.read(i.filename) for i in z.infolist()}
lite = entries("pose_landmarker_lite.task")
heavy = entries("pose_landmarker_heavy.task")
# ★公式には無い組み合わせを作る:1段目はlite、2段目はheavy
mab.create_model_asset_bundle({
"pose_detector.tflite": lite["pose_detector.tflite"],
"pose_landmarks_detector.tflite": heavy["pose_landmarks_detector.tflite"],
}, "pose_landmarker_hybrid.task")
これで作った pose_landmarker_hybrid.task は、そのまま PoseLandmarker に渡して動きました(33点を検出)。特別な署名も検証もありません。
⚠ ただし、中のファイル名は変えられません
試しに pose_detector.tflite を posedetector.tflite に変えただけで、こうなりました。
FileNotFoundError: No file with name: pose_detector.tflite. All files in the model asset bundle are: posedetector.tflite, pose_landmarks_detector.tflite
中のファイル名そのものがインターフェースになっています。MediaPipeのネイティブライブラリ(mediapipe/tasks/c/libmediapipe.dylib)から、期待されている名前を抜き出すとこうなっていました。
| タスク | 必要なファイル名 |
|---|---|
| FaceLandmarker | face_detector.tflite / face_landmarks_detector.tflite / face_blendshapes.tflite |
| HandLandmarker | hand_detector.tflite / hand_landmarks_detector.tflite / hand_roi_refinement.tflite |
| PoseLandmarker | pose_detector.tflite / pose_landmarks_detector.tflite |
| GestureRecognizer | hand_landmarker.task / hand_gesture_recognizer.task |
| (その中) | gesture_embedder.tflite / canned_gesture_classifier.tflite / custom_gesture_classifier.tflite |
| InteractiveSegmenter | interactive_segmentation_encoder.int8.tflite / ..._decoder.int8.tflite |
最後から2番目の custom_gesture_classifier.tflite に注目してください。「自分で学習させた分類器をここに差し込む」ことが最初から想定された名前です。ジェスチャー認識だけは、自作モデルの居場所が公式に用意されているわけです。
★入れ子になっている
ジェスチャー認識の .task は、中にさらに .task が入った2階層の構造でした。
★自作して分かった、いちばん大事なこと
せっかく作れるので、公式に無い組み合わせを試しました。pose の1段目と2段目を入れ替えて、速度と精度をどちらが決めているのかを切り分けます。
| 組み合わせ | サイズ | 時間 | heavyとの差(2枚目・平均/最大) |
|---|---|---|---|
| lite | 5.78 MB | 13.7 ms | 6.789% / 33.801% |
| full | 9.40 MB | 15.2 ms | 2.516% / 6.946% |
| heavy | 30.66 MB | 23.8 ms | 0%(基準) |
| ★軽い検出器 + 重いランドマーカー | 30.66 MB | 23.6 ms | 0.000% / 0.000% |
| ★重い検出器 + 軽いランドマーカー | 5.78 MB | 13.9 ms | 6.789% / 33.801% |
きれいに割れました。
精度も速度も、2段目だけで決まっていた
1段目(人物を見つける方)を軽いものに替えても、結果は heavy と1点も違いませんでした(0.000%)。逆に2段目を軽いものに替えると、1段目が重かろうと lite と完全に同じ結果になります。
公式が lite / full / heavy で差し替えているのも2段目だけで、1段目はほぼ共通のファイルです。「モデルを選ぶ」とは、実質2段目を選ぶことでした。
ということは、自作でおいしい組み合わせは今のところ無いという結論にもなります。1段目を軽くしても速くならず、重くしても精度は上がらないからです😅 それでも「どこが効いているのか」がはっきりしたのは収穫でした。
⚠ モデルそのものを学習して作る道は、いま険しい
ここまでは「手持ちのモデルを詰め替える」話でした。モデル自体を自分のデータで学習させるには、公式に MediaPipe Model Maker という別パッケージがあります。ジェスチャーや画像分類を自前データで学習し、.task を書き出せる道具です。
ただ、いまの状況はあまり良くありません。
- 最終更新が2024年4月(0.2.1.4)。本体が1.0.0になった現在も更新されていません
- 依存が
tensorflow<2.16なのに、TensorFlow 2.15 は Python 3.12 に対応していません(3.12対応は2.16から)。そのままでは要件を満たせません。使うならPython 3.11以下の環境を別に用意することになります
さらに、.tflite にラベルなどのメタデータを書き込む metadata_writers も、いまの配布物では動きません。
| 版 | metadata_writers(メタデータ書き込み) | バンドル作成 | tasks/cc |
|---|---|---|---|
| 0.10.21 | 動く | 動く | 330ファイル |
| 0.10.31 / 0.10.35 / 1.0.0 | No module named 'mediapipe.tasks.cc' | 動く | 0ファイル |
ファイル自体は同梱されているのに、依存している tasks/cc(330ファイルあった生成モジュール群)が丸ごと消えているため読み込めません。この記事の主題だった0.10.31の軽量化は、ここにも及んでいました。
つまり現状は、「詰める道具は生きているが、中身を作る道具は動かない」という状態です。既存のモデルを組み替えるだけなら今のままで足りますが、自分のデータでモデルを作るなら、古い版やTensorFlow側の道具を別環境に用意することになります。
モデルファイル一覧(どこから入手して、どれを使うか)
入手先はすべて https://storage.googleapis.com/mediapipe-models/… です。よく使うものをまとめました(サイズは実測)。
| 用途 | 新API | ファイル | サイズ | 旧APIでの呼び方 |
|---|---|---|---|---|
| 顔+表情 | FaceLandmarker | face_landmarker.task | 3.76 MB | face_mesh |
| 手・指 | HandLandmarker | hand_landmarker.task | 7.82 MB | hands |
| 姿勢(軽い) | PoseLandmarker | pose_landmarker_lite.task | 5.78 MB | pose |
| 姿勢(標準) | PoseLandmarker | pose_landmarker_full.task | 9.40 MB | |
| 姿勢(高精度) | PoseLandmarker | pose_landmarker_heavy.task | 30.66 MB | |
| 顔の位置だけ | FaceDetector | blaze_face_short_range.tflite | 0.23 MB | face_detection |
| 人物の切り抜き | ImageSegmenter | selfie_segmenter.tflite | 0.25 MB | selfie_segmentation |
| ジェスチャー判定 | GestureRecognizer | gesture_recognizer.task | 8.37 MB | (旧APIになし) |
旧APIの呼び方はいずれも mp.solutions. に続く名前です
URLの形は共通で、こうなっています。
https://storage.googleapis.com/mediapipe-models/{タスク名}/{モデル名}/float16/latest/{ファイル名}
例:
https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task
https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task
https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_heavy/float16/latest/pose_landmarker_heavy.task
latest の部分にはバージョン番号も指定できます。再現性が要るなら番号で固定し、常に最新でよければ latest のままにする、という使い分けになります。上の7つを全部落としても合計66.3MBなので、まとめて置いておいても大した量ではありません。
旧API → 新API 書き換え対応表
実際に書き換えると、こうなります。姿勢検出の例です。
旧(0.10.31以降では動きません)
import cv2
import mediapipe as mp
img = cv2.cvtColor(cv2.imread("pose.jpg"), cv2.COLOR_BGR2RGB)
with mp.solutions.pose.Pose(static_image_mode=True,
model_complexity=1) as pose:
result = pose.process(img)
for lm in result.pose_landmarks.landmark:
print(lm.x, lm.y, lm.z)
新(Tasks API)
import mediapipe as mp
from mediapipe.tasks import python as mp_python
from mediapipe.tasks.python import vision
img = mp.Image.create_from_file("pose.jpg") # ← 読み込みも専用のものに
options = vision.PoseLandmarkerOptions(
base_options=mp_python.BaseOptions(
model_asset_path="pose_landmarker_full.task"), # ← ★モデルを自分で渡す
num_poses=1)
with vision.PoseLandmarker.create_from_options(options) as detector:
result = detector.detect(img)
for lm in result.pose_landmarks[0]: # ← 1人目、の書き方に変わる
print(lm.x, lm.y, lm.z)
押さえどころは4つです。
| 観点 | 旧 | 新 |
|---|---|---|
| モデルの指定 | model_complexity=0/1/2 | ファイル名で選ぶ(lite / full / heavy) |
| 画像の渡し方 | OpenCVの配列をそのまま | mp.Image.create_from_file() または mp.Image(image_format=…, data=…) |
| 結果の形 | result.pose_landmarks.landmark(1人分) | result.pose_landmarks[0](人ごとのリスト) |
| 静止画/動画の別 | static_image_mode=True/False | running_mode=IMAGE / VIDEO / LIVE_STREAM |
座標の取り出し方(lm.x / lm.y / lm.z の正規化座標)は同じなので、その先の処理はほとんど書き換えずに済みます。以前このブログで作った「ランドマークをUnityにUDPで送る」仕組み(p705)も、送る中身は変わりません。
★検出結果は変わるのか
ここがいちばん気になるところだと思います。同じ写真を、旧APIと新APIの両方に通して、点を重ねてみました。
顔478点。青が旧・オレンジが新。ほぼ完全に重なります
肉眼ではまず区別がつきません。数字にするとこうです。
| 対象 | 点の数 | 平均のズレ | 最大のズレ |
|---|---|---|---|
| 顔 | 478点 | 0.173% | 0.569% |
| 姿勢(full) | 33点 | 0.050% | 0.146% |
| 手 | 21点 | 0.577% | 1.531% |
※ 画像の幅を100%としたときの距離です。顔の平均0.173%は、1000ピクセル幅の画像なら2ピクセル弱にあたります。
手21点×2。こちらもほぼ重なります
つまり「APIは変わったが、出てくる答えは実質同じ」です。書き換えの手間はかかりますが、精度が落ちる心配をしながら移行する必要はなさそうでした。
むしろ増えているものもあります。
- ブレンドシェイプ52種類… 旧APIでは別の仕組みが必要でしたが、新APIは
output_face_blendshapes=Trueだけで返ります。今回の写真ではmouthSmileLeft 0.96/mouthSmileRight 0.93/browDownLeft 0.84…と、笑っている表情が数値に出ていました - 手の左右判定…
Right/Leftがスコア付きで返ります - 顔は最初から478点… 旧APIは既定468点で、虹彩を含む478点にするには
refine_landmarks=Trueが必要でした。新APIは指定なしで478点です
速度は?──手は2.1倍速くなった
同じ画像・同じ回数で測りました(単位はミリ秒、6回測って1回目を捨てた中央値)。
| 処理 | 旧 0.10.21 | 新 0.10.35 | 新 1.0.0 | 旧→新 |
|---|---|---|---|---|
| 顔(478点) | 3.0 | 4.4 | 4.5 | 0.67倍(遅い) |
| 手 | 26.9 | 12.6 | 13.0 | 2.07倍速い |
| 姿勢 lite | 14.5 | 13.9 | 13.1 | 1.11倍速い |
| 姿勢 full | 18.9 | 15.9 | 14.6 | 1.29倍速い |
| 姿勢 heavy | 55.1 | 23.9 | 23.4 | 2.35倍速い |
重い処理ほど差が開いています。手と姿勢heavyが2倍以上速くなったのは、移行の後押しになる数字だと思います。
顔だけ遅くなっていますが、これは新APIが52種類のブレンドシェイプも一緒に計算しているからです。同じ仕事量ではないので、「遅くなった」と決めつけるのは早いところです。それでも4.5msなので、実用上は誤差の範囲でしょう。
★lite / full / heavy はどれを選ぶか(1枚目と2枚目で結論が変わった)
姿勢検出には3つのモデルがあります。5.78MB・9.40MB・30.66MB。5.3倍の差があるのだから、精度もそれなりに違うはず——と思って、同じ写真で並べてみました。
1枚目の写真。3つとも、ぱっと見の違いが分かりません
数字で見ても、lite と heavy の差は平均0.504%・最大1.020%。ほぼ同じです。ここで記事を書き終えていたら、「軽いモデルで十分」と結論していたはずでした。
ただ、このブログでは1回の出力で断定しないことにしています。そこで、別の写真でもう一度やってみました。
左が1枚目、右が2枚目。同じ比較なのに結果がまったく違います
| 写真 | lite vs heavy の平均 | 最大 |
|---|---|---|
| 1枚目(全身がはっきり写っている) | 0.504% | 1.020% |
| 2枚目(腕が体に近く、手が隠れ気味) | 6.789% | 33.801% |
2枚目では13倍以上の差がつきました。いちばんズレたのは左手の指先で、画像の幅の3分の1もの開きです。骨格図を見ると、liteは腕の位置を取り違えているのが分かります。
⚠ 「1枚で試して決める」がいちばん危ない
条件のいい写真では、どのモデルもほぼ同じ答えを出します。差が出るのは、条件が悪くなったときだけです。ということは、お手本のような画像で試して「軽いので十分」と決めた設定が、本番の映像で崩れることになります。
モデルを選ぶときは、うまくいく画像ではなく、いちばん条件の悪い画像で比べるのが正しい順序でした。
時間のほうは、2枚目でも lite 13.3ms / full 17.2ms / heavy 24.3ms と大きくは変わりません。heavyでも1枚あたり25ミリ秒=毎秒40枚は処理できる計算なので、「重くて使えない」というほどではありません。リアルタイムで多人数を追うなら別ですが、まずは heavy で確かめてから軽くしていくほうが安全だと感じました。
⚠Intel Macは切られました
最後に、環境まわりで大きな変更をひとつ。配布されているwheel(インストール用のファイル)の顔ぶれが変わっています。
| 版 | wheelの数 | macOS | Python |
|---|---|---|---|
| 0.10.21 | 16個 | universal2 と x86_64(Intel対応) | 3.9 / 3.10 / 3.11 / 3.12 それぞれ専用 |
| 0.10.31以降 | 3〜5個 | arm64のみ | 1本で共通(py3) |
2つのことが同時に起きています。
- ⚠ macOSのIntel版(x86_64)が無くなった。Intel Macでは、新しい版はそのまま入りません
- ✅ Pythonのバージョンごとの専用wheelをやめ、1本にまとめた。「Python 3.13にしたら入らない」といった問題が起きにくくなりました。実際、まったく同じファイルが Python 3.10 でも 3.12 でも入ることを確認しました
1.0.0では Linux の arm64(manylinux_2_28_aarch64)と Windows の arm64 も追加されています。「Apple Silicon・ARM前提へ寄せる」という方向がはっきり出ています。Raspberry Pi系で使いたい人には朗報です。
まとめ
- ✅
mp.solutionsは 0.10.31(2025-12-18)で完全に消えた。属性ではなくモジュールごと削除。0.10.21 → 0.10.31 の間に起きた - ✅ 同じ版で、同梱モデルが22個(29.2MB)から0個になった。「入れただけで動く」が終わり、
.taskを自分で渡す形になった - ★
.taskの正体は zip。顔なら「見つける・点を取る・表情を測る」の3つのモデルが入っている。ジェスチャー認識は中にhand_landmarker.taskを丸ごと抱えている - ★ .task は自分で作れる。無圧縮zipに詰めるだけで、署名も検証もない。ただし中のファイル名がインターフェースで、
pose_detector.tfliteのように決められた名前でなければ読み込まれない - ★ 精度も速度も、バンドルの2段目だけで決まっていた。1段目(人物を見つける方)を軽いものに替えても結果は heavy と1点も変わらず、2段目を替えると lite と完全に一致した
- ⚠ モデルを学習して作る道は険しい。Model Maker は2024年4月で更新が止まり、
tensorflow<2.16縛りのため Python 3.12 では要件を満たせない。メタデータを書き込む道具も 0.10.31 以降は動かない(tasks/ccが消えたため) - ★ 中身のモデルは昔とほぼ同じもの(多くは32バイトだけ大きい=メタデータぶん)。作り直したのではなく、配り方を変えたのが実態
- ★ 検出結果は実質同じ。顔478点の平均のズレは画像の幅の0.173%。それでいて手は2.07倍、姿勢heavyは2.35倍速くなった
- ★ モデル選びは1枚で決めてはいけない。lite対heavyの差は、条件のいい写真で0.504%、条件の悪い写真で6.789%(最大33.8%)。13倍以上ひらいた
- ✅ 依存が11個から4個へ。
protobuf<5とnumpy<2の縛りが消えたので、他のライブラリとぶつかって壊れていた環境は、上げるだけで直る - ⚠ Intel Mac向けのwheelは無くなった。一方でPythonのバージョン依存はなくなり、1本のファイルで3.10でも3.12でも入る
1年半前に書いた記事のコードが動かなくなっていたのは、正直ばつが悪いところでした。ただ調べてみると、消えたのは書き方だけで、中で動いているモデルも、返ってくる座標も、ほとんど変わっていません。「作り直し」ではなく「引っ越し」に近い移行です。同じ気持ちで検索してここに辿り着いた方の、遠回りが少しでも減れば嬉しいです😊
それでは、今回はここまで。最後までありがとうございました😊
※ 検証は MacBook Pro(Apple Silicon M4 / Python 3.12.5)で、mediapipe 0.10.21・0.10.31・0.10.35・1.0.0 をそれぞれ専用の仮想環境に入れて実施しました(2026年8月時点)。処理時間は同じ画像を6回処理し、1回目を捨てた中央値です。ズレの割合は画像の幅を100%としたときの距離で、公式のサンプル画像を用いています。★モデルの精度比較は写真2枚ぶんの結果であり、あらゆる条件を代表するものではありません。★.taskの自作と組み合わせの検証は mediapipe 1.0.0 で行い、バンドルの作成には同梱の model_asset_bundle_utils を使いました。Model Maker の制約はPyPIのメタデータから判断したもので、導入までは試していません。バージョンや配布物の内容は今後変わる可能性があります。