MediaPipeを使って骨格検出をする

# 概要
小学校や中学校で視力検査を毎年やり、年々視力が落ちてきているなと実感することが増えてきました。視力検査では、1か所だけ欠けた輪の向きに対して欠けた部分がどの向きにあるか指で示すことで視力を計測します。Maker Faire Tokyo 2024にてあるサークルではMediaPipeを使って右手を検出し、その右手の向きに合わせて欠けた輪の向きを回転させることで誰でも視力検査をA判定で合格できるという面白いものを作っている方がいました。聞いたところによると、右手を検出しているオープンソースのライブラリということで実際にどんなことができるのか試してみました。

# 目次
- [動作環境](#動作環境)
- [環境構築](#環境構築)
- [コード](#コード)
- [コードの解説](#コードの解説)
- [実行結果](#実行結果)

# MediaPipeとは
MediaPipeとは画像や動画からモーションキャプチャーのように骨格検出を行うことができるGoogleが開発したオープンソースのライブラリです。手先の位置から各関節位置を検出することができます。
# 動作環境
- Ubuntu 22.04 LTS
- MediaPipe 
- Raspberry pi 4
# 環境構築
### OpenCVのインストール
ラズパイのターミナルを開いて以下のコマンドを実行するとライブラリがインストールされます。
```bash
pip install opencv-python
pip install opencv-contrib-python
```
### MediaPipeのインストール
先ほどと同様にターミナルに以下のコマンドを実行すると`MediaPipe`のライブラリがインストールされます。
```bash
pip3 install mediapipe
```
# コード
```python
import cv2
import mediapipe as mp

# Mediapipeの初期化
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()
mp_drawing = mp.solutions.drawing_utils

# 動画キャプチャの初期化
cap = cv2.VideoCapture("mediapipe2.mp4")  # 動画ファイルを指定

if not cap.isOpened():
    print("Error: カメラまたは動画を開けませんでした。")
    exit()

# ウィンドウサイズを変更するスケール（例: 0.5 で半分の大きさ）
resize_scale = 0.5

# 保存する動画の設定
output_filename = "output_pose_video.avi"
fps = int(cap.get(cv2.CAP_PROP_FPS)) if cap.get(cv2.CAP_PROP_FPS) > 0 else 30
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) * resize_scale)  # 縮小後の幅
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) * resize_scale)  # 縮小後の高さ
fourcc = cv2.VideoWriter_fourcc(*'XVID')  # 動画のコーデック

out = cv2.VideoWriter(output_filename, fourcc, fps, (frame_width, frame_height))

while True:
    ret, frame = cap.read()
    if not ret:
        print("Error: フレームを取得できませんでした。")
        break

    # フレームの高さと幅を取得
    height, width, _ = frame.shape

    # フレームサイズを縮小
    small_frame = cv2.resize(frame, (int(width * resize_scale), int(height * resize_scale)))

    # BGRからRGBに変換（Mediapipeが必要とするフォーマット）
    frame_rgb = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)

    # Mediapipeで骨格検出を実行
    result = pose.process(frame_rgb)

    # 検出結果を描画
    if result.pose_landmarks:
        mp_drawing.draw_landmarks(
            small_frame, result.pose_landmarks, mp_pose.POSE_CONNECTIONS
        )
        # 各関節の座標を取得して出力
        for i, landmark in enumerate(result.pose_landmarks.landmark):
            x = landmark.x * width    # x座標をピクセル単位に変換
            y = landmark.y * height   # y座標をピクセル単位に変換
            z = landmark.z            # z座標（深度情報）は正規化されている
            print(f"関節 {i}: x={x:.2f}, y={y:.2f}, z={z:.2f}")

    # 縮小されたフレームを保存
    out.write(small_frame)

    # 縮小されたフレームを表示
    cv2.imshow('Pose Detection', small_frame)

    # 'q'キーで終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# リソースを解放
cap.release()
out.release()  # 保存用のVideoWriterを解放
cv2.destroyAllWindows()
print(f"保存された動画ファイル: {output_filename}")

```
# コードの解説
## モジュールの初期化
`mp.solutions.pose`は動画や画像から人体の骨格情報を検出する機能である
`mp.solutions.drawing_utils`は検出された骨格情報を動画や画像に描画する機能である
```python
mp_pose = mp.solutions.pose
mp_drawing = mp.solutions.drawing_utils
```


## 骨格情報（＝関節情報）の取得と描画
- `pose.process`で入力フレーム画像から骨格情報を検出しています。
- 取得したフレームに関節のランドマーク`results.pose_landmarks`があった場合、`mp_drawing.draw_landmarks`を用いてフレーム画像に描画します。
draw_landmarksの引数は
    -`small_frame`: 描画対象のフレーム（画像または動画の1フレーム）
    - `pose_landmarks`: 検出された骨格情報（ランドマーク）
    - `mp_pose.POSE_CONNECTIONS`: 骨格の接続関係を定義する情報
- `result.pose_landmarks.landmark`では検出された各関節位置の情報をのx, y, z座標として格納し、画面サイズに合わせたピクセル単位に変換しています。


```python
results = pose.process(image)
# 検出されたポーズを描画
# 検出された骨格情報を描画
if result.pose_landmarks:
    mp_drawing.draw_landmarks(
        small_frame, result.pose_landmarks, mp_pose.POSE_CONNECTIONS
    )
    # 各関節の座標を取得して出力
    for i, landmark in enumerate(result.pose_landmarks.landmark):
        x = landmark.x * width    # x座標をピクセル単位に変換
        y = landmark.y * height   # y座標をピクセル単位に変換
        z = landmark.z            # z座標（深度情報）は正規化されている
        print(f"関節 {i}: x={x:.2f}, y={y:.2f}, z={z:.2f}")

```


# 実行結果
以下のサイトから男性が動く動画をダウンロードし、骨格検出できるか試したところ、手先や顔の一部、関節位置などを読み取ることができた。
https://www.pexels.com/video/profile-of-a-young-man-standing-by-the-widow-5528734/

![スクリーンショット 2024-12-01 054923.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925972/b5dc5433-ef60-c63f-d306-f6fc5dac21c2.png)

YouTubeのShortsに実際に検出した際の動画をアップロードしたので見てみてください
　
https://youtube.com/shorts/S8bMV3eLb2M?feature=share