当手机倾斜时,Android的方位角度会受到干扰

9
我在使用AR视图作为指南针时遇到了一个非常烦人的问题。当我将手机竖直地握在手中(屏幕朝向我的脸),我调用 remapCoordinateSystem 函数,以便在这种情况下 pitch 的值为 0 。此时方位角(指南针功能)是完美的,但是一旦我倾斜手机,方位角就会出现问题。如果我向前弯曲,方位角会增加;如果我向后弯曲,方位角会减少。
我使用两个传感器来获取读数,Sensor.TYPE_MAGNETIC_FIELDSensor.TYPE_GRAVITY
我使用一个低通滤波器来过滤数据,它相当基础,实现方式是通过一个alpha常量,可以直接应用于传感器读取的值。
以下是我的代码:
float[] rotationMatrix = new float[9];
SensorManager.getRotationMatrix(rotationMatrix, null, gravitymeterValues,
    magnetometerValues);

float[] remappedRotationMatrix = new float[9];

SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_X,
    SensorManager.AXIS_Z, remappedRotationMatrix);

float results[] = new float[3];
SensorManager.getOrientation(remappedRotationMatrix, results);

float azimuth = (float) (results[0] * 180 / Math.PI);
if (azimuth < 0) {
    azimuth += 360;
}

float pitch = (float) (results[1] * 180 / Math.PI);
float roll = (float) (results[2] * 180 / Math.PI);

正如您所见,这里并没有什么魔法。当重力计值和磁力计值准备好使用时,我调用这段代码。
我的问题是,当我倾斜手机时,如何防止方位角疯狂变化?
我在 Google Play 商店上检查了一个免费应用程序“Compass”,它没有解决这个问题,但我希望有一个解决方案。
我有两个解决方案:
1. 使 AR 视图只在非常受限制的俯仰角度下工作,现在我有类似于 `pitch >= -5 && pitch <= 30` 的东西。如果不满足此条件,则向用户显示一个屏幕,要求他/她将手机旋转到纵向。
2. 以某种方式使用俯仰角来抑制方位角,尽管这似乎是一种相当特定于设备的解决方案,但我当然愿意听取建议。
我还可以补充说,我已经搜索了几个小时,寻找一个像 2)这样的解决方案,但都没有找到更好的解决方案。
提前感谢!

你需要低通滤波什么?当使用TYPE_GRAVITY时,无需进行滤波。 - Hoan Nguyen
@HoanNguyen 我需要对 Sensor.TYPE_MAGNETIC_FIELD 进行低通滤波吗?现在我同时对 Sensor.TYPE_MAGNETIC_FIELDSensor.TYPE_GRAVITY 进行低通滤波。 - Johan S
1
不,低通滤波器的使用是为了消除x和y方向上的加速度。getRotationMatrix需要第三个参数大致上是仅在z方向上的加速度。如果您使用TYPE_GRAVITY,则无需低通滤波器,但许多设备没有TYPE_GRAVITY,因此您需要对没有TYPE_GRAVITY的设备使用TYPE_ACCELEROMETER和低通滤波器。 - Hoan Nguyen
好的,那个结果是设备无关的吗?因为在我的Nexus 4上我有一些严重的问题。可能倾斜10-20度就会给我+-30的方位角。如果您愿意,请发布代码,好吗? - Johan S
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/34561/discussion-between-johan-s-and-hoan-nguyen - Johan S
显示剩余2条评论
1个回答

14

完整的代码请参见https://github.com/hoananguyen/dsensor
保留历史记录并平均处理,我不知道俯仰角和滚转角的正确解释,因此以下代码仅适用于方位角。

类成员

private List<float[]> mRotHist = new ArrayList<float[]>();
private int mRotHistIndex;
// Change the value so that the azimuth is stable and fit your requirement
private int mHistoryMaxLength = 40;
float[] mGravity;
float[] mMagnetic;
float[] mRotationMatrix = new float[9];
// the direction of the back camera, only valid if the device is tilted up by
// at least 25 degrees.
private float mFacing = Float.NAN;

public static final float TWENTY_FIVE_DEGREE_IN_RADIAN = 0.436332313f;
public static final float ONE_FIFTY_FIVE_DEGREE_IN_RADIAN = 2.7052603f;

onSensorChanged

@Override
public void onSensorChanged(SensorEvent event)
{
     if (event.sensor.getType() == Sensor.TYPE_GRAVITY)
     {
         mGravity = event.values.clone();
     }
     else
     {
        mMagnetic = event.values.clone();
     }

     if (mGravity != null && mMagnetic != null)
     {
          if (SensorManager.getRotationMatrix(mRotationMatrix, null, mGravity, mMagnetic))
          {
              // inclination is the degree of tilt by the device independent of orientation (portrait or landscape)
              // if less than 25 or more than 155 degrees the device is considered lying flat
              float inclination = (float) Math.acos(mRotationMatrix[8]);
              if (inclination < TWENTY_FIVE_DEGREE_IN_RADIAN 
                      || inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
              {
                  // mFacing is undefined, so we need to clear the history
                  clearRotHist();
                  mFacing = Float.NaN;
              }
              else
              {
                  setRotHist();
                  // mFacing = azimuth is in radian
                  mFacing = findFacing(); 
              }
          }
     }
}

private void clearRotHist()
{
    if (DEBUG) {Log.d(TAG, "clearRotHist()");}
    mRotHist.clear();
    mRotHistIndex = 0;
}

private void setRotHist()
{
    if (DEBUG) {Log.d(TAG, "setRotHist()");}
    float[] hist = mRotationMatrix.clone();
    if (mRotHist.size() == mHistoryMaxLength)
    {
        mRotHist.remove(mRotHistIndex);
    }   
    mRotHist.add(mRotHistIndex++, hist);
    mRotHistIndex %= mHistoryMaxLength;
}

private float findFacing()
{
    if (DEBUG) {Log.d(TAG, "findFacing()");}
    float[] averageRotHist = average(mRotHist);
    return (float) Math.atan2(-averageRotHist[2], -averageRotHist[5]);
}

public float[] average(List<float[]> values)
{
    float[] result = new float[9];
    for (float[] value : values)
    {
        for (int i = 0; i < 9; i++)
        {
            result[i] += value[i];
        }
    }

    for (int i = 0; i < 9; i++)
    {
        result[i] = result[i] / values.size();
    }

    return result;
}

太棒了!现在我的方位角要好得多了,唯一的问题是在快速移动时会跳动很多,但我想你必须接受这一点。非常感谢你的代码。不过还有一个问题:你的传感器更新频率是多少?UI / 游戏 / 哪个?谢谢。 - Johan S
你是在一个指南针应用程序中使用这段代码吗?我使用UI,而且非常频繁,顺便说一下,我用你的代码得到了很好的结果! - Johan S
2
我曾试图编写一个基于位置的AR库。但由于我想要包括高度,并在测试时发现GPS返回的高度极不准确,因此我没有完成它。 - Hoan Nguyen
好的,我会尝试使用普通界面和用户界面进行实验,非常感谢你的帮助,真的很感激! - Johan S
TYPE_GYROSCOPE会解决这个问题,你可以试一下。 - dragonfly
显示剩余2条评论

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