A Day In The Life

とあるプログラマの備忘録

AndroidとiPhoneの加速度計アプリの実装方法を比較してみる

大きな違いとしてはiPhoneの場合加速度計のみ管理しているのに対してAndroidは他のセンサー(温度計や磁気センサーなど)と一括りにして管理しています。またiPhoneでは加速度計オブジェクトを直接取得できるのに対しAndroidでは加速度計オブジェクトを取得することができません(Android1.5からSensorクラスが追加され加速度計オブジェクトを取得できるようになりました)。

iPhoneの場合

  • UIAccelerometerDelegateプロトコルを実装したクラスに加速度イベント(accelerometer:didAccelerate:メソッド)を実装する
  • UIAccelerometer#sharedAccelerometerメソッドで加速度計のオブジェクトを取得する
  • UIAccelerometer#delegateを設定すると加速度イベントが発生する

ローパスフィルター(重力の影響だけが残る)のサンプル

@interface HogeViewController : UIViewController <UIAccelerometerDelegate> {
  UIAccelerationValue accelX, accelY, accelZ;
}
@end

#define kFilteringFactor 0.1

@implementation HogeViewController
-(id)init {
  self = [super init];
  if (!self) {
    return nil;
  }
  [self configureAccelerometer];
  return self;
}
-(void)configureAccelerometer {
  // 加速度オブジェクトの初期化
  UIAccelerometer *theAccelerometer = [UIAccelerometer sharedAccelerometer];
  theAccelerometer.updateInterval = 1 / 50;
  theAccelerometer.delegate = self;
}
// 加速度イベント
- (void)accelerometer:(UIAccelerometer *)accelerometer
    didAccelerate:(UIAcceleration *)acceleration {
  UIAccelerationValue x, y, z;
  x = acceleration.x;
  y = acceleration.y;
  z = acceleration.z;
  // ローパスフィルター(重力の影響だけが残る)
  accelX = (x * kFilteringFactor) + (accelX * (1.0 - kFilteringFactor));
  accelY = (y * kFilteringFactor) + (accelY * (1.0 - kFilteringFactor));
  accelZ = (z * kFilteringFactor) + (accelZ * (1.0 - kFilteringFactor));
}
@end

ハイパスフィルター(重力の影響が取り除かれる=瞬間的な加速度がわかる)のサンプル
ローパスの値をフィールド変数に退避するのがポイントです。Accessing Accelerometer Eventsに書いてある方法だとz軸に0.5の加速度がかかってしまいます(robaさんご指摘有り難うございました)。iPhone Dev CenterのサンプルプログラムAccelerometerGraphに正しいハイパスフィルターの方法が書いてありました。

@implementation HogeViewController
// ローパスと同じなので省略
// 加速度イベント
- (void)accelerometer:(UIAccelerometer *)accelerometer
    didAccelerate:(UIAcceleration *)acceleration {
  UIAccelerationValue x, y, z;
  x = acceleration.x;
  y = acceleration.y;
  z = acceleration.z;
  // はじめにローパスの値を求める
  accelX = x * kFilteringFactor + accelX * (1.0 - kFilteringFactor);
  accelY = y * kFilteringFactor + accelY * (1.0 - kFilteringFactor);
  accelZ = z * kFilteringFactor + accelZ * (1.0 - kFilteringFactor);
  // ハイパスフィルター(重力の影響が取り除かれる=瞬間的な加速度がわかる)
  UIAccelerationValue highX, highY, highZ;
  highX = x - accelX;
  highY = y - accelY;
  highZ = z - accelZ;
}
@end

Android1.0,1.1系の場合

  • SensorListenerを実装したクラスを定義する
  • Activity#getSystemServiceでSensorManagerオブジェクトを取得する
  • リスナーをセットする
public class AccelerometerActivity extends Activity
    implements SensorListener {
  private SensorManager sensorManager;
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
  }

  protected void onResume() {
    super.onResume();
    sensorManager.registerListener(this, 
        SensorManager.SENSOR_ACCELEROMETER |
        SensorManager.SENSOR_ORIENTATION,
        SensorManager.SENSOR_DELAY_FASTEST);
  }

  public void onSensorChanged(int sensor, float[] values) {
    switch(sensor) {
      // 傾き(角度が得られる)
      case SensorManager.SENSOR_ORIENTATION:
        :
        break;
      // 加速度
      case SensorManager.SENSOR_ACCELEROMETER:
        :
        break;
    }
  }
}

Android1.5系の場合

  • SensorEventListenerを実装したクラスを定義する
  • Activity#getSystemServiceでSensorManagerオブジェクトを取得する
  • リスナーをセットする
public class SensorActivity extends Activity
    implements SensorEventListener {
  // SensorManagerのインスタンス
  private SensorManager sensorManager;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // SensorManagerのインスタンスを取得
    sensorManager = (SensorManager)this.getSystemService(SENSOR_SERVICE);
    setContentView(R.layout.main);
  }
  @Override
  protected void onResume() {
    super.onResume();
    List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
    // リスナーの登録
    for (Sensor s : sensors) {
      sensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_NORMAL);
    }
  }
  @Override
  public void onAccuracyChanged(Sensor sensor, int accuracy) {
    //
  }
  @Override
  public void onSensorChanged(SensorEvent e) {
    switch(e.sensor.getType()) {
      // 加速度
      case Sensor.TYPE_ACCELEROMETER:
        :
        break;
      // 傾き
      case Sensor.TYPE_ORIENTATION:
        : 
        break;
    }
  }
  @Override
  protected void onStop() {
    super.onStop();
    sensorManager.unregisterListener(this);
  }
}