はじめに
本記事はStormworksやLua,および数学に対し簡単な理解がある方に向けた記事です.初歩的なLuaについては同じQiita内でyukimaru73氏や零時 秋雨氏が初心者向けの記事を書いておられますので,そちらをご覧ください.数式等についても極力難しい表現を避けるようにしているつもりです.
Stormworksのレーダーはノイズを多分に含んでおり,これは距離に対して±1%,方位に対して±0.02π[rad]であることがわかっています(参考).本記事では,移動平均フィルタ(MAF),一次遅れローパスフィルタ(LPF)の2種類を紹介します.
移動平均フィルタ
原始的なノイズフィルタの一つに,移動平均フィルタ(MAF)があります.これは,過去から現在までのある一定期間のデータの単純平均を取りフィルタリングを行うものです.例えば,"ある一定期間"が過去2tickの場合を考え,以下のようなデータが得られていたとします.
時刻 | n-2 | n-1 | n |
---|---|---|---|
データ | 4 | 2 | 3 |
ここで,これらの単純平均は
\frac{4+2+3}{3} = 3
となります.しかし,実際のデータは刻々と(Stormworksでは1秒間に60回)変化していて,これまでのデータを記憶しておかないと平均を取ることが出来ません.そこで,いくつかのデータを配列に保存し,それらの単純平均を取るプログラムを書けば,MAFの完成ということになります.擬似コードは以下のとおりです.
- 記憶するのは過去n回分
- 配列にデータを格納する
- もし配列がnより大きかったら,一番古い要素を削除
- 配列内の数字の総和を求め,要素数で割る
以上の動作を毎tick行う(onTick内に記述する)ことができれば問題ありません.しかし,このフィルタには弱点があり,それが遅延です.以下の画像では過去30tick分の単純平均を取っていますが,ある程度の遅延が生じてしまっています.加えて,参照するデータが多いほど配列も大きくなってしまい,負荷も多少大きくなります.
ローパスフィルタ
ローパスフィルタ(LPF)はMAFと比べるとアドバンスなフィルタですが,それでも仕組みは単純です.ローパスフィルタの考え方は以下のような式(LPF方程式と呼ぶことにします)で表されることが多いです.
Out[n] = (1-2k)Out[n-1] + k(In[n] + In[n-1])
Outは出力値,Inは入力値,nは今回,n-1は前回という意味です.つまり,上式の意味するところは,
今回出力値 = (1-2k) \times 前回出力値 + k \times (今回入力値 + 前回入力値)
となります.今回出力値がフィルタリングされたデータとなります.また,kは0~1の範囲で,小さければ小さいほどフィルタの効果が大きくなり,遅延も大きくなります.擬似コードは以下の通りです.
- kを設定する
- 前回出力値と前回入力値を初期化する
- LPF方程式を適用する
- 前回出力値と前回入力値をバッファに記憶する
2.の処理に注意
3.と4.の動作を毎tick行う(onTick内に記述する)ことができれば問題ありませんが,特に2.については注意してください.ここでの初期化とは,onTick外で適当な数値を代入しておくことを表します.このコードを実行した最初のtickにコンピュータがこの処理を行ったときに,「前回出力値や前回入力値が存在しない」という問題が生じます.これを解決するのが変数の初期化です.また,このフィルタにはMAF同様遅延がありますが,それでも配列を使用せず直近の数値のみでフィルタリングを行うため,MAFに比べると動作は軽量です.
さいごに
今回紹介したフィルタの他にも,α-βフィルタやカルマンフィルタといたより高度な(高性能な)フィルタもありますが,今回はあくまで初心者向けの解説ということで原始的なフィルタの紹介をしました.何かわからないことや,間違っていると思われる部分があればコメントをしてくだされば分かる範囲で対応します.