2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Raspberry Pi × AIカメラで姿勢推定!スクワットフォームをリアルタイム評価

2
Last updated at Posted at 2025-08-20

こんにちは!ソニーセミコンダクタソリューションズの明です。今回は、AIベースのフィットネスフォームガイダンスシステムをご紹介します。

2024年9月、Raspberry Pi AIカメラが発売され、エッジ環境でAIモデルをより簡単かつ手軽に導入できるようになりました。このカメラの注目すべき応用の一つが、今回ご紹介するAIベースのフィットネスフォームガイダンスシステムです。

はじめに

トレーニング中、自分の姿勢が正しいのか、動作が正確に行えているのか不安になることはよくあります。今回紹介するシステムは、AIを活用して動作を分析し、リアルタイムでフィードバックを提供します。この記事では特にスクワット運動に注目し、AIがスクワットの回数をカウントし、それぞれのスクワットが浅すぎるか、適切か、深すぎるかを判断します。

gymcrop2-1.5xmosaic.gif

準備

AIカメラの接続とセットアップ方法については、以下のドキュメントをご参照ください:
https://www.raspberrypi.com/documentation/accessories/camera.html#install-a-raspberry-pi-camera
https://www.raspberrypi.com/documentation/accessories/ai-camera.html#ai-camera
接続が正しいことを確認するために、サンプルスクリプトを実行できます。ポーズ推定のサンプルを試すには、次のコードを実行してください:

rpicam-hello -t 0s --post-process-file /usr/share/rpi-camera-assets/imx500_posenet.json --viewfinder-width 1920 --viewfinder-height 1080 --framerate 30

結果はこちらになります:
mosaic_20250609113255.png

実装について

このドキュメントでは、ポーズ推定モデルを使用してスクワット運動をカウントし、評価するシステムを紹介します。スクワットの回数を記録するだけでなく、リアルタイムで姿勢のフィードバックやアドバイスを提供することで、フォームの改善を支援します。

AIカメラのポーズ推定スクリプト

ポーズ推定には、AIカメラのセットアップ時にインストールされるサンプルモデルの1つである higherhrnet_coco.rpk を使用します。モデルのリストは以下のディレクトリにあります:
/usr/share/imx500-models/imx500_network_higherhrnet_coco.rpk directory.

ポーズ推定コードについては、Raspberry Piのサンプル物体認識スクリプトを参考にしています。まず、以下のコマンドでスクリプトをクローンしてください。

git clone https://github.com/raspberrypi/picamera2.git

以下のコマンドで動作を確認できます:

python picamera2/examples/imx500_pose_estimation_higherhrnet_demo.py

結果は以下の通りです:
mosaic_20250609113336.png

AIベースのフィットネスフォームガイダンスシステム

このPythonスクリプトは、Sony IMX500 AIカメラを搭載したRaspberry Pi 5上で動作するように設計されており、姿勢推定モデル「HigherHRNet」を使用して、スクワットの姿勢をリアルタイムで検出・評価します。カメラから取得した主要な関節の位置を分析し、スクワットが浅すぎるか、適切か、深すぎるかを判断し、有効なスクワットを自動的にカウントします。

フィットネストレーニング、特に自宅やコーチのいない環境では、次のような疑問がよくあります:

  • 正しくスクワットできているのか?

  • 正しいフォームで何回できたのか?

誤ったフォームはトレーニング効果を下げ、怪我のリスクを高める可能性があります。そこで、このAIベースのソリューションが活躍します。

姿勢判定方法

このコードは、IMX500カメラとHigherHRNetベースのモデルを使用して人物のポーズを検出します。股関節・膝・足首の下半身の関節角度を分析し、スクワットの深さを評価します。

実装は以下の通りです:

def calculate_angle(a, b, c):
    """Calculate angle between three points: hip - knee - ankle"""
    ab = np.array(b) - np.array(a)
    bc = np.array(c) - np.array(b)

    norm_ab = np.linalg.norm(ab)
    norm_bc = np.linalg.norm(bc)

    if norm_ab == 0 or norm_bc == 0:
        return 0

    cosine_angle = np.dot(ab, bc) / (norm_ab * norm_bc)
    angle = 180 - np.degrees(np.arccos(np.clip(cosine_angle, -1.0, 1.0)))
    #print("Angle:", angle)
    return angle


squat_angle_list = []
last_feedback_text = []
feedback_timer = 0
squat_count = 0

角度が計算されると、スクワットの深さは次のように分類されます:

Average Knee Angle Posture Feedback
> 110° Squat too shallow
65° – 110° Good squat (ideal form)
< 65° Squat too deep

UIの表示は以下の通りです:

  • Angle:96° , システムは「Good Squat」と判定します
    mosaic_20250609113510.png

  • Angle:126° , システムは「Squat too shallow」と判定します
    mosaic_20250609113534.png

  • Angle:61° , システムは「Squat too deep」と判定します
    mosaic_20250609113409.png

このスクリプトは現在、体の左側のみを解析しています。対応するキーポイントを調整することで、右側や全身を評価するように変更することも可能です。このコードは、画面上にリアルタイムで視覚的なフィードバックを表示し、音声合成(espeak)によって即時のガイダンスを提供します。また、有効なスクワットを自動的にカウントし、その回数を画面に表示します。

def analyze_squat_posture(keypoints):
    """Analyze squat posture and determine whether it's correct."""
    global prev_knee_y, prev_hip_y, last_squat_keypoints, last_knee_min_y, squat_angle_list, squat_count
    feedback = []

    # Extract left leg keypoints (COCO format: 11 - hip, 13 - knee, 15 - ankle)
    hip = keypoints[11][:2]
    knee = keypoints[13][:2]
    ankle = keypoints[15][:2]

    # Confidence scores
    hip_conf = keypoints[11][2]
    knee_conf = keypoints[13][2]
    ankle_conf = keypoints[15][2]

    # Skip if confidence is too low
    if hip_conf < 0.2 or knee_conf < 0.2 or ankle_conf < 0.2:
        return []

    hip_y = hip[1]
    hip_x = hip[0]
    knee_y = knee[1]
    knee_x = knee[0]
    ankle_x = ankle[0]

    horizontal_knee_ankle_dist = abs(knee_x - ankle_x)
    #print("knee_ankle",horizontal_knee_ankle_dist)
    horizontal_hip_knee_dist= abs(hip_x - knee_x)
    #print("knee_hip",horizontal_hip_knee_dist)
    # Initialize previous frame values
    if prev_knee_y is None or prev_hip_y is None:
        prev_knee_y = knee_y
        prev_hip_y = hip_y
        return []

    # Calculate current frame angle
    current_angle = calculate_angle(hip, knee, ankle)

    # Detect squat start or in squat phase
    if hip_y > prev_hip_y + 20 or horizontal_knee_ankle_dist > 23:
        # Still squatting ? accumulate angles
        squat_angle_list.append(current_angle)
        last_squat_keypoints = keypoints.copy()
        last_knee_min_y = knee_y

    # Detect rising from squat (angle increasing, knee rising)
    elif last_squat_keypoints is not None and knee_y < last_knee_min_y - 10:
        # Process squat angle list
        if len(squat_angle_list) >= 3:
            # Get smallest 3 angles and average
            sorted_angles = sorted(squat_angle_list)[:3]
            avg_angle = sum(sorted_angles) / len(sorted_angles)
        else:
            avg_angle = current_angle  # fallback if too few data

        #print(f"Avg squat angle (min 3): {avg_angle:.2f}")

        # Feedback based on average squat angle
        if avg_angle > 110:
            feedback.append("Squat too shallow")
            os.system('espeak "too shallow" &')
            squat_count += 1
        elif avg_angle < 65:
            feedback.append("Squat too deep")
            os.system('espeak "too deep" &')
            squat_count += 1
        else:
            feedback.append("Good squat!")
            os.system('espeak "Good" &')
            squat_count += 1

        print("Final Squat Feedback:", feedback)
        #squat_count += 1
        # Reset state
        last_squat_keypoints = None
        last_knee_min_y = float('inf')
        squat_angle_list = []  # clear angle history for next squat

    # Update previous Y values
    prev_knee_y = knee_y
    prev_hip_y = hip_y

    return feedback

スクワット角度の計算と画面への描画

この関数は、カメラ映像上にスクワット検出の結果を可視化し、角度の計算方法や画面への描画方法を説明します。人物のキーポイントを解析してスクワット姿勢を評価し、フィードバックを表示します。また、股関節・膝・足首の位置を用いてスクワット角度を計算し、スクワットの回数や姿勢角度をリアルタイムのガイダンスとともに映像上に重ねて表示します。

def ai_output_tensor_draw(request: CompletedRequest, boxes, scores, keypoints, stream='main'):
    global feedback_timer, last_feedback_text
    """Draw detection results and feedback on screen."""
    with MappedArray(request, stream) as m:
        if boxes is not None and len(boxes) > 0:
                        # Analyze squat and get feedback + angle
            feedback = analyze_squat_posture(keypoints[0])  # Only analyze the first person

            if feedback:
                last_feedback_text = feedback
                feedback_timer = 200  # display for 20 frames (~2 seconds if 10fps)
            # Visualize keypoints + feedback
            drawer.annotate_image(
                m.array, boxes, scores, np.zeros(scores.shape), keypoints,
                args.detection_threshold, args.detection_threshold,
                request.get_metadata(), picam2, stream
            )

            # Calculate squat angle and draw on screen (left leg)
            if keypoints[0][11][2] > 0.2 and keypoints[0][13][2] > 0.2 and keypoints[0][15][2] > 0.2:
                hip = keypoints[0][11][:2]
                knee = keypoints[0][13][:2]
                ankle = keypoints[0][15][:2]
                angle = calculate_angle(hip, knee, ankle)

                # Draw squat angle near the knee point

                angle_text = f"Angle: {int(angle)}"
                knee_point = tuple(map(int, knee))
                cv2.putText(m.array, angle_text, (knee_point[0] + 10, knee_point[1]),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2, cv2.LINE_AA)

            text = f"Squat Count: {squat_count}"
            text_size, _ = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 1.0, 2)
            text_x = m.array.shape[1] - text_size[0] - 10  # Right-aligned
            text_y = 30
            cv2.putText(m.array, text, (text_x, text_y),
                        cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2, cv2.LINE_AA)

            if last_feedback_text:
                for idx, line in enumerate(last_feedback_text):
                    cv2.putText(m.array, line, (10, 30 + 30 * idx),
                                cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2, cv2.LINE_AA)

結果

以下にスクリプトの実行結果を示します。このシステムは、スクワットの回数をカウントするだけでなく、姿勢に関するリアルタイムのフィードバックも提供します。これにより、ユーザーはその場でフォームを修正でき、すべてのスクワットを正しく実行することで、トレーニングの効果を高めつつ、怪我のリスクを軽減することが期待されます。

gymcrop2-1.5xmosaic.gif

まとめ

今回はRaspberry Pi 5を使用し、Raspberry Pi AIカメラとポーズ推定モデルを組み合わせることで、リアルタイムなフィットネス姿勢分析に有効かつ手軽なソリューションを構築しました。本システムはスクワットの回数を数えるだけでなく、姿勢の質も評価し、その場でフォームの修正を促します。これにより、エッジAIの個人向けフィットネス応用における実用的な可能性を示し、よりスマートなトレーニングの実現に貢献します。

困った時は

もし記事の途中でうまくいかなかった場合は、気軽にこの記事にコメントいただいたり、以下のサポートのページもご覧ください。
コメントのお返事にはお時間を頂く可能性もありますがご了承ください。

また、記事の内容以外で AITRIOS についてお困りごとなどあれば以下よりお問い合わせください。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?