[Android 開発] 端末の傾きと方位角を求める

Posted by
ぴろり
Posted at
2015/02/07 21:48
Trackbacks
関連記事 (0)
Post Comment
コメントできます
Category
開発メモ カテゴリ
カバーイメージ

 前回の記事で紹介した傾きと方位角を求める方法は、最新の Android OS では非推奨(deprecated)とされています。現在のリファレンスでは、加速度センサと方位角センサそれぞれの測定値から計算するよう求められています。開発者が記述すべきコードが増えるため、一見すると面倒になったように思えますが、この過程で得られた回転行列は、OpenGL などの 3D グラフィックス描画においてそのまま利用できるメリットもあって、総合的にはより汎用性を増したといえます。

この記事を Delicious に追加する   このエントリーをはてなブックマークに追加  

スクラップ帳 » Android アプリ開発 Tips ~ センサ入力

 Android アプリを開発する際の小技まとめ。GPS、コンパスなどの各種センサ デバイスからのデータの取得方法などの覚書きを網羅して、必要な機能をコピー&ペーストで再利用できるようにします。

  1. GPS を使う 2015/02/05
  2. GPS 衛星の情報を取得する 2015/02/06
  3. GPS モジュールが出力する NMEA メッセージを取得する 2015/02/06
  4. 端末の傾きと方位角を求める (非推奨) 2015/02/07
  5. 端末の傾きと方位角を求める 2015/02/07 今ココ

概要

 変更点は 2 つ。リスナを登録する際に、傾き(TYPE_ORIENTATION)センサ一つで済んだものが、加速度(TYPE_ACCELEROMETER)センサと、磁気(TYPE_MAGNETIC_FIELD)センサを 2 つ必要とする点。そして、傾きと方位角を求める際に、両者のセンサの測定値から回転行列を求めて、最終的な解を得るという点です。
 TYPE_ORIENTATION を指定した場合でも、加速度センサと磁気センサを用いて内部的に同じように計算していたと思うのですが、それを開発者が明示的に書く必要があるということです。

  1. SensorManager を取得する
  2. SensorManager に、 加速度センサ(TYPE_ACCELEROMETER) と 磁気センサ(TYPE_MAGNETIC_FIELD) について、SensorEventListener を登録する
  3. センサの状態が変化したら、SensorEventListeneronSensorChanged メンバ関数がコールバックされる
  4. 加速度センサと磁気センサの値から回転行列を求め、端末の最終的な傾きと方位角を計算する
  5. 動作を止めるには SensorManager から SensorEventListener を外す


Fig.1 スクリーンショット

ソースコード

package com.example.helloworld;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.content.Context;
import android.hardware.SensorManager;
import android.hardware.SensorEventListener;
import android.hardware.SensorEvent;
import android.hardware.Sensor;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity
{
    private SensorManager mSensorManager = null;
    private SensorEventListener mSensorEventListener = null;

    private float[] fAccell = null;
    private float[] fMagnetic = null;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSensorManager = (SensorManager) getSystemService( Context.SENSOR_SERVICE );

        mSensorEventListener = new SensorEventListener()
        {
            public void onSensorChanged (SensorEvent event) {
                // センサの取得値をそれぞれ保存しておく
                switch( event.sensor.getType()) {
                case Sensor.TYPE_ACCELEROMETER:
                    fAccell = event.values.clone();
                    break;
                case Sensor.TYPE_MAGNETIC_FIELD:
                    fMagnetic = event.values.clone();
                    break;
                }

                // fAccell と fMagnetic から傾きと方位角を計算する
                if( fAccell != null && fMagnetic != null ) {
                    // 回転行列を得る
                    float[] inR = new float[9];
                    SensorManager.getRotationMatrix(
                            inR,
                            null,
                            fAccell,
                            fMagnetic );
                    // ワールド座標とデバイス座標のマッピングを変換する
                    float[] outR = new float[9];
                    SensorManager.remapCoordinateSystem(
                            inR,
                            SensorManager.AXIS_X, SensorManager.AXIS_Y,
                            outR );
                    // 姿勢を得る
                    float[] fAttitude = new float[3];
                    SensorManager.getOrientation(
                            outR,
                            fAttitude );

                    String buf =
                            "---------- Orientation --------\n" +
                            String.format( "方位角\n\t%f\n", rad2deg( fAttitude[0] )) +
                            String.format( "前後の傾斜\n\t%f\n", rad2deg( fAttitude[1] )) +
                            String.format( "左右の傾斜\n\t%f\n", rad2deg( fAttitude[2] ));
                    TextView t = (TextView) findViewById( R.id.textView1 );
                    t.setText( buf );
                }
            }
            public void onAccuracyChanged (Sensor sensor, int accuracy) {}
        };
    }

    private float rad2deg( float rad ) {
        return rad * (float) 180.0 / (float) Math.PI;
    }

    protected void onStart() { // ⇔ onStop
        super.onStart();

        mSensorManager.registerListener(
                mSensorEventListener,
                mSensorManager.getDefaultSensor( Sensor.TYPE_ACCELEROMETER ),
                SensorManager.SENSOR_DELAY_UI );
        mSensorManager.registerListener(
                mSensorEventListener,
                mSensorManager.getDefaultSensor( Sensor.TYPE_MAGNETIC_FIELD ),
                SensorManager.SENSOR_DELAY_UI );
    }

    protected void onStop() { // ⇔ onStart
        super.onStop();

        mSensorManager.unregisterListener( mSensorEventListener );
    }
}

解説

24 行目
 SensorManager を取得します(概要の手順.1)
28 行目
 加速度センサ、あるいは磁気センサの値が変化した際に呼ばれるコールバックです。
30~37 行目
 加速度センサと磁気センサの測定値をそれぞれ保存しておきます。
40 行目
 加速度センサと磁気センサの測定値が両方そろって初めて、傾きと方位角を計算できるようになります。
42~47 行目
 加速度センサと磁気センサの測定値から回転行列を求めます。
48~53 行目
 例えば、カメラのように水平方向を狙って使うアプリと、手のひらに載せて使うアプリでは、端末が想定する座標系が違いますが、求められた回転行列を、端末の任意の軸に合わせて変換できます。これによって、アプリの使途に併せるための行列計算を開発者が自前で実装しなくても済みます。
54~58 行目
 回転行列から、端末の最終的な傾きと方位角を求めます。
80~87 行目
 SensorManager に、加速度(TYPE_ACCELEROMETER)センサと、磁気(TYPE_MAGNETIC_FIELD)センサについて、SensorEventListener を登録して、センサ値の取得を始めます(概要の手順.2)。
93 行目
 センサ値の取得を停止します(概要の手順.5)。

 回転行列の処理については、代数幾何のお話になるので私もよく判っていません*1。とりあえずオマジナイ的にコピー&ペーストで使えれば OK ということで。

参考リンク

この記事を Delicious に追加する   このエントリーをはてなブックマークに追加  

  1. *1 大学でも代数幾何の単位はギリギリだったのですsad.gif

この記事を読んだ人はこんな記事も読んでいます記事リコメンデーションについて

カバー画像:[Android 開発] 端末の傾きと方位角を求める (非推奨)

関連記事/トラックバック

関連記事/トラックバックはまだありません

この記事にトラックバックを送るには?

コメントを投稿する

 
 (必須, 匿名可, 公開, トリップが使えます)
 (必須, 匿名可, 非公開, Gravatar に対応しています)
 (必須)
スパム コメント防止のため「投稿確認」欄に ランダムな数字 CAPTCHAについて を入力してから送信してください。お手数ですがご協力のほど宜しくお願いいたします。