こんにちは!ソニーセミコンダクタソリューションズの明です。今回は、AIベースのフィットネスフォームガイダンスシステムをご紹介します。
2024年9月、Raspberry Pi AIカメラが発売され、エッジ環境でAIモデルをより簡単かつ手軽に導入できるようになりました。このカメラの注目すべき応用の一つが、今回ご紹介するAIベースのフィットネスフォームガイダンスシステムです。
はじめに
トレーニング中、自分の姿勢が正しいのか、動作が正確に行えているのか不安になることはよくあります。今回紹介するシステムは、AIを活用して動作を分析し、リアルタイムでフィードバックを提供します。この記事では特にスクワット運動に注目し、AIがスクワットの回数をカウントし、それぞれのスクワットが浅すぎるか、適切か、深すぎるかを判断します。
準備
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
実装について
このドキュメントでは、ポーズ推定モデルを使用してスクワット運動をカウントし、評価するシステムを紹介します。スクワットの回数を記録するだけでなく、リアルタイムで姿勢のフィードバックやアドバイスを提供することで、フォームの改善を支援します。
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
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の表示は以下の通りです:
このスクリプトは現在、体の左側のみを解析しています。対応するキーポイントを調整することで、右側や全身を評価するように変更することも可能です。このコードは、画面上にリアルタイムで視覚的なフィードバックを表示し、音声合成(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)
結果
以下にスクリプトの実行結果を示します。このシステムは、スクワットの回数をカウントするだけでなく、姿勢に関するリアルタイムのフィードバックも提供します。これにより、ユーザーはその場でフォームを修正でき、すべてのスクワットを正しく実行することで、トレーニングの効果を高めつつ、怪我のリスクを軽減することが期待されます。
まとめ
今回はRaspberry Pi 5を使用し、Raspberry Pi AIカメラとポーズ推定モデルを組み合わせることで、リアルタイムなフィットネス姿勢分析に有効かつ手軽なソリューションを構築しました。本システムはスクワットの回数を数えるだけでなく、姿勢の質も評価し、その場でフォームの修正を促します。これにより、エッジAIの個人向けフィットネス応用における実用的な可能性を示し、よりスマートなトレーニングの実現に貢献します。
困った時は
もし記事の途中でうまくいかなかった場合は、気軽にこの記事にコメントいただいたり、以下のサポートのページもご覧ください。
コメントのお返事にはお時間を頂く可能性もありますがご了承ください。
また、記事の内容以外で AITRIOS についてお困りごとなどあれば以下よりお問い合わせください。





