フィットネスのためのセンサーフュージョン: Pythonで正確なレップカウンターを作る
信頼性の低い単一センサーデータを捨てよう。相補フィルターで加速度計とジャイロスコープの信号を融合し、任意のエクササイズ向けの堅牢なレップカウンターを構築する方法を学びます。
このページの内容
フィットネスアプリはあらゆる場所にありますが、多くのアプリがかってにうんどうなどの単純な運動より複雑な運動の反復回数を正確にカウントするという基本的な課題に苦労しています。こんな課題了下のような経験はありませんか?回数を読み忘れた,或者はPhantom movementをカウントしてしまった—这种情况说明什么问题?もしそのようなアプリを使ったことがあるなら、单一センサー使用の限界を経験済みはずです。お使いの電話やウェアラブルの加速度計はノイズが多く、ジャイロスコープは回転には最適ですが、時間が経つとドリフト(狂い)が生じます。その解決策とは?センサーフュージョンです。
このディープダイブチュートリアルでは、加速度計とジャイロスコープのデータを融合させて堅牢な反復カウンターを構築します。抽象的な理論を放棄して、生のセンサー信号を処理し、クリーンな方位推定値を適用する実践的なPythonコードを作成し、 затем построим умный алгоритм подсчета.
これは开发商にとって重要です,因為對準確、即時反饋的需求正在爆發。掌握這些技術將使您能夠為可穿戴設備、移動設備和物聯網設備構建更智能、更可靠、更有價值的應用程序。
前提条件:
- Pythonの基本的な理解。
- ベクトルや角度などの概念に精通していること。
- Python 3とNumPyライブラリがインストールされていること。
問題の理解
優れたソリューションを構築するには、生の材料である加速度計とジャイロスコープの欠点をまず理解する必要があります。
すべての сучасних смартфонахにある慣性測定ユニット(IMU)は обычноこれらの2つのセンサーを組み合わせています。
-
加速度計:適切な加速度(重力の一定引力を含む)を測定します。重力の向きを使用してデバイスの向き(傾き)を把握できます。
- ** 長所 **:長期的に安定しています。重力が消えることはありません。
- 短所:外部力に非常に敏感です。突然の衝撃、振動、または動きのノイズが方位データを破損します。
-
ジャイロスコープ:角速度(デバイスが軸を中心に回転する速度)を測定します。この速度を時間で積分して方位の変化を追跡できます。
- ** 長所 **:素早い回転変化の検出に優れていて,加速度計を困らせる外部衝撃に影響されません。
- 短所:ドリフトが発生します。測定値の中の小さく避けられないエラーが時間とともに蓄積し、計算された角度が徐々に不正確になります。
片方だけに頼ると,反復カウンターは失敗します。加速度計だけのカウンターは急激な動きで誤作動し,ジャイロスコープだけのカウンターは最終的にドリフトが激しくなり、実際の方位を見失います。
ここにセンサー融合の出番があります。加速度計の長期的な安定性とジャイロスコープの短期的な精度を組み合わせて、両方の利点を活かすことができます。
前提条件
コーディングを開始する前に、環境を設定しましょう。Python 3とNumPyが必要です。
# python3とpipがインストールされていることを確認
pip install numpy
# 出力例:
# Successfully installed numpy-x.x.x
このチュートリアルをアルゴリズムに焦点を当てるために、サンプルデータセットを使用して作業します。このデータがreact-native-sensorsのようなライブラリを使用してモバイルデバイスからストリーミングされている,或者はIoTデバイスから直接取得している状況を想像できます。
ステップ1:生センサーアナライズ
まず、コードで問題を定義しましょう。ノイズのある加速度計とドリフトするジャイロスコープをシミュレートして、何に立ち向かうかを確認します。上腕二頭筋のカールでは、前腕の角度の変化が主な動作です。シンプルのために1つの軸に焦点を当てます。
何をしているか
加速度計とジャイロスコープのデータからデバイスの傾斜角度を別途計算して、個々の欠点を視覚化します。
実装
電話のIMUから(ax, ay, az, gx, gy, gz)を返すget_sensor_data()という関数があるとします。電話を持っている上腕二頭筋のカールの場合、関心のある角度はX軸周囲の回転かもしれません。
# src/data_analysis.py
import numpy as np
# dt: センサー測定間の時間差
dt = 0.01
def get_accel_angle(ax, ay, az):
"""加速度計データから傾斜角度を計算します。"""
# 注:これは1軸の簡略化された計算です。
# 完全な実装ではピッチにatan2(ay, az)などを使用します。
return np.arctan2(ay, np.sqrt(ax**2 + az**2)) * 180 / np.pi
# ジャイロスコープデータを積分する必要がある
gyro_angle_x = 0.0
def get_gyro_angle(gx, dt):
"""ジャイロスコープデータを積分して角度を計算します。"""
global gyro_angle_x
gyro_angle_x += gx * dt
return gyro_angle_x
# --- いくつかの測定値をシミュレート ---
# 上腕二頭筋のカールをシミュレート:角度が-90から0そして戻る
true_angle = -90
gyro_drift = 0.1 # 小さく一定のドリフト
accel_noise_stddev = 2.5 # 加速度計測定値のノイズ
# 結果を保存
simulated_gyro_angles = []
simulated_accel_angles = []
for _ in range(300): # 3秒間のデータをシミュレート
# スムーズなカール動作をシミュレート
true_angle += 0.6
if true_angle > 0: true_angle = 0 # 上端でキャップ
# ノイズのある加速度計データを生成
accel_reading_y = np.sin(np.deg2rad(true_angle)) + np.random.normal(0, accel_noise_stddev)
accel_reading_z = np.cos(np.deg2rad(true_angle)) + np.random.normal(0, accel_noise_stddev)
# ドリフトするジャイロスコープデータを生成
gyro_reading_x = 0.6 / dt # 理想的な角速度
gyro_reading_x += gyro_drift # ドリフトを追加
# 角度を計算
accel_angle = get_accel_angle(0, accel_reading_y, accel_reading_z)
gyro_angle = get_gyro_angle(gyro_reading_x, dt)
simulated_accel_angles.append(accel_angle)
simulated_gyro_angles.append(gyro_angle)
print(f"最終ジャイロ角度(ドリフトあり): {simulated_gyro_angles[-1]:.2f}")
print(f"最終加速度計角度(ノイズがあるが0付近であるべき): {simulated_accel_angles[-1]:.2f}")
動作の仕組み
get_accel_angle:三角法(arctan2)を使用して、重力ベクトル成分に基づく傾斜を計算します。デバイスが傾くとayとazが変化します。get_gyro_angle:積分をシミュレートします。角速度(gx)を取り、時間ステップ(dt)を掛けて角度の変化を求め、前の角度に追加します。- シミュレーション:「真の」角度を作成し、それから現実的なノイズとドリフトを追加してセンサー測定値を生みます。これは問題を明確に示します:最終的なジャイロ角度は真の値(0)から大幅にドリフトしていますが、加速度計の測定値はノイズがあります。
ステップ2:相補フィルターで信号を融合
カルマンフィルターはセンサー融合の最適解ですが、複雑です。相補フィルターは훌륭한 대안으로 매우 저렴하며优秀的替代方案,计算成本极低,モバイルや組込みシステムに最適です。
コアアイデアはシンプルです:
融合角度 = α * (ジャイロベースの角度) + (1 - α) * (加速度計ベースの角度)
ジャイロスコープを高周波数の変化に対して信頼し、安定した低周波数の加速度計データでそれを「補完」します。α(アルファ)値はバランスを決定します。一般的な値は0.98です。
何をしているか
生のセンサーデータと前の融合角度を受け取り、新しいクリーンな角度を返す関数を作成します。
実装
# src/fusion_filter.py
import numpy as np
class ComplementaryFilter:
def __init__(self, alpha=0.98):
self.alpha = alpha
self.angle = 0.0
def process(self, ax, ay, az, gx, dt):
"""
新しいセンサーデータを処理して融合角度を更新します。
"""
# 加速度計から角度を計算
accel_angle = np.arctan2(ay, np.sqrt(ax**2 + az**2)) * 180 / np.pi
# ジャイロスコープデータを積分して角度の変化を取得
gyro_angle_change = gx * dt
# 相補フィルターを適用
# 短期的な変化はジャイロスコープを信頼し、加速度計で補正
self.angle = self.alpha * (self.angle + gyro_angle_change) + (1 - self.alpha) * accel_angle
return self.angle
# --- フィルター付きでシミュレーションを再実行 ---
filter = ComplementaryFilter(alpha=0.98)
fused_angles = []
# シミュレーション変数をリセット
gyro_angle_x = 0.0
for _ in range(300):
# (ステップ1と同じシミュレーションロジックを再使用)
true_angle += 0.6
if true_angle > 0: true_angle = 0
accel_reading_y = np.sin(np.deg2rad(true_angle)) + np.random.normal(0, accel_noise_stddev)
accel_reading_z = np.cos(np.deg2rad(true_angle)) + np.random.normal(0, accel_noise_stddev)
gyro_reading_x = (0.6 / dt) + gyro_drift
fused_angle = filter.process(0, accel_reading_y, accel_reading_z, gyro_reading_x, dt)
fused_angles.append(fused_angle)
print(f"最終融合角度: {fused_angles[-1]:.2f}")
動作の仕組み
ComplementaryFilterクラスは現在の状態(self.angle)を維持します。各process呼び出しで:
- 加速度計から現在の傾斜を計算します。
- ジャイロスコープからの角度の変化を計算します。
- 投影された新しいジャイロ角度の98%取り、加速度計が測定した角度の2%を追加して、新しい融合角度を計算します。これにより、ドリフトするジャイロ角度が安定した(しかしノイズのある)加速度計角度に向かって優しく「引き寄せ」られ、時間が経つにつれてドリフトが補正されます。
出力は真の値の0に驚くほど近く、フィルターの威力を示しています。
ステップ3:状態機械で回数をカウント
クリーンな信号があれば、ついに回数をカウントできます。素朴なアプローチは角度データでピークをカウントするだけですが、これは壊れやすいです。ユーザーが中間でポーズを取ったら怎么办?
より堅牢な方法は状態機械です。上腕二頭筋カールのような運動には明確なフェーズがあります:上に上がる「アップ」フェーズと下に下がる「ダウン」フェーズ。ユーザーがこれらのフェーズを通過するにつれて状態を追跡できます。
何をしているか
状態(IDLE、GOING_UP、GOING_DOWN)を定義し、状態間の遷移をトリガーする角度しきい値を定義します。完全なサイクルが完了したときにだけカウンターをインクリメントします。
実装
# src/rep_counter.py
class RepCounter:
def __init__(self, entry_threshold, exit_threshold):
self.count = 0
self.state = "IDLE" # IDLE、GOING_UP、GOING_DOWNのいずれか
self.entry_threshold = entry_threshold # 回数を開始する角度
self.exit_threshold = exit_threshold # 回数を完了する角度
def process(self, angle):
"""
新しい角度を処理して回数を更新します。
現在の回数を返します。
"""
if self.state == "IDLE":
if angle > self.entry_threshold:
self.state = "GOING_UP"
elif self.state == "GOING_UP":
if angle < self.exit_threshold:
self.state = "GOING_DOWN"
elif self.state == "GOING_DOWN":
if angle >= self.exit_threshold:
self.count += 1
print(f"反復{self.count}が検出されました!")
self.state = "IDLE"
return self.count
# --- すべてを一緒に実行 ---
# 上腕二頭筋のカールでは、腕が45度を通過すると回数が始まり、
# 上端(例:140度)近くで終わります。
# 融合角度空間をしきい値を定義します。
# 融合角度が開始時に~0、ピーク時に~90を想定。
rep_counter = RepCounter(entry_threshold=30.0, exit_threshold=75.0)
# 2回の完全な回りをシミュレート
full_motion_fused_angles = fused_angles + list(reversed(fused_angles)) # 1回
full_motion_fused_angles += full_motion_fused_angles # 2回
for angle in full_motion_fused_angles:
rep_counter.process(angle)
print(f"最終的回数: {rep_counter.count}")
動作の仕組み
- 初期化:カウンターは0回で
IDLE状態で始まります。2つの角度しきい値を定義します。 IDLEからGOING_UPへ:角度が初めてentry_threshold交差するとき、回数が始まったことがわかります。状態をGOING_UPに変更します。GOING_UPからGOING_DOWNへ:ユーザーが動作のピークに達して下り始めるにつれて、角度が減少してexit_threshold下り坂で交差します。状態をGOING_DOWNに変更します。GOING_DOWNからIDLEへ:ユーザーが開始位置に戻り、角度がexit_thresholdの上に再び上がるとき、完全な反復が正常に完了したことがわかります。回数をインクリメントし、次の準備ができたIDLEに状態をリセットします。
このロジックは単純なピークカウントよりポーズや小さな急激な動きに対してずっと回復力があります。
他の運動への適応
このフレームワークの利点は適応性です。
-
ケトルベル Swing:ここでは、電話がポケットにある可能性があります。主な動作は強力な腰のヒンジで、結果として前方向加速度(
az)と角速度に循環パターンが生成されます。同じ融合と状態機械ロジックを胴体または太ももの融合ピッチ角度に適用して、Swingのピークとトラフを検出できます。 -
Squats:ケトルベルSwingと同様に、電話の垂直方向の動きとピッチ角度が回数をカウントするためのクリーンで循環的な信号を提供します。しきい値を調整するだけです。
パフォーマンスの考慮事項
- 相補フィルター:これは非常に軽量です。センサー測定ごとに数回の乗算と加算だけで、モバイルデバイスやマイコンでのリアルタイム処理に最適です。
- カルマンフィルター:完全なカルマンフィルターの 구현はより多くの計算コストかかり、行列演算 때문입니다。より正確ですが、単純な回数カウントには性能のトレードオフ不值得かもしれません。VR/ARやロボティクスのようなアプリケーションに非常に正確な方位が必要なときに使用してください。
代替アプローチ
- カルマンフィルター:述べたように、これはゴールドスタンダードです。システムの状態の予測モデルを使用し、ノイズの処理に優れています。Pythonの
pykalmanのようなライブラリが役に立ちますが、効果的に調整するには線形代数をよく理解している必要があります。 - 機械学習/ディープラニング:ラベル付きセンサーデータでニューラルネットワークを訓練して、動きを分類し、回数をカウントできます。このアプローチは非常にパワフルで、手動のしきい値調整なしで微妙な運動を認識できます。ただし、大量の質の高いデータセットと推論により多くの処理能力が必要です。
結論
ノイズが多くて信頼性の低いセンサー測定値から、スマートで状態ベースの反復カウンターへの変換に成功しました。個々のセンサーの弱点を理解し、シンプルで効果的な相補フィルターでそれらを軽減することで、クリーンな信号を作成しました。次に、その信号を使用して、より単純なアルゴリズムを騙す小さなジッターとポーズを無視して、丈夫に反復回数をカウントする状態機械を動かしました。
この基盤は、洗練されたフィットネスアプリケーションを構築するための出発点です。これでワークアウトを確実하게追跡し、フォームに関するリアルタイムフィードバックを提供し、より興味深く正確なユーザー体験を作成できます。
次のステップ:
- お電話からの実際のセンサーデータで試してください。
- Squatsや腕立て伏せなどの別の運動もしきい値と軸を調整してください。
- 正確さの比較のためにカルマンフィルターの実現可能性を探索してください。
リソース
クイックリファレンス
| 側面 | 重要な情報 |
|---|---|
| トピック | 開発 |
| 関連性 | 患者教育と意識向上 |
| アクション | 医療提供者との協議 |
| 頻度 | ケアチームの推奨に応じて |
| エビデンス | 現在の臨床ガイドラインに基づく |
よくある質問
Q: これが自分に該当するものかどうかをどうやってわかりますか? A: 個人的な病歴と家族歴について医療提供者と相談して、個人的なリスク要因を評価し、適切な次のステップを決定してください。
Q: 医師に何を聞くべきですか? A: 良い質問には次のようなものがあります:「私の結果はどういう意味ですか?」「私オプションは何ですか?」「どんなライフスケールの変更が役立ちますか?」「いつフォローアップする必要がありますか?」
Q: 役立つライフスケールの変更はありますか? A: ほとんどの健康状態は基本的な措置の恩恵を受けます:バランスの取れた栄養、定期的な運動(週150分以上)、質の高い睡眠(7〜9時間)、ストレス管理、タバコの回避。
医療免责声明
この記事は教育目的のみを目的としており、医療アドバイスを提供ものではありません。健康、治療、またはケアに関する決定する前に、常に資格のある医療提供者にご相談ください。個々の状況は異なり、1人に適切なものが別の人に適切ではない場合があります。
医学的緊急時に遭遇した場合は、直ちに911(またはお住まいの地域の緊急電話番号)に連絡してください。