web-dev-qa-db-ja.com

Androidチルトとピッチを補正できるコンパス

私はAndroid電話(Nexus 4)でアプリケーションを作成しようとしています。これはモデルボートで使用されます。ローパスフィルターを追加して、センサーからジッターを除外しました。

ただし、コンパスが安定するのは、携帯電話が背面にある場合のみです。私が上に傾けると(booKのページをめくるなど)、コンパスの見出しが途切れる-最大50 *です。

私は、Sensor.TYPE_MAGNETIC_FIELDをSensor.TYPE_GRAVITYとSensor.TYPE_ACCELEROMETERのいずれかで試してみましたが、効果は同じです。

私は言及されたソリューションを使用しました here 、および他の多くの場所。私の数学は素晴らしいものではありませんが、これは一般的な問題であるに違いなく、それに対処するためのAPIがないことに苛立ちを感じています。

私は3日間この問題に取り組んできましたが、まだ解決策は見つかりませんが、 Catchからのコンパス を使用すると、電話がどの程度傾いても問題は解決しません。だから私はそれが可能でなければならないことを知っています。

私がやりたいのは、電話が北を指している場合、コンパスが北を読み取り、電話が他の軸(ロールまたはピッチ)を移動するときにジャンプしないコンパスを作成することだけです。

私のプロジェクトを断念する前に、だれでも手伝ってください。

ありがとう、アダム

22
Adam Davies

偶然、私はこの問題について数週間考えてきました。

  1. 数学者として、私は他の場所で提案された見た答えのどれにも満足していません。そして
  2. 現在取り組んでいるアプリには良い答えが必要です。

私は ここではmath.stackexchange.com を使用しているその数学を入れて、以下で使用したコードを貼り付けました。このコードは、生の_TYPE_GRAVITY_および_TYPE_MAGNETIC_FIELD_センサーデータから方位角とピッチを計算します。 SensorManager.getRotationMatrix(...)またはSensorManager.getOrientation(...)。コードはおそらく改善される可能性があります。入力が少し不安定になる場合は、ローパスフィルターを使用します。コードはメソッドonAccuracyChanged(Sensor sensor, int accuracy)を介してセンサーの精度を記録することに注意してください。方位角が不安定に思われる場合は、各センサーの精度を確認する必要があります。いずれの場合も、このコードですべての計算が明示的に示されているため、不安定性の問題がある場合(センサーの精度が妥当な場合)は、入力または方向ベクトル_m_NormGravityVector[]_の不安定性を調べることで対処できます。 、_m_NormEastVector[]_または_m_NormNorthVector[]_。

私は、この方法に関して私にだれかが持っているどんなフィードバックにも非常に興味があります。私のアプリでは、デバイスが上向き、縦向き、またはその中間にある限り、夢のように機能することがわかりました。ただし、math.stackexchange.comの記事で述べたように、デバイスが上下逆になり始めると、問題が発生します。そのような状況では、人が望む行動を注意深く定義する必要があります。

_    import Android.app.Activity;
    import Android.hardware.Sensor;
    import Android.hardware.SensorEvent;
    import Android.hardware.SensorEventListener;
    import Android.hardware.SensorManager;
    import Android.view.Surface;

    public static class OrientationSensor implements  SensorEventListener {

    public final static int SENSOR_UNAVAILABLE = -1;

    // references to other objects
    SensorManager m_sm;
    SensorEventListener m_parent;   // non-null if this class should call its parent after onSensorChanged(...) and onAccuracyChanged(...) notifications
    Activity m_activity;            // current activity for call to getWindowManager().getDefaultDisplay().getRotation()

    // raw inputs from Android sensors
    float m_Norm_Gravity;           // length of raw gravity vector received in onSensorChanged(...).  NB: should be about 10
    float[] m_NormGravityVector;    // Normalised gravity vector, (i.e. length of this vector is 1), which points straight up into space
    float m_Norm_MagField;          // length of raw magnetic field vector received in onSensorChanged(...). 
    float[] m_NormMagFieldValues;   // Normalised magnetic field vector, (i.e. length of this vector is 1)

    // accuracy specifications. SENSOR_UNAVAILABLE if unknown, otherwise SensorManager.SENSOR_STATUS_UNRELIABLE, SENSOR_STATUS_ACCURACY_LOW, SENSOR_STATUS_ACCURACY_MEDIUM or SENSOR_STATUS_ACCURACY_HIGH
    int m_GravityAccuracy;          // accuracy of gravity sensor
    int m_MagneticFieldAccuracy;    // accuracy of magnetic field sensor

    // values calculated once gravity and magnetic field vectors are available
    float[] m_NormEastVector;       // normalised cross product of raw gravity vector with magnetic field values, points east
    float[] m_NormNorthVector;      // Normalised vector pointing to magnetic north
    boolean m_OrientationOK;        // set true if m_azimuth_radians and m_pitch_radians have successfully been calculated following a call to onSensorChanged(...)
    float m_azimuth_radians;        // angle of the device from magnetic north
    float m_pitch_radians;          // tilt angle of the device from the horizontal.  m_pitch_radians = 0 if the device if flat, m_pitch_radians = Math.PI/2 means the device is upright.
    float m_pitch_axis_radians;     // angle which defines the axis for the rotation m_pitch_radians

    public OrientationSensor(SensorManager sm, SensorEventListener parent) {
        m_sm = sm;
        m_parent = parent;
        m_activity = null;
        m_NormGravityVector = m_NormMagFieldValues = null;
        m_NormEastVector = new float[3];
        m_NormNorthVector = new float[3];
        m_OrientationOK = false;
    }

    public int Register(Activity activity, int sensorSpeed) {
        m_activity = activity;  // current activity required for call to getWindowManager().getDefaultDisplay().getRotation()
        m_NormGravityVector = new float[3];
        m_NormMagFieldValues = new float[3];
        m_OrientationOK = false;
        int count = 0;
        Sensor SensorGravity = m_sm.getDefaultSensor(Sensor.TYPE_GRAVITY);
        if (SensorGravity != null) {
            m_sm.registerListener(this, SensorGravity, sensorSpeed);
            m_GravityAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
            count++;
        } else {
            m_GravityAccuracy = SENSOR_UNAVAILABLE;
        }
        Sensor SensorMagField = m_sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (SensorMagField != null) {
            m_sm.registerListener(this, SensorMagField, sensorSpeed);
            m_MagneticFieldAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;     
            count++;
        } else {
            m_MagneticFieldAccuracy = SENSOR_UNAVAILABLE;
        }
        return count;
    }

    public void Unregister() {
        m_activity = null;
        m_NormGravityVector = m_NormMagFieldValues = null;
        m_OrientationOK = false;
        m_sm.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent evnt) {
        int SensorType = evnt.sensor.getType();
        switch(SensorType) {
            case Sensor.TYPE_GRAVITY:
                if (m_NormGravityVector == null) m_NormGravityVector = new float[3];
                System.arraycopy(evnt.values, 0, m_NormGravityVector, 0, m_NormGravityVector.length);                   
                m_Norm_Gravity = (float)Math.sqrt(m_NormGravityVector[0]*m_NormGravityVector[0] + m_NormGravityVector[1]*m_NormGravityVector[1] + m_NormGravityVector[2]*m_NormGravityVector[2]);
                for(int i=0; i < m_NormGravityVector.length; i++) m_NormGravityVector[i] /= m_Norm_Gravity;
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                if (m_NormMagFieldValues == null) m_NormMagFieldValues = new float[3];
                System.arraycopy(evnt.values, 0, m_NormMagFieldValues, 0, m_NormMagFieldValues.length);
                m_Norm_MagField = (float)Math.sqrt(m_NormMagFieldValues[0]*m_NormMagFieldValues[0] + m_NormMagFieldValues[1]*m_NormMagFieldValues[1] + m_NormMagFieldValues[2]*m_NormMagFieldValues[2]);
                for(int i=0; i < m_NormMagFieldValues.length; i++) m_NormMagFieldValues[i] /= m_Norm_MagField;  
                break;
        }
        if (m_NormGravityVector != null && m_NormMagFieldValues != null) {
            // first calculate the horizontal vector that points due east
            float East_x = m_NormMagFieldValues[1]*m_NormGravityVector[2] - m_NormMagFieldValues[2]*m_NormGravityVector[1];
            float East_y = m_NormMagFieldValues[2]*m_NormGravityVector[0] - m_NormMagFieldValues[0]*m_NormGravityVector[2];
            float East_z = m_NormMagFieldValues[0]*m_NormGravityVector[1] - m_NormMagFieldValues[1]*m_NormGravityVector[0];
            float norm_East = (float)Math.sqrt(East_x * East_x + East_y * East_y + East_z * East_z);
            if (m_Norm_Gravity * m_Norm_MagField * norm_East < 0.1f) {  // Typical values are  > 100.
                m_OrientationOK = false; // device is close to free fall (or in space?), or close to magnetic north pole.
            } else {
                m_NormEastVector[0] = East_x / norm_East; m_NormEastVector[1] = East_y / norm_East; m_NormEastVector[2] = East_z / norm_East;

                // next calculate the horizontal vector that points due north                   
                float M_dot_G = (m_NormGravityVector[0] *m_NormMagFieldValues[0] + m_NormGravityVector[1]*m_NormMagFieldValues[1] + m_NormGravityVector[2]*m_NormMagFieldValues[2]);
                float North_x = m_NormMagFieldValues[0] - m_NormGravityVector[0] * M_dot_G;
                float North_y = m_NormMagFieldValues[1] - m_NormGravityVector[1] * M_dot_G;
                float North_z = m_NormMagFieldValues[2] - m_NormGravityVector[2] * M_dot_G;
                float norm_North = (float)Math.sqrt(North_x * North_x + North_y * North_y + North_z * North_z);
                m_NormNorthVector[0] = North_x / norm_North; m_NormNorthVector[1] = North_y / norm_North; m_NormNorthVector[2] = North_z / norm_North;

                // take account of screen rotation away from its natural rotation
                int rotation = m_activity.getWindowManager().getDefaultDisplay().getRotation();
                float screen_adjustment = 0;
                switch(rotation) {
                    case Surface.ROTATION_0:   screen_adjustment =          0;         break;
                    case Surface.ROTATION_90:  screen_adjustment =   (float)Math.PI/2; break;
                    case Surface.ROTATION_180: screen_adjustment =   (float)Math.PI;   break;
                    case Surface.ROTATION_270: screen_adjustment = 3*(float)Math.PI/2; break;
                }
                // NB: the rotation matrix has now effectively been calculated. It consists of the three vectors m_NormEastVector[], m_NormNorthVector[] and m_NormGravityVector[]

                // calculate all the required angles from the rotation matrix
                // NB: see https://math.stackexchange.com/questions/381649/whats-the-best-3d-angular-co-ordinate-system-for-working-with-smartfone-apps
                float sin = m_NormEastVector[1] -  m_NormNorthVector[0], cos = m_NormEastVector[0] +  m_NormNorthVector[1];
                m_azimuth_radians = (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);
                m_pitch_radians = (float) Math.acos(m_NormGravityVector[2]);
                sin = -m_NormEastVector[1] -  m_NormNorthVector[0]; cos = m_NormEastVector[0] -  m_NormNorthVector[1];
                float aximuth_plus_two_pitch_axis_radians = (float)(sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);
                m_pitch_axis_radians = (float)(aximuth_plus_two_pitch_axis_radians - m_azimuth_radians) / 2;
                m_azimuth_radians += screen_adjustment;
                m_pitch_axis_radians += screen_adjustment;
                m_OrientationOK = true;                                 
            }
        }
        if (m_parent != null) m_parent.onSensorChanged(evnt);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        int SensorType = sensor.getType();
        switch(SensorType) {
            case Sensor.TYPE_GRAVITY: m_GravityAccuracy = accuracy; break;
            case Sensor.TYPE_MAGNETIC_FIELD: m_MagneticFieldAccuracy = accuracy; break;
        }
        if (m_parent != null) m_parent.onAccuracyChanged(sensor, accuracy);
    }
}
_
34
Stochastically

わかった、私はそれを解決したと思う。

Sensor.TYPE_ACCELEROMETER(またはTYPE_GRAVITY)とSensor.TYPE_MAGNETIC_FIELDを使用する代わりに、Sensor.TYPE_ROTATION_VECTORを次のもので使用しました。

float[] roationV = new float[16];
SensorManager.getRotationMatrixFromVector(roationV, rotationVector);

float[] orientationValuesV = new float[3];
SensorManager.getOrientation(roationV, orientationValuesV);

これは、電話のロールやピッチに関係なく、安定した方位角を返しました。

ここでAndroidモーションセンサー を見ると、表1のすぐ下にあります。ROTATIONセンサーは、コンパス、拡張現実などに最適であることを示しています。

方法がわかればとても簡単です。しかし、エラーが発生するかどうかを確認するために、これを時間をかけてテストすることはまだしていません。

14
Adam Davies

あなたが持っている問題はおそらく ジンバルロック です。考えてみると、電話が直立していて、ピッチがプラスまたはマイナス90度の場合、方位角とロールは同じものになります。数学を調べると、その状況では方位角+ロールまたは方位角ロールのいずれかが適切に定義されていることがわかりますが、それらは個別に定義されていません。したがって、ピッチがプラスまたはマイナス90度に近づくと、読み取り値が不安定になります。一部の人々は、座標系を再マッピングしてこれを回避することを選択します。たとえば、 Androidデバイスが平らではない場合 の場合、方位角、ピッチ、方向をどのように計算すればよいですか?.

2
Stochastically

これは、ピッチやロールの影響を受けずに磁気方位を取得するもう1つの方法です。

private final static double PI = Math.PI;
private final static double TWO_PI = PI*2;

 case Sensor.TYPE_ROTATION_VECTOR:
                float[] orientation = new float[3];
                float[] rotationMatrix = new float[9];

                SensorManager.getRotationMatrixFromVector(rotationMatrix, rawValues);
                SensorManager.getOrientation(rotationMatrix, orientation);

                float heading = mod(orientation[0] + TWO_PI,TWO_PI);//important
                //do something with the heading
                break;



private double mod(double a, double b){
        return a % b;
    }
2
Adrian Alex

Androidおよびiphone向けのオープンソース拡張現実ツールである Mixare をご覧ください。スマートフォンの向き/位置を補正して物事を表示することに関して、いくつか優れた機能があります。画面上で正しく。

編集:特に、センサーイベントを処理する MixView Java class を見てください。

1
AndroidNoob