当用户直立手持手机时,方位角读数变为相反 [英] Azimuth reading changes to opposite when user holds the phone upright

查看:27
本文介绍了当用户直立手持手机时,方位角读数变为相反的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已根据可在网上找到的常用建议实施了指南针读数.我使用 ROTATION_VECTOR 传感器类型,并使用标准 API 调用将其转换为 (azimuth, pitch, roll) 三元组.这是我的代码:

有趣的 Fragment.receiveAzimuthUpdates(方位角变化:(浮动)->单元,精度更改:(Int)->单元){val sensorManager = activity!!.getSystemService(Context.SENSOR_SERVICE)作为传感器管理器val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)!!sensorManager.registerListener(OrientationListener(azimuthChanged,accuracyChanged),传感器,10_000)}私有类 OrientationListener(private val azimuthChanged: (Float) ->单元,私有 val 精度更改:(Int)->单元) : SensorEventListener {私有 val 旋转矩阵 = FloatArray(9)私有 val 方向 = FloatArray(3)覆盖 fun onSensorChanged(event: SensorEvent) {if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) 返回SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)SensorManager.getOrientation(旋转矩阵,方向)方位角变化(方向[0])}覆盖乐趣 onAccuracyChanged(传感器:传感器,精度:Int){如果(sensor.type == Sensor.TYPE_ROTATION_VECTOR){准确度改变(准确度)}}}

当您水平拿着手机时,这会产生非常好的行为,就像您拿着真正的指南针一样.然而,当你像相机一样拿着它,直立在你面前时,读数就会崩溃.如果你把它稍微倾斜到超过直立的位置,它就会向你倾斜,方位就会变成相反的方向(突然旋转 180 度).

显然,此代码跟踪手机 y 轴的方向,在直立手机上该轴变为垂直,当手机向您倾斜时,其地面方向朝向您.

我可以做些什么来改善这种行为,使其对手机的音调不敏感?

解决方案

分析

<块引用>

显然,此代码跟踪手机 y 轴的方向,在直立手机上该轴变为垂直,当手机向您倾斜时,其地面方向朝向您.

是的,这是正确的.您可以检查 getOrientation() 的代码以了解发生了什么:

public static float[] getOrientation(float[] R, float[] values) {/**/R[ 0] R[ 1] R[ 2] * |R[ 3] R[ 4] R[ 5] |*  R[ 6] R[ 7] R[ 8]/*/values[0] = (float) Math.atan2(R[1], R[4]);...

values[0] 是你得到的方位角值.

您可以将旋转矩阵 R 解释为指向设备三个主轴的向量的分量:

  • 第 0 列:指向手机右侧
  • 的向量
  • 第 1 列:指向手机 up
  • 的向量
  • 第 2 列:指向手机正面
  • 的向量

矢量是从地球坐标系(天空)的角度描述的.

考虑到这一点,我们可以解释getOrientation()中的代码:

  1. 选择手机的up轴(矩阵第1列,存储在数组元素1、4、7中)
  2. 将其投影到地球的水平面(这很容易,只需忽略元素 7 中存储的 sky 组件)
  3. 使用atan2从向量的剩余eastnorth分量推导出角度.

这里还隐藏着另一个微妙之处:atan2 的签名是

public static double atan2(double y, double x);

注意参数顺序:y,然后是 x.但是 getOrientationeastnorth 的顺序传递参数.这实现了两件事:

  • 使 north 成为参考轴(在几何中它是 x 轴)
  • 镜像角度:几何角度是逆时针的,但方位角必须是从北到顺时针的角度

自然,当手机的轴垂直(向天空")然后超出时,其方位角会翻转 180 度.我们可以用一种非常简单的方式解决这个问题:我们将使用手机的轴.请注意以下事项:

  • 当手机水平朝北时,它的轴与轴对齐.轴,在地球坐标系中,是x"几何轴,所以我们的 0 角参考是开箱即用的.
  • 当手机向右转(向东)时,它的方位角应该上升,但它的几何角度变为负值.因此,我们必须翻转几何角度的符号.

解决方案

所以我们的新公式是这样的:

val azimuth = -atan2(R[3], R[0])

这个微不足道的改变就是你所需要的!无需调用getOrientation,只需将其应用于方向矩阵即可.

改进的解决方案

到目前为止,一切都很好.但是如果用户在横向使用手机呢?手机的轴不受影响,但现在用户将手机的左"或右"方向视为向前"(取决于用户如何转动手机).我们可以通过检查 Display.rotation 属性来纠正这个问题.如果屏幕旋转了,我们将使用手机的轴来起到与上面轴相同的作用.

所以方向监听器的完整代码变成了这样:

私有类 OrientationListener(私人 val 活动:活动,private val azimuthChanged: (Float) ->单元,私有 val 精度更改:(Int)->单元) : SensorEventListener {私有 val 旋转矩阵 = FloatArray(9)覆盖乐趣 onSensorChanged(event: SensorEvent) {if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) 返回SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)val (matrixColumn, sense) = when (val 旋转 =活动.windowManager.defaultDisplay.rotation){Surface.ROTATION_0 ->对(0, 1)Surface.ROTATION_90 ->对(1, -1)Surface.ROTATION_180 ->对(0, -1)Surface.ROTATION_270 ->对(1, 1)否则 ->错误(无效的屏幕旋转值:$rotation")}val x = sense * rotationMatrix[matrixColumn]val y = sense * rotationMatrix[matrixColumn + 3]方位角变化(-atan2(y,x))}覆盖乐趣 onAccuracyChanged(传感器:传感器,精度:Int){如果(sensor.type == Sensor.TYPE_ROTATION_VECTOR){准确度改变(准确度)}}}

使用此代码,您将获得与 Google 地图完全相同的行为.

I have implemented the compass reading according to the usual recommendations that I could find on the web. I use the ROTATION_VECTOR sensor type and I transform it into the (azimuth, pitch, roll) triple using the standard API calls. Here's my code:

fun Fragment.receiveAzimuthUpdates(
        azimuthChanged: (Float) -> Unit,
        accuracyChanged: (Int) -> Unit
) {
    val sensorManager = activity!!.getSystemService(Context.SENSOR_SERVICE)
            as SensorManager
    val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)!!
    sensorManager.registerListener(OrientationListener(azimuthChanged, accuracyChanged),
            sensor, 10_000)
}

private class OrientationListener(
        private val azimuthChanged: (Float) -> Unit,
        private val accuracyChanged: (Int) -> Unit
) : SensorEventListener {
    private val rotationMatrix = FloatArray(9)
    private val orientation = FloatArray(3)

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return
        SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
        SensorManager.getOrientation(rotationMatrix, orientation)
        azimuthChanged(orientation[0])
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        if (sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
            accuracyChanged(accuracy)
        }
    }
}

This results in behavior that's quite good when you hold the phone horizontally, like you would a real compass. However, when you hold it like a camera, upright and in front of you, the reading breaks down. If you tilt it even slightly beyond upright, so it leans towards you, the azimuth turns to the opposite direction (sudden 180 degree rotation).

Apparently this code tracks the orientation of the phone's y-axis, which becomes vertical on an upright phone, and its ground orientation is towards you when the phone leans towards you.

What could I do to improve this behavior so it's not sensitive to the phone's pitch?

解决方案

Analysis

Apparently this code tracks the orientation of the phone's y-axis, which becomes vertical on an upright phone, and its ground orientation is towards you when the phone leans towards you.

Yes, this is correct. You can inspect the code of getOrientation() to see what's going on:

public static float[] getOrientation(float[] R, float[] values) {
    /*
     *   /  R[ 0]   R[ 1]   R[ 2]  
     *   |  R[ 3]   R[ 4]   R[ 5]  |
     *     R[ 6]   R[ 7]   R[ 8]  /
     */
     values[0] = (float) Math.atan2(R[1], R[4]);
     ...

values[0] is the azimuth value you got.

You can interpret the rotation matrix R as the components of the vectors that point in the device's three major axes:

  • column 0: vector pointing to phone's right
  • column 1: vector pointing to phone's up
  • column 2: vector pointing to phone's front

The vectors are described from the perspective of the Earth's coordinate system (east, north, and sky).

With this in mind we can interpret the code in getOrientation():

  1. select the phone's up axis (matrix column 1, stored in array elements 1, 4, 7)
  2. project it to the Earth's horizontal plane (this is easy, just ignore the sky component stored in element 7)
  3. Use atan2 to deduce the angle from the remaining east and north components of the vector.

There's another subtlety hiding here: the signature of atan2 is

public static double atan2(double y, double x);

Note the parameter order: y, then x. But getOrientation passes the arguments in the east, north order. This achieves two things:

  • makes north the reference axis (in geometry it's the x axis)
  • mirrors the angle: geometrical angles are anti-clockwise, but azimuth must be the clockwise angle from north

Naturally, when the phone's up axis goes vertical ("skyward") and then beyond, its azimuth flips by 180 degrees. We can fix this in a very simple way: we'll use the phone's right axis instead. Note the following:

  • when the phone is horizontal and facing north, its right axis is aligned with the east axis. The east axis, in the Earth's coordinate system, is the "x" geometrical axis, so our 0-angle reference is correct out-of-the-box.
  • when the phone turns right (eastwards), its azimuth should rise, but its geometrical angle goes negative. Therefore we must flip the sign of the geometrical angle.

Solution

So our new formula is this:

val azimuth = -atan2(R[3], R[0])

And this trivial change is all you need! No need to call getOrientation, just apply this to the orientation matrix.

Improved Solution

So far, so good. But what if the user is using the phone in the landscape orientation? The phone's axes are unaffected, but now the user perceives the phone's "left" or "right" direction as "ahead" (depending on how the user turned the phone). We can correct for this by inspecting the Display.rotation property. If the screen is rotated, we'll use the up axis of the phone to play the same role as the right axis above.

So the full code of the orientation listener becomes this:

private class OrientationListener(
        private val activity: Activity,
        private val azimuthChanged: (Float) -> Unit,
        private val accuracyChanged: (Int) -> Unit
) : SensorEventListener {
    private val rotationMatrix = FloatArray(9)

    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return
        SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
        val (matrixColumn, sense) = when (val rotation = 
                activity.windowManager.defaultDisplay.rotation
        ) {
            Surface.ROTATION_0 -> Pair(0, 1)
            Surface.ROTATION_90 -> Pair(1, -1)
            Surface.ROTATION_180 -> Pair(0, -1)
            Surface.ROTATION_270 -> Pair(1, 1)
            else -> error("Invalid screen rotation value: $rotation")
        }
        val x = sense * rotationMatrix[matrixColumn]
        val y = sense * rotationMatrix[matrixColumn + 3]
        azimuthChanged(-atan2(y, x))
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        if (sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
            accuracyChanged(accuracy)
        }
    }
}

With this code, you're getting the exact same behavior as on Google Maps.

这篇关于当用户直立手持手机时,方位角读数变为相反的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆