注釈
こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。
参加する理由
専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。
学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。
限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。
特別割引: 最新製品を特別割引でお楽しみいただけます。
季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。
👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!
8. スクワットカウンター
1. 概要
前の章では、基本的な人体姿勢推定を実装しました。 この章では、その基礎の上に MediaPipe Pose を用いた シンプルな スクワットカウンター を実装します。
これは次の要素を組み合わせた実用的な例です:
姿勢検出
動作認識
リアルタイムカウント
スマートフィットネスシステム、 ホームトレーニングアシスタント、 または動作解析アプリケーションに活用できます。
2. 動作の仕組み
スクワットカウンターは、次のロジックで実装されています:
MediaPipe Pose を使用して 33 個の身体キーポイントを検出する
主要な関節(肩・腰・足首)を選択する
正規化された y 座標を使って腰の高さを推定する
上側と下側の閾値を設定する(例:0.55 と 0.45)
シンプルな状態遷移で 「立つ → しゃがむ → 立つ」 を検出する
スクワット 1 回分の動作が完了したらカウンタを増やす
スクワット回数と現在の腰位置を画面に表示する
注釈
このサンプルでは関節角度の計算は使用しません。
計算量を減らすため、正規化座標を利用しています。
この方法は軽量で、Raspberry Pi に適しています。
3. コードの実行
重要
開始する前に、次の項目を確認してください:
パンチルトが組み立てられている
Raspberry Pi のデスクトップにアクセスできる
コードパッケージがインストールされている
Fusion HAT+ がインストールおよび設定されている
OpenCV がインストールされている
詳細な手順については 0. OpenCV のセットアップ を参照してください。
ターミナルを開き、次のコマンドを入力します:
sudo python3 ~/ai-lab-kit/mediapipe/mp_pose_squat.py
プログラムを実行すると、「Show Video」というタイトルのウィンドウが開き、ライブカメラ映像が表示されます。
カメラの前に人が立つと:
MediaPipe Pose が 33 個の身体ランドマークをリアルタイムで検出します。
全身骨格が画面上に描画されます。
システムは腰の相対位置(HipRel)を継続的に計算します。
スクワットを行うと:
体を下げて腰が下側の閾値(DOWN_TH)を超えると、 システムは「しゃがみ込み位置」に入ったと判断します。
そこから再び立ち上がり、腰が上側の閾値(UP_TH)を超えると、 スクワット回数が 1 増加します。
画面には次の情報が表示されます:
Squats: N— 完了したスクワットの総回数HipRel: value— 判定に使用している現在の正規化腰位置
カウンタは、完全な 1 サイクル (立つ → しゃがむ → 立つ) が完了した後にのみ増加するため、重複カウントを防げます。
qを押すとプログラムを終了できます。 カメラは停止し、OpenCV ウィンドウは自動的に閉じます。
4. 完全なコード
以下がスクワットカウンターの完全な実装です:
from picamera2 import Picamera2, Preview
import cv2
import mediapipe.python.solutions.pose as mp_pose
import mediapipe.python.solutions.drawing_utils as drawing
import mediapipe.python.solutions.drawing_styles as drawing_styles
# Initialize the Pose model
pose = mp_pose.Pose(
static_image_mode=False,
model_complexity=1,
enable_segmentation=True,
)
# ---- Count and threshold ----
squat_count = 0
in_bottom = False
DOWN_TH = 0.55 # Hip relative position > 0.55 is considered "full squat"
UP_TH = 0.45 # Hip relative position < 0.45 is considered "stand up"
# Open the camera
picam2 = Picamera2()
config = picam2.create_preview_configuration(
main={"size": (640, 480), "format": "XRGB8888"} ,
)
picam2.configure(config)
picam2.start()
print("Streaming... press 'q' to quit")
while True:
frame_bgra = picam2.capture_array() # XRGB8888 to BGRA
frame_bgr = cv2.cvtColor(frame_bgra, cv2.COLOR_BGRA2BGR)
# Convert the frame from BGR to RGB (required by MediaPipe)
frame_rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
# Process the frame for pose detection and tracking
results = pose.process(frame_rgb)
# Convert the frame back from RGB to BGR (required by OpenCV)
frame = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)
# If pose is detected, draw landmarks and connections on the frame
if results.pose_landmarks:
drawing.draw_landmarks(
frame,
results.pose_landmarks,
mp_pose.POSE_CONNECTIONS,
landmark_drawing_spec=drawing_styles.get_default_pose_landmarks_style(),
)
# Count squat without using hip angle
lms = results.pose_landmarks.landmark
# left 11-23-27 (shoulder, hip, ankle)
# right 12-24-28 (shoulder, hip, ankle)
idx_sets = [(11,23,27), (12,24,28)]
hip_rel_list = []
for sh, hp, an in idx_sets:
try:
y_sh, y_hp, y_an = lms[sh].y, lms[hp].y, lms[an].y
base = abs(y_an - y_sh) # Distance between shoulder and ankle
if base > 1e-6:
hip_rel = (y_hp - y_sh) / base # Position of hip relative to shoulder, 0.5 means hip is in the middle, 0 means hip is at the top, 1 means hip is at the bottom
hip_rel_list.append(hip_rel)
except IndexError:
pass
if hip_rel_list:
hip_rel = min(hip_rel_list) # Choose the smaller one, which is more stable
# State machine:
# from low -> mark "in_bottom";
# from back to high -> count +1
if not in_bottom and hip_rel >= DOWN_TH:
in_bottom = True
elif in_bottom and hip_rel <= UP_TH:
squat_count += 1
in_bottom = False
# Display
cv2.putText(frame, f"Squats: {squat_count}", (20, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.3, (0, 0, 255), 3, cv2.LINE_AA)
cv2.putText(frame, f"HipRel: {hip_rel:.2f}", (20, 90),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2, cv2.LINE_AA)
# Display the frame with annotations
cv2.imshow("Show Video", frame)
# Exit the loop if 'q' key is pressed
if cv2.waitKey(1) & 0xff == ord('q'):
break
# Release the camera
picam2.stop_preview()
picam2.stop()
cv2.destroyAllWindows()
スクリプトを実行すると、システムは次の処理を行います:
人体骨格を検出する
腰の相対位置を計算する
「しゃがむ → 立つ」の完全な動作サイクルが完了すると +1 する
画面上に Squats: N と現在の HipRel 値をリアルタイムで表示する
5. 座標と状態設計
ここでは次の 6 つのキーポイント(左右 3 点ずつ)を使用します:
Keypoint |
Index |
Description |
|---|---|---|
Shoulder |
11 (Left) / 12 (Right) |
上側の基準点 |
Hip |
23 (Left) / 24 (Right) |
スクワット位置計算の中心となる点 |
Ankle |
27 (Left) / 28 (Right) |
下側の基準点 |
Hip Relative 値の計算式:
hip_rel が大きいほど、腰は地面に近い位置にあります(つまり深くしゃがんでいる状態)。
hip_rel が小さいほど、立ち上がった姿勢に近くなります。
ここでは 2 つの閾値を定義します:
DOWN_TH = 0.55: スクワットの最下部に入ったとみなす
UP_TH = 0.45: 立ち上がり動作に戻ったとみなす
信頼性の高いカウントのために、シンプルな状態機械を使用します:
if hip_rel >= DOWN_TH:
in_bottom = True
if in_bottom and hip_rel <= UP_TH:
squat_count += 1
in_bottom = False
6. パラメータ調整と最適化
Parameter |
Description |
Adjustment Suggestion |
|---|---|---|
DOWN_TH |
スクワット判定の閾値 |
値を高くすると、より深くしゃがまないとカウントされない |
UP_TH |
立ち上がり判定の閾値 |
値を低くすると、よりしっかり立ち上がらないと判定されない |
model_complexity |
Pose モデルの複雑度 |
高速化のため 1 を推奨 |
Resolution |
フレームレートと精度に影響する |
640×480 を推奨 |
Tip
身長や体型が異なる人に対しては、適応型閾値や個別キャリブレーションを使用すると、より正確なカウントが可能になります。
5. トラブルシューティング
カウントが不正確
スクワット回数が正確にカウントされない場合、閾値があなたの体の位置やカメラ角度に合っていない可能性があります。
hip_relをリアルタイムで表示しながら、DOWN_THとUP_THを調整してください。 また、スクワット動作が安定しており、はっきり見えるようにしてください。人物が検出されない
人体が検出されない場合は、照明条件を改善し、複雑な背景を避けてください。
全身がフレーム内に入っていること、そしてカメラに正面を向いていることを確認してください。
遅延が大きい
動画の応答が遅い場合は、
model_complexityを 1 に下げ、カメラ解像度も下げてください(例:640×480 または 320×240)。不要なバックグラウンドプログラムを終了すると、パフォーマンスが改善します。
6. まとめ
Pose キーポイント + 状態機械を用いた リアルタイムスクワットカウンター を実装しました
複雑な角度計算は不要で、動作効率が高いです
Raspberry Pi などのエッジデバイスに適しています
今後はさらに次のような拡張が可能です:
腕立て伏せ / 腹筋の検出
データ記録と可視化
自動リズムガイドやトレーニングフィードバック