安卓设备上方向传感器的方位角/偏航和翻滚值不一致

5

我在获取方向传感器读数时遇到了问题。 传感器读数似乎不可靠,所以我使用了两个免费的传感器测试应用程序(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个回答

6
我认为在设备不平放时定义方向角的最佳方式是使用比从SensorManager.getOrientation(...)获取到的标准欧拉角更合适的角度坐标系。我建议使用我在这里描述的那个。我还在这里的答案中放了一些实现它的代码。除了对方位角的良好定义之外,它还具有更好的俯仰角定义,该方法将其定义为沿任意轴旋转出水平面的旋转。
您可以从第一段中提供的两个链接中获得完整的详细信息。然而,在总结中,您从SensorManager.getRotationMatrix(...)获得的旋转矩阵R如下所示:
其中(Ex,Ey,Ez),(Nx,Ny,Nz)和(Gx,Gy,Gz)是指向东,北和重力方向的向量。然后您想要的方位角由以下公式给出:

当从北向移动时,它只提供-90度至90度的方位角。我该如何获得0至359度范围内的方位角? - Master
2
@Hoosier 很好的问题,因为正如你所说,通常的反正切函数返回的数字在-90到90度之间。答案是使用Atan2(http://en.wikipedia.org/wiki/Atan2),它在我使用的两种语言(即C#和Java)中都可用。 - Stochastically
2
谢谢。它以这种方式工作。mAzimuthAngleNotFlat = (int) Math.toDegrees(Math .atan2((rotationMatrix[1] - rotationMatrix[3]), (rotationMatrix[0] + rotationMatrix[4]))); if (mAzimuthAngleNotFlat < 0) { mAzimuthAngleNotFlat += 360; } - Master
在你的解决方案中,你没有使用remapCoordinates()。我在你的帖子(http://math.stackexchange.com/questions/381649/whats-the-best-3d-angular-co-ordinate-system-for-working-with-smartphone-apps/382048#382048)中读到你认为`remapCoordinates()`不是一个好的解决方案。但是在你的例子中,roll也会影响azimuth。那么如何在你的方法中忽略roll呢? - unlimited101
重力还是反重力? - Xyz
我一直想看到有人发明出一台反重力机器,@Xyz,如果你能研究出来,请告诉我 :-) - Stochastically

5
这完全取决于设备的方向。如果设备不是平放的,有一些旋转,即不是完全的纵向或横向,则方位角是错误的。在这种情况下,在调用getOrientation之前,您必须调用remapCoordSystem。您还应该至少过滤加速度计值。
有关详细信息,请参见我的答案:将设备中的磁场X、Y、Z值转换为全局参考框架
有关平坦度的含义,请参见如何使用Android中的加速度计测量手机在XY平面上的倾斜度

您应按以下方式测试您的应用程序:

  1. 将设备放平并启动您的应用程序,将您的值与其他应用程序进行比较。每个读数约为30秒,请在此步骤中不要移动设备。
    我的猜测:在这一步中,您的值应该与其他应用程序的值相差不大,但开始时可能不太稳定。

  2. 当您的应用程序正在运行时,将设备旋转到新位置,等待值变得稳定(不必是相同的值,但变化约为1或2度)。在相同的位置将您的值与其他应用程序的值进行比较。重复同样的操作,但同时运行其他应用程序时进行旋转。
    我的猜测:您的稳定值应该与其他应用程序的值相差不大,但需要更长时间才能使您的值稳定。此外,在停留在新位置时,您的应用程序前几个值与稳定值之间的差异比其他应用程序更大。

  3. 现在在注释 //dump to logcat 之前添加以下代码,并像处理方向那样记录它
    float rotation = (float) Math.atan2(RotationMatrix[6], RotationMatrix[7]);
    将您的设备放在一个盒子里,使其保持竖直状态,以便它不能旋转。确保上述给出的旋转值尽可能接近0(它会有一点跳动)。
    现在使用竖直设备重复步骤1和2。
    我的猜测是,您的结果将与我对步骤1和2的猜测相同。

  4. 将您的设备放在一个盒子里,使其保持倾斜的竖直状态,以便它不能进一步旋转。确保上述给出的旋转值尽可能接近25或-25(它会有一点跳动)。
    现在使用倾斜竖直设备重复步骤1。
    我的猜测是,您的结果将与其他应用程序非常不同。
    您可以使用不同的旋转值重复步骤4。

如果我的猜测是正确的,请回来,我会给您解释。同时,您可以阅读我在Magnetic Fields, Rotation Matrix And global coordinates中的答案。


谢谢您的回复,但我认为它并不能解决我的问题。我已经在原问题底部添加了更多信息。此外,我认为过滤器无法解决这个问题,因为这些错误是持续存在的。过滤器可以帮助解决间歇性的错误读数。不管怎样,感谢您的链接,让我更好地理解了传感器的含义。 - gisking
你说得对,当手机平放时一切正常,而竖立时就会出现问题。我已经开始使用Android的Sensor.TYPE_GRAVITY,我认为这意味着我不需要担心过滤的问题了。现在唯一让我感到困惑的是,当我的应用程序在手机竖立时失败时,免费的应用程序如何能够一致地定义方位角、俯仰角和翻滚角。如果有答案,我将非常感激。 - gisking
如果您发布另一个问题并在此处评论以提供问题链接,我将为您提供详细答案。我要求您这样做的原因是因为我不知道我编辑了多少次这个答案。我在这个网站上得到的最佳答案突然变成了维基,因为我编辑得太频繁。我因为给出正确、清晰和有用的答案而受到了惩罚。 - Hoan Nguyen
谢谢,我现在已经发布了剩下的问题:https://dev59.com/7GUo5IYBdhLWcg3w9TLb - gisking
我正在撰写答案。 - Hoan Nguyen

3
我可以回答部分问题。当设备平放在桌子上时,一切都会很好,但是当设备竖立且俯仰角为正或负90度时,您将会遇到万向锁定。在这种情况下,方位角和横滚角是未定义的。在这种情况下,方位角和横滚角意味着相同的事情,如果您查看数学公式,您会发现当俯仰角= +90度时,方位角+横滚角被定义,或者当俯仰角= -90度时,方位角-横滚角被定义,但是它们两个单独定义不了。在您给出的示例中,请注意您的应用程序和其他应用程序之间的方位角+横滚角之和大致相同。
此外,您可以使用Android Sensor.TYPE_GRAVITY,这样您就不需要对加速度计读数进行低通滤波器处理。Sensor.TYPE_GRAVITY是通过其他传感器计算出来的传感器,旨在消除加速度计传感器的加速度影响。但是,如果您希望您的应用程序在引入Sensor.TYPE_GRAVITY之前的旧版Android上运行,则需要自己完成工作。

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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接