Android上方位角/偏航和滚动的方向传感器值不一致

时间:2013-03-21 00:04:48

标签: android orientation android-sensors sensormanager

我无法获得良好的方向传感器读数。传感器读数似乎不可靠,因此我针对两个免费的传感器测试应用(Sensor Tester (Dicotomica)Sensor Monitoring (R's Software))测试了我的代码。我发现虽然我的读数经常与传感器测试应用程序一致,但偶尔方位角/偏航和滚动的值差异高达40度,尽管音高读数大多是一致的。两个免费的应用程序似乎总是彼此一致。

我将我的代码放入一个微小的Android活动中并且出现了相同的不一致。代码如下:

public class MainActivity extends Activity implements  SensorEventListener {

    private SensorManager mSensorManager;
    private float[] AccelerometerValues;
    private float[] MagneticFieldValues;
    private float[] RotationMatrix;
    private long nextRefreshTime;           // used to ensure dump to LogCat occurs no more than 4 times a second
    private DecimalFormat df;               // used for dumping sensors to LogCat

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

        mSensorManager = (SensorManager)getSystemService(android.content.Context.SENSOR_SERVICE);
        Sensor SensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(this, SensorAccelerometer, SensorManager.SENSOR_DELAY_UI);  
        Sensor SensorMagField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        mSensorManager.registerListener(this, SensorMagField, SensorManager.SENSOR_DELAY_UI);
        AccelerometerValues = new float[3];
        MagneticFieldValues = new float[3];
        RotationMatrix = new float[9];
        nextRefreshTime = 0;
        df = new DecimalFormat("#.00");
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            System.arraycopy(event.values, 0, AccelerometerValues, 0, AccelerometerValues.length);
        else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
                System.arraycopy(event.values, 0, MagneticFieldValues, 0, MagneticFieldValues.length);

        if (AccelerometerValues != null && MagneticFieldValues != null) {
            if(SensorManager.getRotationMatrix(RotationMatrix, null, AccelerometerValues, MagneticFieldValues)) {
                float[] OrientationValues = new float[3];
                SensorManager.getOrientation(RotationMatrix, OrientationValues);

                // chance conventions to match sample apps
                if (OrientationValues[0] < 0) OrientationValues[0] += 2*(float)Math.PI;
                OrientationValues[2] *= -1;

                // dump to logcat 4 times a second
                long currentTimeMillis = System.currentTimeMillis();
                if (currentTimeMillis > nextRefreshTime) {
                    nextRefreshTime = currentTimeMillis+250;
                    Log.i("Sensors",    // arrange output so that numbers line up in columns :-)
                            "(" + AngleToStr(OrientationValues[0]) + "," + AngleToStr(OrientationValues[1]) + "," + AngleToStr(OrientationValues[2])
                            + ") ("+FloatToStr(AccelerometerValues[0]) + "," + FloatToStr(AccelerometerValues[1]) + "," + FloatToStr(AccelerometerValues[2])
                            + ") ("+FloatToStr(MagneticFieldValues[0]) + "," + FloatToStr(MagneticFieldValues[1]) + "," + FloatToStr(MagneticFieldValues[2])+")");
                }               
            }
        }               
    }

    private String AngleToStr(double AngleInRadians) {
        String Str = "   "+Integer.toString((int)Math.toDegrees(AngleInRadians));
        return Str.substring(Str.length() - 3);
    }
    private String FloatToStr(float flt) {
        String Str = "      "+df.format(flt);
        return Str.substring(Str.length() - 6);
    }   

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onAccuracyChanged(Sensor arg0, int arg1) { }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}

我正在使用运行Jelly Bean 4.1.1的Galaxy Note 2。谁能告诉我我做错了什么?

2013年3月24日更新:更多信息。 (1)我在清单中禁用了纵向和横向之间的切换,因此getWindowManager()。getDefaultDisplay()。getRotation()始终为零。因此我不认为remapCoordSystem会对此有所帮助,因为那是用于切换轴的,而我所看到的错误不是大错误,它们更加微妙。 (2)我检查了准确度灵敏度,当两个传感器都声称具有高精度时会出现不一致。

作为我看到的不一致的一个例子,当上面的代码给我(方位角,俯仰,滚动)=(235,-52,-11)时,两个免费应用程序显示相似的值。但是当我看到(278,-58,-52)应用程序显示(256,-58,-26)时,方位角和滚动都有很大差异,尽管音高似乎没问题。

4 个答案:

答案 0 :(得分:5)

这完全取决于设备的方向。如果设备不是平面且有一些旋转即不完全纵向横向,那么方位角是错误的。在这种情况下,您必须在致电remapCoordSystem之前致电getOrientation。您还应该至少过滤加速度计值 有关详细信息,请参阅Convert magnetic field X, Y, Z values from device into global reference frame的答案 对于平坦度的含义,请参阅How to measure the tilt of the phone in XY plane using accelerometer in Android

您应该按照以下方式测试您的应用:

  1. 将您的设备平放并启动您的应用,并将您的价值与其他应用进行比较。每次读数大约30秒,并且在此步骤中不要移动设备 我对这一步的猜测:你的价值应该与其他应用价值差异很小,但它在开始时可能不如其他应用程序稳定。

  2. 在您的应用程序运行时,将设备旋转到新位置,等待值稳定(不必是相同的值,但变化大约是1或2度)。在相同位置将您的值与其他应用值进行比较。重复相同的事情,但旋转时其他应用程序正在运行 我对此步骤的猜测:您的稳定值应与其他应用程序值略有不同,但您的值变得稳定需要更长时间。此外,当停在新位置时,您的应用程序的前几个值与稳定值之间的差异要大于另一个。

  3. 现在将以下代码放在注释// dump到logcat之前,然后像对方向一样记录它 float rotation =(float)Math.atan2(RotationMatrix [6],RotationMatrix [7]);
    将设备竖直放置在盒子中,使其无法旋转。确保上面给出的旋转值尽可能接近0(它会稍微跳一下) 现在重复步骤1和2,设备竖直 我的猜测是你的结果与我对上面步骤1和2的猜测相同。

  4. 将您的设备倾斜直立放置在一个盒子中,使其无法进一步旋转。确保上面给出的旋转值尽可能接近25或-25(它会稍微跳一下) 现在重复步骤1,设备倾斜直立 我的猜测是你的结果与其他应用程序完全不同。
    您可以使用不同的旋转值重复步骤4。

  5. 如果我猜对了,我会给你解释。与此同时,您可以在Magnetic Fields, Rotation Matrix And global coordinates

    阅读我的答案

答案 1 :(得分:4)

我认为在设备不平坦时定义方向角的最佳方法是使用更合适的角度坐标系统,该坐标系统是从SensorManager.getOrientation(...)得到的标准欧拉角。我建议我描述here on math.stackexchange.com。我还在an answer here中添加了一些实现它的代码。除了方位角的良好定义之外,它还具有更好的俯仰角定义,该方法定义为旋转出水平平面,而不管旋转沿哪个轴发生。

您可以从我在第一段中给出的两个链接中获取完整的详细信息。但是,总之,来自SensorManager.getRotationMatrix(...)的旋转矩阵 R

Definition of rotation matrix R

其中(E x ,E y ,E z ),(N x ,N y ,N z )和(G x ,G y ,G z )矢量指向东方,北方和重力方向。然后你想要的方位角由

给出

Definition of azimuth angle phi

答案 2 :(得分:3)

我可以回答这个难题的一部分。当设备平放在桌子上时,一切都会好的,但是当它直立且音高为正或负90度时,你会遇到Gimbal lock。发生这种情况时,方位角和滚动是不确定的。在那种情况下,方位角和滚动意味着相同的事情,如果你查看数学,你会看到定义了azumith + roll(当音高= +90度时)或方位角滚动被定义(当音高= -90度时) ),但其中两个并不是自己定义的。在您给出的示例中,请注意您的应用与其他应用之间的总和方位角+滚动大致相同。

此外,您可以使用android Sensor.TYPE_GRAVITY,这样您就不需要在加速度计读数上使用低通滤波器。 Sensor.TYPE_GRAVITY是一个传感器,它是从其他传感器计算出来的,试图消除加速度传感器加速度的影响。但是,如果您希望您的应用在引入Sensor.TYPE_GRAVITY之前在旧版Android上运行,那么您需要自己完成这项工作。

答案 3 :(得分:0)

@Stochastically对getOrientation()说,使用:

azimuth = atan2((Ey-Nx), (Ex-Ny))

但是,如果你看看Android的实现,实际上是实时的

azimuth = atan2(Ey, Ny)

这也将为您提供-180和180范围内的值。

以下是SensorManager.class中提供的源代码

public static float[] getOrientation(float[] R, float values[]) {
    /*
     * 4x4 (length=16) case:
     *   /  R[ 0]   R[ 1]   R[ 2]   0  \
     *   |  R[ 4]   R[ 5]   R[ 6]   0  |
     *   |  R[ 8]   R[ 9]   R[10]   0  |
     *   \      0       0       0   1  /
     *
     * 3x3 (length=9) case:
     *   /  R[ 0]   R[ 1]   R[ 2]  \
     *   |  R[ 3]   R[ 4]   R[ 5]  |
     *   \  R[ 6]   R[ 7]   R[ 8]  /
     *
     */
    if (R.length == 9) {
        values[0] = (float)Math.atan2(R[1], R[4]);
        values[1] = (float)Math.asin(-R[7]);
        values[2] = (float)Math.atan2(-R[6], R[8]);
    } else {
        values[0] = (float)Math.atan2(R[1], R[5]);
        values[1] = (float)Math.asin(-R[9]);
        values[2] = (float)Math.atan2(-R[8], R[10]);
    }
    return values;
}