「電車に乗っていないのに電車判定」— GPS ジッターと軌跡の直線性で戦う
| 開発記録 | 位置情報
タグ: #GPS #センサー #アルゴリズム #Flutter #個人開発
自宅にいるだけなのに「電車で移動中」と誤判定される。GPS ジッター起因の行動認識バグに対し、軌跡の直線性(直線距離÷経路距離)という指標で散らばりを検出する対策を入れました。大きな修正が即日 Revert された話も含めた、センサーフュージョンの実録です。
家にいるのに「電車で移動中」
行動を自動認識するアプリを運用していると、ユーザーの生活を GPS がどれだけ嘘つきに描くかを思い知ります。象徴的だったのが「自宅にいるだけなのに 自宅→電車→自宅 を繰り返す」誤判定でした。
原因は GPS ジッターです。屋内では測位が数十〜数百 m 単位で飛び、点列だけ見ると高速移動に見える瞬間がある。逆の問題もあって、実際に電車を降りたのに「電車のまま」から抜けられないケースもありました。
最初の修正は即日 Revert された
まず状態遷移ロジックを大きく直す修正を入れました。ところが投入当日、バックグラウンド処理が止まる・行動記録が途中で切れるという副作用が発覚し、その日のうちに Revert。行動認識のステートマシンは複数の条件が絡み合っていて、大きく直すと必ずどこかが壊れる。反省して、2 日後に対策の核心部分だけを小さく切り出して再投入しました。「大きく直して壊す → 小さく刻む」を地で行く経験でした。
軌跡の直線性という指標
再投入した核心が linearity(直線性)による散らばり検出です。直近 10 点の位置をリングバッファに持ち、
netDistance = 始点→終点の直線距離
pathDistance = 各点間の累積距離
linearity = netDistance / pathDistance
を計算します。本当に移動しているなら軌跡はおおむね一方向に進むので linearity は 1.0 に近づく。ジッターで散らばっているだけなら、累積距離ばかり増えて直線距離が伸びず、値は 0 に近づく。
そこで「静止→乗り物」の遷移前にこのチェックを挟み、linearity が 0.5 以上のときだけ実移動と認めて遷移を許可しました。累積距離が 30m 未満のときはそもそも判定不能として扱います。しきい値 0.5 は実データのログを眺めて決めた経験値です。
GPS を信じられないなら慣性センサーに聞く
「電車から降りられない」問題は逆のアプローチで解きました。加速度センサー系の行動認識(歩行検出)は GPS の精度に依存しません。そこで、慣性センサーによる乗り物状態からの強制離脱だけは GPS 精度ガードの外側に置く構成にしました。GPS の精度が 100m を超えていても、体が歩いていれば降りたと判断できる。
さらに遷移のしきい値は方向で非対称にしています。乗り物に「入る」条件は厳しく、「出る」条件は緩く。誤判定のコストが対称ではない(家にいるのに電車と記録される方が、降車検出が数十秒遅れるより痛い)ためです。
教訓
センサーは一つも信用しない。GPS は散らばり、基地局測位は幽霊のような速度を出し、OS のイベントは遅れて届く。複数の証拠を突き合わせ、判定不能を判定不能のまま扱い、しきい値を非対称にする。行動認識の実装は、アルゴリズムというより「疑い方の設計」でした。
低速判定にも同じ思想を
同じ「疑い方の設計」は速度の扱いにも及びます。GPS の精度が悪いとき、測位点の飛びが見かけ上の速度として観測されます。屋内で基地局測位に切り替わった瞬間、時速 40km で移動したことになっている、というログを何度も見ました。そこで低速・停止の判定では精度 50m 超のサンプルを除外し、ジッター由来の幽霊速度を無視するようにしています。「センサー値を使う前に、その値の信頼度を必ず添える」— 位置情報アプリの実装で一番大事な習慣かもしれません。