mediapipeを使って野球のバッティングフォームを解析してみた

# はじめに
[mediapipe](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker?hl=ja)という姿勢認識ライブラリを使って、野球のバッティングフォームを解析してみました。何が正解かというのは難しいですが、子どものバッティングフォームとMLBの選手のバッティングフォームを比較して何がどう違うのかというのを考察してみようと思います。

# ライブラリインストール
```
!pip install opencv-python mediapipe
```

# スクリプト
左右の肩の位置、左右の腰の位置、左右の膝の位置、左右の足首の位置、からそれぞれの距離を出し、時系列でプロットするスクリプトを作りました。swing.MOVというファイルを読み込んでグラフ化するようにしています。

``` Python
import cv2
import mediapipe as mp
import numpy as np
import matplotlib.pyplot as plt

# Mediapipeの設定
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()
mp_drawing = mp.solutions.drawing_utils

def calculate_distance(a, b):
    # a, b are landmark points with x, y values
    return np.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)

def process_video_and_plot_distances(video_path):
    video = cv2.VideoCapture(video_path)
    fps = video.get(cv2.CAP_PROP_FPS)
    distances = {
        "shoulders": [],
        "hips": [],
        "knees": [],
        "ankles": []
    }
    time_stamps = []  # 各フレームのタイムスタンプを保持するリスト
    frame_idx = 0

    while True:
        ret, frame = video.read()
        if not ret:
            break

        # フレームの処理
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        result = pose.process(frame_rgb)

        if result.pose_landmarks:
            landmarks = result.pose_landmarks.landmark

            # 左右のキーポイントを取得
            left_shoulder = landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value]
            right_shoulder = landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value]
            left_hip = landmarks[mp_pose.PoseLandmark.LEFT_HIP.value]
            right_hip = landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value]
            left_knee = landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value]
            right_knee = landmarks[mp_pose.PoseLandmark.RIGHT_KNEE.value]
            left_ankle = landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value]
            right_ankle = landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value]

            # 距離を計算して保存
            distances["shoulders"].append(calculate_distance(left_shoulder, right_shoulder))
            distances["hips"].append(calculate_distance(left_hip, right_hip))
            distances["knees"].append(calculate_distance(left_knee, right_knee))
            distances["ankles"].append(calculate_distance(left_ankle, right_ankle))
            time_stamps.append(frame_idx)  # タイムスタンプを計算して追加

        frame_idx += 1

    video.release()

    # プロット
    plt.figure(figsize=(10, 6))
    plt.plot(time_stamps, distances["shoulders"], label='Shoulders', color='blue')
    plt.plot(time_stamps, distances["hips"], label='Hips', color='green')
    plt.plot(time_stamps, distances["knees"], label='Knees', color='orange')
    plt.plot(time_stamps, distances["ankles"], label='Ankles', color='red')
    plt.xlabel('Frame')
    plt.ylabel('Distance')
    plt.title('Distance Between Left and Right Keypoints Over Time')
    plt.legend()
    plt.grid()
    plt.show()

# 動画パスを指定して実行
video_path = "swing.MOV"
process_video_and_plot_distances(video_path)

```
# 結果
子どものスイングとMLB選手（アーロン・ジャッジ）のスイングを数値化して比較してみました。

## 子どものスイング（以下は静止画）
![スクリーンショット 2024-10-09 17.03.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/ebe9c150-8546-abe2-4bf7-d3e2152b902b.png)


子どものスイングで、左右の肩の距離、腰の距離、膝の距離、足首の距離が時間軸でどう変化していくかをプロットしてみました。
![swing1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/87a41cb1-befa-baad-ea7a-092d4168d6c8.png)

- 最初にかなりひねって膝左右・肩左右・腰左右の距離が短くなっていく様子がわかると思います。その後それらが開いていく
- 肩の動きがすごく不安定

## アーロン・ジャッジのスイング

https://www.youtube.com/watch?v=5chQXBe4Bfc

同じく、左右の方の距離、腰の距離、膝の距離が時間軸でどう変化していくかをプロットしてみました。スローモーションの動画だったので、タイミングのスケールは比較できませんが、どのように変化していくかに注目します。

![judge_swing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/44e2881e-37fe-e86b-d45e-44afe5e2a83d.png)

- 膝左右・肩左右・腰左右の距離がそれほど極端に短くならず、スイングしている
- スイング後に肩と腰が正面を向くので値が小さくなる
- アーロン・ジャッジはあまり足を閉じずに足を上げて、そのまま降ろしてスイングしている

# 考察
なんとなくですが、膝、肩、腰を最初はあまりひねらず、安定させてたままスイングすることで、バットの軌道が安定するのかな、と思いました。こういうものを数値化してみることで、どのようにアドバイスするかがわかりますね。一流選手と比較して、真似てみるのがまずはいいと思います。