当我的Android设备不平放时,我应该如何计算方位角、俯仰角和方向?

15
我正在使用Android重力和磁场传感器通过SensorManager.getRotationMatrix和SensorManager.getOrientation来计算方向。这给了我方位、俯仰和定向数字。当设备平放在桌子上时,结果看起来合理。

然而,在清单文件中,我已经禁用了竖屏和横屏之间的切换,这样getWindowManager().getDefaultDisplay().getRotation()始终为零。当我将设备旋转90度以使其垂直时,我遇到了麻烦。有时数字似乎相当错误,我意识到这与陀螺锁定有关。然而,其他应用程序似乎没有这个问题。例如,我将我的应用程序与两个免费的传感器测试应用程序(Sensor Tester (Dicotomica)Sensor Monitoring (R's Software))进行了比较。当设备平放时,我的应用程序与这些应用程序一致,但是当我将设备旋转到垂直位置时,可能会出现显着差异。这两个应用程序似乎彼此一致,那么它们是如何解决这个问题的?

3个回答

11
在设备不平放时,需要在调用getOrientation之前调用remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR)getOrientation返回的方位角是通过将设备单位Y轴正交投影到世界东北平面上,然后计算所得投影向量与北轴之间的角度得出的。
现在我们通常认为方向是指后置摄像头指向的方向。这是-Z的方向,其中Z是指向屏幕外的设备轴。当设备平放时,我们不考虑方向并接受任何给定的方向。但当设备不平放时,我们期望它是-Z的方向。但是getOrientation计算了Y轴的方向,因此需要在调用getOrientation之前交换Y和Z轴。这正是remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR)所做的,它保持X轴不变,并将Z映射到Y
那么如何知道何时需要进行映射呢?您可以通过检查来确定。
float inclination = (float) Math.acos(rotationMatrix[8]);
if (result.inclination < TWENTY_FIVE_DEGREE_IN_RADIAN 
            || result.inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
{
    // device is flat just call getOrientation
}
else
{
    // call remap
}

上面的倾斜度是指设备屏幕与世界东北平面之间的角度。它显示了设备的倾斜程度。


感谢回复。然而,这不是那两个免费传感器应用程序所做的,因为当倾斜超过25(或155度)时,按照您的建议,俯仰和滚转数字会跳动,因为轴的切换。免费传感器应用的俯仰和滚转数字不会跳动。此外,将轴切换到AXIS_X和AXIS_Z仅适用于向一个方向倾斜,在其他方向上倾斜会导致不正确的方位角跳跃(尽管无疑可以通过调整要切换的轴来纠正)。 - gisking
对于俯仰和横滚值,您不需要调用remap函数,只需要为方位角调用。我不明白您所说的向另一个方向倾斜是什么意思?如果您的意思是屏幕没有面向您,那么方向肯定与屏幕面向您时相反。 - Hoan Nguyen
谢谢您的想法,但是另一个答案似乎提供了更一致的方法。 - gisking
这是几乎每篇AR应用程序帖子中都写的一般方法,我还从Play商店的一些3D指南针中检查了它。每个应用程序都提供相同的方位角,但在设备平放时获得的值与此有所不同。差异约为10度。我还没有能够解决这个问题。当设备放置在表面上或平放时,您无法获得相同的结果。 - Thracian

8
当设备不平放时,定义方位角的最佳方法是使用比从SensorManager.getOrientation(...)获取的标准欧拉角更合适的角度坐标系。我建议使用我在math.stackexchange.com上描述的那个。我还在这里的一个答案中放置了一些实现它的代码。除了良好的方位角定义外,它还具有俯仰角的定义,这正是另一个答案中提到的Math.acos(rotationMatrix[8])给出的角度。
您可以从第一段提供的两个链接中获取详细信息。然而,简而言之,您从SensorManager.getRotationMatrix(...)获得的旋转矩阵R
其中(Ex,Ey,Ez),(Nx,Ny,Nz)和(Gx,Gy,Gz)是指向东、北和向重力方向的向量。然后您想要的方位角由以下公式给出

1
不幸的是,这对我从SensorManager.getRotationMatrixFromVector(使用传感器TYPE_ROTATION_VECTOR)获得的旋转矩阵无效。当我计算double a = Math.toDegrees(Math.atan((r[1] - r[3]) / (r[0] + r[4])));(其中r是旋转矩阵)时,我总是得到-90 <= a <= 90的角度。 - medihack

0
我会添加一个对我有用的示例,使用Hoan的答案,并突出一些现在可用的新资源,这些资源可能有助于查看需要使用陀螺仪传感器的应用程序。
首先,有一个Google Code实验室可用,其中包含一个示例应用程序-我发现已完成的示例应用程序非常有用,可以理解设备的行为。
应用程序显示如下:

enter image description here

这些链接(截至撰写本文时)提供了代码实验室和最终应用程序版本:

特别是这使您可以进行以下实验(比描述更容易...):

  • 将设备平放在桌子上,并尝试旋转,同时将顶部和底部抬起(即将显示器的顶部(通常是前置摄像头)从桌子上抬起,而将底部(如果有一个主页按钮)留在桌子上。同样,将设备的左右边缘抬起。观察应用程序中的方位角、俯仰角和翻滚角值。

  • 将设备竖直放置在衣柜门上,再次尝试移动它。打开门也可以看到效果。旋转到横向以查看差异。

我认为您可能从以上内容中看到的关键是,当平放时,一切都非常简单且运作良好,而不平放时则变得复杂且相互关联。

具体来看,我需要在纵向肖像模式下显示俯仰角和翻滚角,以下方法适用于我:

 when (event?.sensor?.type) {
            Sensor.TYPE_ROTATION_VECTOR -> {

                // Calculate the rotation matrix
                val rotMatrix = FloatArray(9)
                var rotationMatrixAdjusted = FloatArray(9)
                val azimuthChanged: (Float) -> Unit
                val orientation = FloatArray(3)
                SensorManager.getRotationMatrixFromVector(rotMatrix, event.values);
                SensorManager.remapCoordinateSystem(rotMatrix,
                        SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X,
                        rotationMatrixAdjusted);
                SensorManager.getOrientation(rotationMatrixAdjusted, orientation)

                val rollAngleRadians = orientation[1]
                val pitchAngleRadians = orientation[2]
                              
                //Report results back to listener
                thisListener.onRollPitchEvent(rollAngleRadians, pitchAngleRadians)
            }
        }

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