Android: 设备方向计算问题

11

我试图构建一个简单的增强现实应用程序,因此我开始使用传感器数据进行工作。

根据这个线程(Android指南针示例)和示例(http://www.codingforandroid.com/2011/01/using-orientation-sensors-simple.html),使用Sensor.TYPE_ACCELEROMETERSensor.TYPE_MAGNETIC_FIELD计算方向并不是很合适。

所以我无法获得“好”的值。方位角度的值根本没有意义,因此如果我将手机倒置,该值会发生极端变化。即使我只是旋转手机,这些值也不能表示手机的方向。

是否有人知道如何根据给定的示例提高值的质量?

5个回答

24

在使用此示例应用程序时,您需要使用什么方向?根据代码中的说明,该应用程序仅支持纵向或平放在桌面上的方向,这取决于设备。"好"的意思是什么?

当旋转设备时,数值不"好"是正常的,设备坐标系统应该在纵向或者我不知道的平放方向工作(Y轴垂直于屏幕指向上方,Z轴从屏幕中心指向屏幕外,X轴垂直于Y轴向右沿屏幕方向延伸)。有了这个,旋转设备将不会旋转设备坐标系,您需要重新映射它。

但是,如果您想要在纵向方向上获取设备的方向,这是一段对我有效的好代码:

@Override
public void onSensorChanged(SensorEvent event)
{
    // It is good practice to check that we received the proper sensor event
    if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR)
    {
        // Convert the rotation-vector to a 4x4 matrix.
        SensorManager.getRotationMatrixFromVector(mRotationMatrix,
                event.values);
        SensorManager
                .remapCoordinateSystem(mRotationMatrix,
                        SensorManager.AXIS_X, SensorManager.AXIS_Z,
                        mRotationMatrix);
        SensorManager.getOrientation(mRotationMatrix, orientationVals);

        // Optionally convert the result from radians to degrees
        orientationVals[0] = (float) Math.toDegrees(orientationVals[0]);
        orientationVals[1] = (float) Math.toDegrees(orientationVals[1]);
        orientationVals[2] = (float) Math.toDegrees(orientationVals[2]);

        tv.setText(" Yaw: " + orientationVals[0] + "\n Pitch: "
                + orientationVals[1] + "\n Roll (not used): "
                + orientationVals[2]);

    }
}

您将得到标题(或方位角):

orientationVals[0]

1
记录一下,我尝试了这段代码用于3x3矩阵,但只有4x4(即float[16])才能正常工作。 - MonoThreaded

15

Tíbó的回答很好,但如果您记录滚动值,您将会得到不规则的数字。(对于AR浏览器来说,滚动很重要)

这是因为

SensorManager.remapCoordinateSystem(mRotationMatrix,
                    SensorManager.AXIS_X, SensorManager.AXIS_Z,
                    mRotationMatrix);

您需要使用不同的矩阵来进行重映射的输入和输出。以下代码对我有效,可以得到正确的滚动值:

@Override
public void onSensorChanged(SensorEvent event)
{
    // It is good practice to check that we received the proper sensor event
    if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR)
    {
        // Convert the rotation-vector to a 4x4 matrix.
        SensorManager.getRotationMatrixFromVector(mRotationMatrixFromVector, event.values);
        SensorManager.remapCoordinateSystem(mRotationMatrixFromVector,
                    SensorManager.AXIS_X, SensorManager.AXIS_Z,
                    mRotationMatrix);
        SensorManager.getOrientation(mRotationMatrix, orientationVals);

        // Optionally convert the result from radians to degrees
        orientationVals[0] = (float) Math.toDegrees(orientationVals[0]);
        orientationVals[1] = (float) Math.toDegrees(orientationVals[1]);
        orientationVals[2] = (float) Math.toDegrees(orientationVals[2]);

        tv.setText(" Yaw: " + orientationVals[0] + "\n Pitch: "
                + orientationVals[1] + "\n Roll (not used): "
                + orientationVals[2]);

    }
}

是的,你可以在源代码中检查: public static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) * @param outR * 转换后的旋转矩阵。inR 和 outR 不应该是同一个数组。 - N Kaushik

9

可能有点晚了,不过无论如何,这就是我得到方位角的方法:

private final int sensorType =  Sensor.TYPE_ROTATION_VECTOR;
float[] rotMat = new float[9];
float[] vals = new float[3];

@Override
public void onSensorChanged(SensorEvent event) {
    sensorHasChanged = false;
    if (event.sensor.getType() == sensorType){
        SensorManager.getRotationMatrixFromVector(rotMat,
                event.values);
        SensorManager
                .remapCoordinateSystem(rotMat,
                        SensorManager.AXIS_X, SensorManager.AXIS_Y,
                        rotMat);
        SensorManager.getOrientation(rotMat, vals);
        azimuth = deg(vals[0]); // in degrees [-180, +180]
        pitch = deg(vals[1]);
        roll = deg(vals[2]);
        sensorHasChanged = true;
    }
}

希望能对您有所帮助。

1
remapCoordinateSystem 调用只是恒等变换,因此它是多余的。更糟糕的是,它同时将 rotMat 用作输入和输出,而文档明确指出不应该这样做。 - Thomas

0

1
好的,那么有没有关于如何使用这个传感器的示例呢?我得到了xsin(θ/2),ysin(θ/2)和z*sin(θ/2)的值。但是我该如何获取我需要构建指南针的值呢?我是否应该再次使用getRotationMatrix?感谢你的帮助。 - Adrian Eb
X被定义为向量积Y.Z(它与设备当前位置的地面切线大致指向东方)。 Y在设备当前位置与地面切线相切并指向磁北方。 Z指向天空且垂直于地面。 拂去你的几何课本尘埃,或者谷歌一下:),你就能弄明白它了。 - Streets Of Boston

0
这是一个 Kotlin 的方法,包含了所有必要的矩阵(之前的答案不知为何省略了数组大小,这很重要)。
// This is determined from the deprecated Sensor.TYPE_ORIENTATION
var lastOrientation: FloatArray = FloatArray(3)
var lastHeading: Float = 0f
var currentHeading: Float = 0f

// This is from the non deprecated Sensor.TYPE_ROTATION_VECTOR
var lastVectorOrientation: FloatArray = FloatArray(5)
var lastVectorHeading: Float = 0f
var currentVectorHeading: Float = 0f

override fun onSensorChanged(event: SensorEvent) {
    when(event.sensor?.type) {
        null -> return
        Sensor.TYPE_ORIENTATION -> {
            lastOrientation = event.values
            lastHeading = currentHeading
            currentHeading = abs(event.values[0].roundToInt().toFloat())
        }
        Sensor.TYPE_ROTATION_VECTOR -> {
            lastVectorOrientation = event.values
            lastVectorHeading = currentVectorHeading

            val tempRotationMatrix = FloatArray(9)
            val tempOrientationMatrix = FloatArray(3)
            getRotationMatrixFromVector(tempRotationMatrix, event.values)
            remapCoordinateSystem(tempRotationMatrix, AXIS_X, AXIS_Z, tempRotationMatrix)
            getOrientation(tempRotationMatrix, tempOrientationMatrix)
            currentVectorHeading = Math.toDegrees(tempOrientationMatrix[0].toDouble()).toFloat()
            
            if(currentVectorHeading < 0) {
                currentVectorHeading += 360f//heading = 360 - abs(neg heading), which is really 360 + (-heading)
            }
        }
        else -> return
    }
}

我还提供了已弃用的Sensor.TYPE_ORIENTATION,以便任何人都可以看到两种方法之间的差异。使用已弃用的方法与更新的方法相比有几度的差异。


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