加速度センサーの使い方 - 測定値の平滑化

| コメント(0)

加速度センサーから取得したデータを平滑化する方法です。

生のデータは、SensorManager#registerListenerメソッドの第3パラメータの指定によって、こんな感じで変わります。


SENSOR_DELAY_NORMAL ⇒ 標準。スクリーンの回転検出に適す
SENSOR_DELAY_UI ⇒ ユーザインタフェース用に適す
SENSOR_DELAY_GAME ⇒ ゲーム用に適する
SENSOR_DELAY_FASTEST ⇒ 可能な限り早く

いまいち何を言ってるのかわかりませんので、実際に取得したデータをグラフ化してみました。
グラフは普通に端末を手で持った時の物で、端から端までで約5秒分のデータです。

SENSOR_DELAY_NORMAL
2014-06-13-16-12-15_norm.png

SENSOR_DELAY_UI
2014-06-13-16-12-53_ui.png

SENSOR_DELAY_GAME
2014-06-13-16-10-31_game.png

SENSOR_DELAY_FASTEST
2014-06-13-16-11-34_fast.png

データ採取のプログラムはこんな感じで、1000個の配列に5msごとに値を入れています。


void setGSensorData(double x, double y, double z) {
	double now = System.nanoTime();
	if( mPTime != 0 ) {
		double dt = now - mPTime;
		long msec = (long)(dt / 5000000.0);
		for(int n=0; n < msec; n++ ) {
			mValX[mValPtr] = mPValX;
			mValY[mValPtr] = mPValY;
			mValZ[mValPtr] = mPValZ;
			mValPtr++;
			if (mValPtr == UnitCount)
				mValPtr = 0;
		}
	}
	mPValX	= (float)x;
	mPValY	= (float)y;
	mPValZ	= (float)z;
	mPTime	= now;
	mValX[mValPtr] = mPValX;
	mValY[mValPtr] = mPValY;
	mValZ[mValPtr] = mPValZ;
	mValPtr++;
	if (mValPtr == UnitCount)
		mValPtr = 0;
}

SENSOR_DELAY_NORMALとSENSOR_DELAY_UIはデータの取得間隔が60ms~200ms程度と結構粗く、グラフでも線がガタガタしているのが見て取れます。
SENSOR_DELAY_FASTESTとSENSOR_DELAY_GAMEはそれより速く20ms程度のようで、細かな動きまでとらえているようです。
このデータ取得間隔は端末の機種によってもかなりバラつきがあるので、APIでは抽象化したシンボルで指定するようになっているようです。

細かな振動があるように見えますが、私の手が小刻みに震えているわけではなく、机の上などにおいてもこの振動は消えることがありません。センサーのノイズです。
ノイズに注目してグラフを良く見ると、FASTESTとGAMEの違いがわかるでしょうか?
GAMEの方はヒゲが少なく線が滑らかになっています。おそらく、GAMEの動作に不要な細かな振動とノイズをローパスフィルターによる平滑化が行われて滑らかにしていると思います。

用途によっては更に滑らかにする必要があるので、加速度センサーの利用にはローパスフィルターが欠かせません。
こういう信号のローパスフィルターは色々なアルゴリズムがあるのですが、滑らかにすればするほど信号に遅れが発生したり、本来必要な部分も消えてしまう事になります。
このあたりはどれが正解という事はなく、用途に応じて選択する事でしょう。

実際に2種類のローパスフィルターで、波形の変化を見てみます。

ローパスフィルター1
過去いくつかの値の平均を取る方法です。この例ではWindSize=10で10個の平均としています。


	mLPuX[mWndPtr] = in;
	mWndPtr++;
	if (mWndPtr == WindSize) {
		mWndPtr = 0;
	}
	double out = 0;
	for (int i = 0; i < WindSize; i++) {
		out += mLPuX[i];
	}
	out /= WindSize;


ローパスフィルター2
前回の値と混ぜる方法です。


	double out = mPrev * 0.8 + in * 0.2;
	mPrev = in;


ローパスフィルター3
前回の値と混ぜる方法ですが、混ぜた値で畳み掛けます。


	double out = mPrev * 0.8 + in * 0.2;
	mPrev = out;


では、以上のフィルターを通した結果です。
すべてFASTESTの同じチャンネルのデータを使用し、フィルターだけを変えています。

赤い線は生データ、緑がローパスフィルター1、青がローパスフィルター2です。
2014-06-13-16-49-09_lp.png

赤い線は生データ、緑がローパスフィルター2、青がローパスフィルター3です。
2014-06-13-17-01-21_lp2.png


同じアルゴリズムでもパラメータ(平均化する数や混ぜ合わせる比率)を変えれば、様々な結果が取り出せます。
アルゴリズム的には平均化は単純で効果が分かりやすいですが、フィルター2や3の混ぜ合わせの方がCPU負荷が低い割に平滑化効果が高く遅れも少ないのでお勧めです。

コメントする