My Photo

« 結局プログラマを酷使してるだけじゃん | Main | b-mobileのtalking SIMだけでいけるか »

October 19, 2011

Androidアプリ開発メモ029:モーションセンサー その3 SensorManager#getOrientation()

前のセンサの記事で方位と傾きの取得する方法として
・getDefaultSensor()やgetSensorlist()でタイプをSensor.TYPE_ORIENTATIONに指定してセンサを取得する
やり方を説明したが、これは現在は推奨されていない。
現在推奨されているのはgetOrientation()を使用する方法である。

しかし、リファレンス読んでもおいらの頭では理解不能^^;
線形代数学の授業、全く理解できなかったからなあorz
3Dグラフィックスやっている人には分かるのかな。
#この方法だとOpen GLとか3Dライブラリと親和性が高いとどこかに書いてあった。
で、自分には意味不明なので、

「お約束としてSensorManagerの3つのstaticメソッドgetRotationMatrix()、remapCoordinateSystem()、getOrientation()をこの順番で呼ぶ」

と覚えることにした。バカ丸出しだ。

remapCoordinateSystem()の第2、第3引数は、デバイスのX軸、Y軸がそれぞれ指している世界座標系の方向を指定する。
例:
画面が縦長表示(portrait)でデバイスを水平に持ってディスプレイを真上に向けた場合は

remapCoordinateSystem(inR, AXIS_X, AXIS_Y, outR); 
デバイスを縦に持って背面カメラを正面に向けディスプレイを持っている人に向けた状態の場合は
remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR); 
画面が横長表示(landscape)でデバイスを水平に持ってディスプレイを真上に向けた場合は
remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR); 

getOrientation()で取得した方位・傾きはラジアンで、反時計回りで正の値。
ラジアンはMath#toDegrees()で度に変換できる。
「反時計回りで正」っていうのも良く分からない。デバイスを縦に持って左(反時計回り)に傾けたらロールが負の値になった。Y軸の正の方から原点方向を見た場合に反時計回りに回ると正ってことか?

怪しいサンプルプログラム

package com.example.orientationexample;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class OrientationExample extends Activity
  implements SensorEventListener {
  
  private TextView textView;
  
  private SensorManager sensorManager;
  private List accelerometers;  // 加速度センサー
  private List magnetics;  // 磁気センサー
  
  float[] aValues;    // 加速度センサの値
  float[] mValues;    // 磁気センサの値
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    textView = (TextView)findViewById(R.id.textView1);
    
    // センサーマネージャの取得
    sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
    
    // センサーの取得
    accelerometers = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
    magnetics = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
    
    Log.v("SENSOR", "num of accs:" + accelerometers == null ? "null": Integer.toString(accelerometers.size()));
    Log.v("SENSOR", "num of mags:" + magnetics == null ? "null": Integer.toString(magnetics.size()));
    }

  @Override
  protected void onResume() {
    super.onResume();
    
    // リスナの登録
    if (accelerometers != null) {
      for (Sensor accelerometer: accelerometers) {
        sensorManager.registerListener(
            this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
      }
    }
    if (magnetics != null) {
      for (Sensor magnetic: magnetics) {
        sensorManager.registerListener(
            this, magnetic, SensorManager.SENSOR_DELAY_NORMAL);
      }
    }
  }

  @Override
  protected void onPause() {
    super.onPause();
    
    // リスナの登録解除
    if (accelerometers != null) {
      for (Sensor accelerometer: accelerometers) {
        sensorManager.unregisterListener(this, accelerometer);
      }
    }
    if (magnetics != null) {
      for (Sensor magnetic: magnetics) {
        sensorManager.unregisterListener(this, magnetic);
      }
    }
  }

  @Override
  public void onSensorChanged(SensorEvent event) {
    float[] oValues = new float[3];    // 向き
    
    switch (event.sensor.getType()) {
    case Sensor.TYPE_ACCELEROMETER:
      aValues = event.values.clone();
      break;
    case Sensor.TYPE_MAGNETIC_FIELD:
      mValues = event.values.clone();
      break;
    default:
      Log.v("SENSOR", "unexpected type");
    }
    
    float[] R = new float[16];
    float[] I = new float[16];
    float[] outR = new float[16];
    
//    Log.v("SENSOR", "aValues=" + ((aValues != null) ? "not null" : "null") + " mValues=" + ((mValues != null) ? "not null" : "null"));
    if (aValues != null && mValues != null) {
      SensorManager.getRotationMatrix(R, I, aValues, mValues);
      SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_X, SensorManager.AXIS_Y, outR);	// デバイスを地面と水平にして縦長表示の場合
      SensorManager.getOrientation(outR, oValues);

      String text = "OrientationExample X Y"
        + "\n方位:"       + (int)Math.toDegrees(oValues[0])
        + "\nピッチ:"     + (int)Math.toDegrees(oValues[1])
        + "\nロール:"     + (int)Math.toDegrees(oValues[2]);
      textView.setText(text);
      aValues = null;
      mValues = null;
    }
  }
  
  @Override
  public void onAccuracyChanged(Sensor arg0, int arg1) {
  }
}

以下、3つメソッドのリファレンスの怪しい訳。

public static boolean getRotationMatrix(float[] R, float[] I, float[] gravity, float[] geomagnetic)
傾斜マトリックスIおよびベクトルをデバイス座標系から直接の正規直交基底として定義されている世界座標系に変換する回転マトリックスRを計算する。
・Xはベクトル積YZ(これはデバイスの現在の位置で地面に接しほぼ東を指している)として定義されている。
・Yはデバイスの現在の位置で地面に接し、磁北極の方向を指している。
・Zは空の向きを指し地面に対して垂直である。

定義により:
[0 0 g] = R * gravity (g = magnitude of gravity) 
[0 m 0] = I * R * geomagnetic (m = magnitude of geomagnetic field) 

デバイスが世界座標系にそろえられている場合、Rは単位行列(恒等行列?)である。すなわち、デバイスのX座標が東を指している場合、Y座標が北極を指していてデバイスは空に面している。

Iは重力(世界座標空間)と同じ座標空間に地磁気ベクトルを変換する回転行列である。IはX軸周りの単純な回転である。ラジアン単位の傾斜角度はgetInclination(float[])で計算することができる。

-------------------------
(訳すの面倒くさくなったので中略)
-------------------------
各行列の逆行列はその転置を取ることによって簡単に計算することができます。

この関数によって返される行列は、デバイスが自由落下ではなく磁北に近くない場合にのみ意味がある。装置が加速される、または強力な磁場に置かれている場合、返される行列が不正確になることがある。

引数
  R:この関数が帰った場合、回転行列Rを保持している浮動小数点数9個のarray。Rはnullもありうる。
  I:この関数が帰った場合、回転行列Iを保持している浮動小数点数9個のarray。Iはnullもありうる。
  gravity:デバイス座標で表される重力のベクトルを含む浮動小数点数3個のarray。単純にTYPE_ACCELEROMETERタイプのSensorのSensorEventによって返される値を使うことができる。
  geomagnetic:デバイス座標で表される地磁気のベクトルを含む浮動小数点数3個のarray。単純にTYPE_MAGNETIC_FIELDタイプのSensorのSensorEventによって返される値を使うことができる。
戻り値
  成功の場合はtrue。失敗の場合はfalse(たとえば、デバイスが自由落下している)。失敗の場際、出力行列は変更されない。


public static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR)
与えられた回転行列を異なる座標系で表現されるように回転する。アプリケーションが異なる座標系にあるデバイスの三方向の角度を計算する必要があるときにこれは通常使用される(getOrientation(float[], float[])参照)。
もし画面が物理的に回転しなければ、回転行列が描画(たとえばOpenGL ESで)のために使用される場合、普通はこの関数で変換することは必要ではない。その場合、現在の画面の回転を取得するためにDisplay.getRotation()を使用できる。ユーザーは一般的に画面を回転させるのは自由なので、ここで利用するパラメータを決定するには回転を頻繁に考慮しなければならないことに注意してください。

例:
・回転角度が必要とされる拡張現実アプリケーションのためにカメラ(カメラの軸に沿ったY軸)を使う場合
    remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);
・回転がSurface.ROTATION_90のときにデバイスを機械式のコンパスとして使う場合
    remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR); 

上記の例では注意をせよ。この呼び出しは、回転角度を計算するときに、その自然の方向からの回転を計上するためにのみ必要です。回転行列がレンダリングのために使用されている場合も、変形は必要ない可能性もある、たとえばアクティビティがlandscapeモードで走っている場合。

結果として得られる座標系は正規直交であるので、二つの軸だけを指定する必要があります。

引数:
  inR:変換される回転行列。普通、それはgetRotationMatrix(float[], float[], float[], float[])によって返される。
  X:デバイスのX軸がマッピングされる世界の軸と方向を定義する。
  Y:デバイスのY軸がマッピングされる世界の軸と方向を定義する。
  outR:変換された回転行列。inRとoutRは同じarrayかもしれない。しかしそれは性能の理由から推奨されない。
戻り値:
  成功の場合true。入力パラメータが間違っている場合false。たとえばX軸とY軸が同じ軸の場合。あるいはinRとoutRが同じ長さではない場合。


public static float[] getOrientation(float[] R, float[] values)
回転行列に基づいてデバイスの向きを計算する。
返る場合、array valuesは次の結果が入れられる。:
・values[0]:アジマス。Z軸まわりの回転。
・values[1]:ピッチ。X軸まわりの回転。
・values[2]:ロール。Y軸周りの回転。
使用されるリファレンス座標系は回転行列に対して定義された世界座標系とは異なる。
・Xはベクトル積YZ(これはデバイスの現在の位置で地面に接しほぼ西を指している)として定義されている。
・Yはデバイスの現在の位置で地面に接し磁北極の方向を指している。
・Zは地球の中心法方向を指しており地面に対して垂直である。
上記すべて3つの角度はラジアン単位で反時計回り方向に正である。

引数
  R:回転行列。getRotationMatrix(float[], float[], float[], float[])参照。
  valuse:結果を保持する3個の浮動小数点数のarray
戻り値
  引き数として渡されたarray values。

« 結局プログラマを酷使してるだけじゃん | Main | b-mobileのtalking SIMだけでいけるか »

Androidアプリ開発」カテゴリの記事

Comments

Post a comment

(Not displayed with comment.)

TrackBack

TrackBack URL for this entry:
http://app.cocolog-nifty.com/t/trackback/26461/53033991

Listed below are links to weblogs that reference Androidアプリ開発メモ029:モーションセンサー その3 SensorManager#getOrientation():

« 結局プログラマを酷使してるだけじゃん | Main | b-mobileのtalking SIMだけでいけるか »

March 2017
Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
無料ブログはココログ

日本blog村

  • にほんブログ村 IT技術ブログへ
  • にほんブログ村 アニメブログへ
  • にほんブログ村 サッカーブログ アルビレックス新潟へ

好きな音楽家

メモ

XI-Prof