【Python】骨格推定の概要、様々な骨格推定パターンの実装

## はじめに

骨格推定（Pose Estimation）は、画像や動画から人物の関節位置を特定する技術で、スポーツ分析やリハビリテーション、人間の動作認識、AR（拡張現実）など、さまざまな分野で利用されています。リアルタイムで骨格推定を行うためには、高速で効率的な推定手法が求められます。

本記事では、代表的な骨格推定ライブラリである**MediaPipe**と**OpenPose**を比較し、その後、**MediaPipe**を使った骨格推定の実装方法を紹介します。

## 骨格推定の実装方法

骨格推定を実現するためのライブラリにはいくつかの選択肢がありますが、**MediaPipe**と**OpenPose**が代表的です。両者の特徴を比較してみましょう。

#### **MediaPipe** vs **OpenPose** 比較表

| 特徴                         | **MediaPipe**                           | **OpenPose**                        |
|----------------------------|------------------------------------|----------------------------------|
| **提供元**                   | Google                             | Carnegie Mellon University (CMU)  |
| **使用用途**                 | リアルタイムアプリ向け（モバイル、Web） | 高精度な学術・研究用途に向けた大型モデル |
| **処理速度**                 | 高速、モバイル端末でも実行可能         | 処理速度はやや遅くなる（高解像度で重くなる） |
| **精度**                     | 中程度（リアルタイム重視）             | 高精度（研究用途に適した精度）               |
| **インストールの簡便さ**      | 非常に簡単（pipでインストール可能）      | 複雑、CMakeを使って手動でビルドが必要  |
| **依存ライブラリ**             | 少ない             | 多くの依存関係（Caffe、CUDA、OpenCVなど） |
| **プラットフォーム**           | Android、iOS、Windows、Linux、Web   | Windows、Linux（MacOSは公式サポートなし） |
| **リアルタイム処理の適性**     | ◎（非常に高速、モバイル端末でも動作）  | △（精度重視だが遅くなることがある） |
| **モデルのサイズ**            | 小さめ（モデルは軽量）               | 大きめ（モデルが重い）              |

**MediaPipe**は、軽量でリアルタイム処理を重視しており、特にモバイルやWebアプリケーションに適しています。一方で、**OpenPose**は高精度な推定が可能ですが、実行速度がやや遅く、特に高解像度のデータで処理が重くなることがあります。一般的に、**精度重視**のシナリオでは**OpenPose**が、**速度重視**のシナリオでは**MediaPipe**が選ばれます。

### 骨格推定のパターン一覧

`mediapipe`には、以下のような骨格推定のパターンがあります。これらは主に「姿勢推定」「顔」「手」「足」など、用途に応じたモジュールで提供されます。

| パターン名         | 説明                                              |
|--------------------|---------------------------------------------------|
| 全身 (Pose)        | 人体全体の骨格を推定 (33個のランドマーク)           |
| 手 (Hands)         | 手の骨格を推定 (21個のランドマーク)                |
| 顔 (Face Mesh)     | 顔のランドマークを推定 (468個のランドマーク)       |
| ポーズと手の複合  | ポーズ推定と手の推定を同時に行う                   |
| ポーズと顔の複合  | ポーズ推定と顔の推定を同時に行う                   |

## 環境の準備

まず、骨格推定を行うために必要なライブラリのインストールを行います。**mediapipe**をインストールするだけで利用できます。非常にお手軽です。

```bash
pip3 install mediapipe
```

## 骨格推定を実装

### ◆ 基本的な実装
**MediaPipe**を使った骨格推定の実装方法を説明します。まずは、全身 (Pose)の骨格推定のみを実装してみます。

```python
import cv2
import mediapipe as mp

# MediaPipeのポーズ推定モジュールをインスタンス化
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)

# Webカメラを開く
cap = cv2.VideoCapture(0)

# カメラが開けない場合のエラーハンドリング
if not cap.isOpened():
    print("Error: Camera not found.")
    exit()

# 画像処理ループ
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        print("Error: Failed to grab frame.")
        break

    # BGRからRGBに変換（MediaPipeはRGB形式を期待するため）
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # MediaPipeで骨格推定を実行
    results = pose.process(rgb_frame)

    # 結果を描画
    if results.pose_landmarks:
        # 骨格のランドマークを描画
        mp.solutions.drawing_utils.draw_landmarks(
            frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)

    # 画像を表示
    cv2.imshow("Pose Estimation", frame)

    # 'q'を押すと終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 終了処理
cap.release()
cv2.destroyAllWindows()
```

#### コード解説

1. **`mediapipe`の設定**  
   `mp.solutions.pose.Pose()`を使用して、骨格推定モデルを設定します。`min_detection_confidence`と`min_tracking_confidence`は、検出と追跡の精度を調整するパラメータです。

2. **Webカメラキャプチャ**  
   `cv2.VideoCapture(0)`を使ってWebカメラから映像を取得します。`cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)`で、BGRからRGBに変換します。

3. **骨格推定の実行**  
   `pose.process(rgb_frame)`で、MediaPipeによる骨格推定を実行し、得られた結果を画面に表示します。

4. **結果の描画**  
   `mp.solutions.drawing_utils.draw_landmarks()`で、推定された骨格のランドマークを描画します。

### ◆ 様々な骨格推定パターンの実装

次に、全身 (Pose)だけでなく、様々な骨格推定パターンを実装し、コマンドライン引数で切り替えられる実装方法を示します。

```python
import cv2
import mediapipe as mp
import argparse

# 引数のパース
def parse_arguments():
    parser = argparse.ArgumentParser(description="mediapipeで骨格推定を実行")
    parser.add_argument('--pattern', type=str, default='pose', choices=['pose', 'hands', 'face', 'pose_hands', 'pose_face'],
                        help="使用する骨格推定のパターンを指定します。")
    return parser.parse_args()

def run_pose():
    mp_pose = mp.solutions.pose
    pose = mp_pose.Pose()
    return pose, mp_pose

def run_hands():
    mp_hands = mp.solutions.hands
    hands = mp_hands.Hands()
    return hands, mp_hands

def run_face_mesh():
    mp_face_mesh = mp.solutions.face_mesh
    face_mesh = mp_face_mesh.FaceMesh()
    return face_mesh, mp_face_mesh

def main():
    args = parse_arguments()
    
    # 動画の読み込み (カメラを使用)
    cap = cv2.VideoCapture(0)

    # 使用する推定モデルを選択
    if args.pattern == 'pose':
        pose, mp_pose = run_pose()
    elif args.pattern == 'hands':
        hands, mp_hands = run_hands()
    elif args.pattern == 'face':
        face_mesh, mp_face_mesh = run_face_mesh()
    elif args.pattern == 'pose_hands':
        pose, mp_pose = run_pose()
        hands, mp_hands = run_hands()
    elif args.pattern == 'pose_face':
        pose, mp_pose = run_pose()
        face_mesh, mp_face_mesh = run_face_mesh()

    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break

        # 処理を行うための画像の前処理
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        results = None

        if args.pattern == 'pose':
            results = pose.process(frame_rgb)
            mp.solutions.drawing_utils.draw_landmarks(
                image=frame,
                landmark_list=results.pose_landmarks,
                connections=mp_pose.POSE_CONNECTIONS)
            cv2.imshow('MediaPipe Pose', frame)

        elif args.pattern == 'hands':
            results = hands.process(frame_rgb)
            if results.multi_hand_landmarks:
                for hand_landmarks in results.multi_hand_landmarks:
                    mp.solutions.drawing_utils.draw_landmarks(
                        image=frame,
                        landmark_list=hand_landmarks,
                        connections=mp_hands.HAND_CONNECTIONS,
                        landmark_drawing_spec=mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
                        connection_drawing_spec=mp.solutions.drawing_styles.get_default_hand_connections_style()
                    )
            cv2.imshow("Hand Estimation", frame)

        elif args.pattern == 'face':
            results = face_mesh.process(frame_rgb)
            if results.multi_face_landmarks:
                for face_landmarks in results.multi_face_landmarks:
                    mp.solutions.drawing_utils.draw_landmarks(
                        image=frame,
                        landmark_list=face_landmarks,
                        connections=mp_face_mesh.FACEMESH_CONTOURS,
                        landmark_drawing_spec=mp.solutions.drawing_utils.DrawingSpec(thickness=1, circle_radius=1),
                        connection_drawing_spec=mp.solutions.drawing_utils.DrawingSpec(thickness=1, circle_radius=1),
                    )
                cv2.imshow('Face Estimation', frame)                

        elif args.pattern == 'pose_hands':
            # 両方のモデルを処理
            results_pose = pose.process(frame_rgb)
            mp.solutions.drawing_utils.draw_landmarks(
                image=frame,
                landmark_list=results_pose.pose_landmarks,
                connections=mp_pose.POSE_CONNECTIONS)
            cv2.imshow('MediaPipe Pose', frame)

            results_hands = hands.process(frame_rgb)
            if results_hands.multi_hand_landmarks:
                for hand_landmarks in results_hands.multi_hand_landmarks:
                    mp.solutions.drawing_utils.draw_landmarks(
                        image=frame,
                        landmark_list=hand_landmarks,
                        connections=mp_hands.HAND_CONNECTIONS,
                        landmark_drawing_spec=mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
                        connection_drawing_spec=mp.solutions.drawing_styles.get_default_hand_connections_style()
                    )
            cv2.imshow("Hand Estimation", frame)

        elif args.pattern == 'pose_face':
            # 両方のモデルを処理
            results_pose = pose.process(frame_rgb)
            mp.solutions.drawing_utils.draw_landmarks(
                image=frame,
                landmark_list=results_pose.pose_landmarks,
                connections=mp_pose.POSE_CONNECTIONS)
            cv2.imshow('MediaPipe Pose', frame) 

            results_face = face_mesh.process(frame_rgb)
            if results_face.multi_face_landmarks:
                for face_landmarks in results_face.multi_face_landmarks:
                    mp.solutions.drawing_utils.draw_landmarks(
                        image=frame,
                        landmark_list=face_landmarks,
                        connections=mp_face_mesh.FACEMESH_CONTOURS,
                        landmark_drawing_spec=mp.solutions.drawing_utils.DrawingSpec(thickness=1, circle_radius=1),
                        connection_drawing_spec=mp.solutions.drawing_utils.DrawingSpec(thickness=1, circle_radius=1),
                    )
                cv2.imshow('Face Estimation', frame)

        # 結果を表示
        cv2.imshow("Skeleton Estimation", frame)

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

    cap.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()
```

#### 実行例

コマンドラインで以下の通り実行することで、`pose`（全身）推定が実行されます。他のパターンも同様に実行できます。

```bash
python3 main.py --pattern pose
```
コマンドラインの使い方（ヘルプ）は以下のように表示します。

```bash
python3 main.py --help

# 出力結果
------------------------------------------------------------------------
usage: main.py [-h] [--pattern {pose,hands,face,pose_hands,pose_face}]

mediapipeで骨格推定を実行

options:
  -h, --help            show this help message and exit
  --pattern {pose,hands,face,pose_hands,pose_face}
                        使用する骨格推定のパターンを指定します。
------------------------------------------------------------------------

```

## おわりに

本記事では、骨格推定を行うための代表的なライブラリである**MediaPipe**と**OpenPose**を比較し、**MediaPipe**を使った実装方法を紹介しました。骨格推定は、様々な用途に応用できる技術であり、AR/VRの分野などでも活躍できる技術ですので、ぜひ勉強してみて下さい。