万歩計アプリで歩行ルートに Kalman + RTS smoother を入れた話

| 開発記録 | 万歩計

タグ: #GPS #信号処理 #Flutter

GPS で取得した位置情報は揺らぎが大きく、地図に描くとガタガタの軌跡になります。Kalman フィルタ + RTS smoother で「自然な歩行ルート」に整形する話です。

生の GPS は思った以上にノイジー

位置情報を 1 秒ごとに取得して地図に描くと、特に都市部や室内では、本来直線で歩いているはずの場所がジグザグの軌跡として描かれます。GPS の精度誤差は数 m〜数十 m あるので、これは避けられません。

そのままユーザーに見せると「自分こんなに千鳥足で歩いてた…?」という体験になります。

Kalman フィルタを採用

位置推定の鉄板である Kalman フィルタを採用しました。各時点の観測値と、その精度(GPS の accuracy)を使い、状態(実際の位置・速度)を更新していくアルゴリズムです。

Flutter から呼ぶ部分は Dart で実装し、状態行列・観測行列をフレームごとに更新しています。

スカラー版の Kalman 更新だけ取り出すと、本質は「予測」と「更新」の 2 行に集約されます。一次元の擬似コードは下記の通り。

class Kalman1D {
  Kalman1D({required this.processNoise, required this.measurementNoise})
      : _x = 0,
        _p = 1;

  final double processNoise;     // Q
  final double measurementNoise; // R
  double _x; // 推定状態
  double _p; // 推定誤差共分散

  double update(double measurement) {
    // Predict (今回はモデルが恒等なので x はそのまま)
    _p = _p + processNoise;

    // Update
    final k = _p / (_p + measurementNoise); // カルマンゲイン
    _x = _x + k * (measurement - _x);
    _p = (1 - k) * _p;
    return _x;
  }
}

RTS smoother で後処理

Kalman フィルタは「過去から現在」までの情報で推定するため、リアルタイム表示には向きます。一方、「記録された軌跡を後から見る」用途では、未来の情報も使ったほうが滑らかになります。

RTS(Rauch-Tung-Striebel)smoother は、Kalman フィルタの結果を時間方向に再パスして、過去・未来両方の情報で軌跡を整える手法です。これで地図上の軌跡が劇的に滑らかになりました。

stationary → vehicle 遷移問題

停止状態(stationary)から移動状態(vehicle / walking)への遷移検出も改善しました。停止中に GPS のノイズで「動いた」と誤検知されると、移動モードに切り替わって変な軌跡が記録されます。

軌跡整合性チェックを入れ、ノイズによる scatter(散らばり)を検出してフィルタするようにしました。

バッテリー改善

位置情報を 1 秒ごとに取り続けると、バッテリーがどんどん減ります。

  • iOS では SLC(Significant Location Change)+ CLVisit を使い、必要な時だけ高精度取得
  • distanceFilter を動的に変化させ、停止中は粗く、移動中は細かく
  • DB への書き込みを batch insert 化(FK violation 問題で一度 revert したが、再度修正してリリース)

地図のデバッグ点表示

開発中、生の GPS 点と Kalman 推定値を地図上に重ねて表示するデバッグ機能を作りました。設定で表示の段階を切り替えられ、精度(accuracy)の円も可視化しています。

この設定を永続化しておくと、デバッグセッションの間も保持されて捗ります。

教訓

  • 生の GPS は使わない、必ず何らかの平滑化を入れる
  • リアルタイムは Kalman、後処理は RTS smoother
  • バッテリーは「必要な時に細かく、不要な時は粗く」の原則

位置情報アプリは、地味な信号処理の蓄積でユーザー体験が大きく変わります。