2010年11月29日月曜日

傾き、加速度の手振れノイズをフィルターする

人間の手の震えもありますが、端末で取れる加速度センサーの値は、結構細かく震えています。
この加速度センサーの値をもとにLinearで中心にあるボールを動かしたかったのですが、この震えのせいで、
きれいに移動しないで、ぴょこぴょこと跳ねているような動きに見えてしまうので困りました。

センサーの精度かと思って、そちらを調査してみたり、
アニメーションの処理飛びなのかと思ってログを見てみたりしていたのですが、
特に解決策は見つからず。

結局のところこのブレを減らすための解決策が必要だろうと推測して、ノイズフィルターを探していたところ身近なところに解決策がありました。

DJミキサーでよくある、ハイパスフィルターとローパスフィルターです。

ハイパスフィルターは、つまみを回すとだんだんシャキシャキの音になってくあれですw。
ローパスフィルターは、つまみを回すとだんだんモコモコの音になってくあれですw。


WikiPedia ローパスフィルター(ハイカット)
ローパスフィルタ(Low-pass filter: LPF)はフィルタ回路の一種で、低周波を良く通し、ある遮断周波数より高い周波数の帯域を通さない(減衰させる)フィルタである。

WikiPedia ハイパスフィルター(ローカット)
ハイパスフィルタ(High-pass filter: HPF)はフィルタ回路の一種で、高周波を良く通し、遮断周波数より低い周波数の帯域を通さない(減衰させる)フィルタである。 日本語では「高域通過濾波器」とも言われる。また英語では「low-cut filter」とも言われ、これは「bass-cut filter」または「rumble filter」としてオーディオ機器などで使用される。


加速度は、重力加速度+手振れなどの実際の端末の動きが加速度が影響してきます。
あるXYZで考えてどれか一方向についての変化
重力加速度の変化:端末の大体の傾きになりますので周波数で考えるとだんだん傾いていれば低い周波数です。
端末の動きの変化:手振れなので重力加速度の変化より高い周波数のノイズになります。

ぴょこぴょこと動いてしまうのは、「端末の動きの変化」が影響しているので、高い周波数をカットすればいい。すなわち低い周波数だけ通せばいいということになります。

ってことで、ロジック。
一つ前の値と比較しながら、値を取ります。
XYだけの例です。

部分的に抜き出すとわけわからなくなったので、全部載せます。

イメージを最初に真ん中において、ローパスフィルタをかけた加速度で、イメージを移動しています。

package jp.mediba.android.xxxxxx;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.util.Log;
import android.widget.ImageView;

public class PointViewOverlay extends ImageView {

 private Matrix mMatrix = new Matrix();
 private Matrix mSavedMatrix = new Matrix();
 private float[] currentOrientationValues = {0.0f, 0.0f};
 
 private PointF startPoint = new PointF();
 
 private ImageView imageView;
 private int frameWidth = 0;
 private int frameHeight = 0;
 
 public PointViewOverlay(Context context) {
  super(context);
  // TODO 自動生成されたコンストラクター・スタブ
 }

 public void setImageView(ImageView imageView) {this.imageView = imageView;}
 public void setFrameSize(int frameWidth, int frameHeight) {
  this.frameWidth = frameWidth;
  this.frameHeight = frameHeight;
 }
 
 public void resetStartPoint() {
  mMatrix.set(mSavedMatrix);
  this.startPoint = new PointF((float)this.frameWidth/2,(float)this.frameHeight/2);
  mMatrix.postTranslate(this.startPoint.x, this.startPoint.y);
  imageView.setImageMatrix(mMatrix);
  Log.v("resetStartPoint.",String.valueOf("X:" + this.startPoint.x) + "Y:" + String.valueOf(this.startPoint.y));
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  // TODO 自動生成されたメソッド・スタブ
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }

 public void animateView( SensorEvent event ) {
  
  if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

   // Lowpass Filter ハイカット これ!
   currentOrientationValues[0] = event.values[0] * 0.1f + currentOrientationValues[0] * (1.0f - 0.1f);
   currentOrientationValues[1] = event.values[1] * 0.1f + currentOrientationValues[1] * (1.0f - 0.1f);

   // 重力加速度
   float accX = currentOrientationValues[0];
   float accY = currentOrientationValues[1];

   // 横画面なのでちょっと大きめにしました。
   float moveX = Math.round(accY*48);
   float moveY = Math.round(accX*32);

   float pointX = this.startPoint.x+moveX;
   float pointY = this.startPoint.y+moveY;
   
   mMatrix.set(mSavedMatrix);
   mMatrix.postTranslate(pointX,pointY);
   
   imageView.setImageMatrix(mMatrix);
   Log.v("animateView","pointX,pointY"+String.valueOf(pointX)+","+String.valueOf(pointY));
   
  }
 }
 
}