はじめに
Pythonで手の認識をしたかった。
調べると、どうやらMediaPipeというライブラリを使うといいらしい。
そこでAIにコードを書いてもらって試したところ、こんなエラーが出た。
AttributeError: module 'mediapipe' has no attribute 'solutions'
どうやら、solutionsが新しいバージョンでは削除されているらしい。
具体的には、バージョン0.10.30 で削除されたようだ。
しかし困ったことに、
- AIの生成するコード
- 少し前のQiita記事
- 古いチュートリアル
このあたりは、だいたい mp.solutions を使っている。
どうすればいいのかの情報があまり無いので、解決法をまとめる。
結論としては、次の2つ↓
- 楽な解決法:バージョンを下げる
- 最新版を使いたいなら:Tasks API に移行する
解決法その1:MediaPipeのバージョンを下げる
こちらの方が簡単でおすすめです。
メリット
- AIのコードがそのまま使える
- 情報が豊富
- Qiita記事をそのまま試せる
インストール
solutions が使えるバージョンをインストールする。
pip install mediapipe==0.10.21
OpenCVも必要なので忘れずに。
pip install opencv-python
バージョン確認
import mediapipe as mp
print(mp.__version__)
出力:
0.10.21
これでOK。
手の認識を試す
あとはAIに聞けばだいたい教えてくれる。
例えば、以下のコードはChatGPTに書いてもらったもの。
import cv2
import mediapipe as mp
# MediaPipe Hands
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
mp_draw = mp.solutions.drawing_utils
# カメラ起動
cap = cv2.VideoCapture(0)
while True:
success, frame = cap.read()
if not success:
break
# 左右反転
frame = cv2.flip(frame, 1)
# BGR -> RGB
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 手検出
results = hands.process(rgb)
# 検出結果描画
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_draw.draw_landmarks(
frame,
hand_landmarks,
mp_hands.HAND_CONNECTIONS
)
cv2.imshow("Hand Tracking", frame)
# ESCで終了
if cv2.waitKey(1) == 27:
break
cap.release()
cv2.destroyAllWindows()
実行すると、
- カメラが起動
-
"Hand Tracking"ウィンドウが表示 - 手を映すとランドマークが描画
されるはず。
個人的には、この方法がいちばん楽だった。
解決法その2:Tasks API に移行する
こちらは、最新版のMediaPipeを使いたい人向け。
最新版ではsolutionsの代わりにTasks APIを使う必要がある。
メリット
- 新しいAPIが使える
- ジェスチャー認識ができる
- 右手・左手を識別できる
※ちゃんと調べていないので、他にもあるかもしれない。
デメリット
- とにかく情報が少ない
- AIが平然と存在しないコードを書く
正直、こだわりが無いならバージョンダウンのほうをおすすめします。
しかし、最新版至上主義者のみなさん(私含む)のために、やり方をまとめます。
準備
MediaPipeをインストール
pip install mediapipe
OpenCVも忘れずに。
pip install opencv-python
モデルをダウンロードする
新版では、モデルファイルを自分でダウンロードする必要がある。
旧版では不要だったので、最初かなり困惑した。
# モデルをダウンロード
import urllib.request
url = "https://storage.googleapis.com/mediapipe-models/gesture_recognizer/gesture_recognizer/float16/latest/gesture_recognizer.task"
urllib.request.urlretrieve(url, "gesture_recognizer.task")
print("downloaded")
実行後、gesture_recognizer.task がダウンロードされていればOK。
認識結果を描画する
旧版で使えた mp.solutions.drawing_utils は使えなくなっている。
そこで、Google公式サンプルコードを使う。
少し改変したコードがこちら。
# 少し改変
import mediapipe as mp
import numpy as np
import cv2
mp_hands = mp.tasks.vision.HandLandmarksConnections
mp_drawing = mp.tasks.vision.drawing_utils
mp_drawing_styles = mp.tasks.vision.drawing_styles
MARGIN = 10
FONT_SIZE = 1
FONT_THICKNESS = 1
HANDEDNESS_TEXT_COLOR = (88, 205, 54)
def draw_landmarks_on_image(rgb_image, detection_result):
hand_landmarks_list = detection_result.hand_landmarks
handedness_list = detection_result.handedness
annotated_image = np.copy(rgb_image)
for idx in range(len(hand_landmarks_list)):
hand_landmarks = hand_landmarks_list[idx]
# ランドマーク描画
mp_drawing.draw_landmarks(
annotated_image,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles.get_default_hand_landmarks_style(),
mp_drawing_styles.get_default_hand_connections_style()
)
# テキスト表示位置
height, width, _ = annotated_image.shape
x_coordinates = [landmark.x for landmark in hand_landmarks]
y_coordinates = [landmark.y for landmark in hand_landmarks]
text_x = int(min(x_coordinates) * width)
text_y = int(min(y_coordinates) * height) - MARGIN
# 元の認識結果
handedness = handedness_list[idx][0].category_name
# 左右反転しているので入れ替え
if handedness == "Left":
handedness_text = "Right"
else:
handedness_text = "Left"
cv2.putText(
annotated_image,
handedness_text,
(text_x, text_y),
cv2.FONT_HERSHEY_DUPLEX,
FONT_SIZE,
HANDEDNESS_TEXT_COLOR,
FONT_THICKNESS,
cv2.LINE_AA
)
return annotated_image
後で出てくるコードで、カメラ映像を左右反転している。
そのままだと右手と左手が逆になるので、表示を入れ替えている。
カメラ映像を取得して認識する
やっていること↓
カメラ映像取得
↓
手を認識
↓
結果を描画
↓
画面表示
↓
ループ
コードはこちら。
import cv2
import mediapipe as mp
def camera_loop(cap, recognizer):
while True:
success, frame = cap.read()
if not success:
break
# 左右反転
frame = cv2.flip(frame, 1)
# BGR -> RGB
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# mediapipe.Image に変換
mp_image = mp.Image(
image_format=mp.ImageFormat.SRGB,
data=rgb
)
# 手検出
results = recognizer.recognize(mp_image)
# 検出結果描画
if results.hand_landmarks:
annotated_rgb = draw_landmarks_on_image(rgb, results)
# RGB -> BGR
frame = cv2.cvtColor(
annotated_rgb,
cv2.COLOR_RGB2BGR
)
cv2.imshow("Hand Tracking", frame)
# ESCで終了
if cv2.waitKey(1) == 27:
break
最後に実行するコード
以下を実行すれば動くはず。
import mediapipe as mp
BaseOptions = mp.tasks.BaseOptions
GestureRecognizer = mp.tasks.vision.GestureRecognizer
GestureRecognizerOptions = mp.tasks.vision.GestureRecognizerOptions
VisionRunningMode = mp.tasks.vision.RunningMode
model_path = "gesture_recognizer.task"
options = GestureRecognizerOptions(
base_options=BaseOptions(
model_asset_path=model_path
),
running_mode=VisionRunningMode.IMAGE
)
with GestureRecognizer.create_from_options(options) as recognizer:
# カメラ起動
cap = cv2.VideoCapture(0)
try:
camera_loop(cap, recognizer)
finally:
# エラー時でもちゃんと閉じる
cap.release()
cv2.destroyAllWindows()
実行すると、
- カメラ起動
- 手を認識
- ランドマーク描画
-
"Hand Tracking"ウィンドウに表示
されるはず。
おわりに
MediaPipeを試そうとしたところ、コードが動かなくて困惑した。
調べたら、仕様変更があったことは分かった。
しかし、新バージョンでどうすれば動かせるのか情報が少なくて困った。
そのために解決法をまとめた。
同じところでハマった人の助けになればうれしいです。
全体コードはこちら。