12

我希望通过以下方法获取手机的当前方向:

  1. getRotationMatrix()首先通过和获取初始方向(方位角)getOrientation()
  2. 添加陀螺仪读数随时间的积分以获得当前方向。

电话方向:

手机的 xy 平面与接地平面平行固定。即,处于“边走边发短信”的方向。

" getOrientation()" 回报:

Android API 让我可以轻松地从getOrientation().

注意,此方法始终返回范围内的值:[0, -PI][o, PI]

我的问题:

由于用 表示的陀螺仪读数的积分dR可能很大,所以当我这样做时CurrentOrientation += dRCurrentOrientation可能会超过[0, -PI][o, PI]范围。

需要进行哪些操作才能始终在[0, -PI][o, PI]范围内获得当前方向?

我在 Python 中尝试过以下方法,但我高度怀疑它的正确性。

rotation = scipy.integrate.trapz(gyroSeries, timeSeries) # integration
if (headingDirection - rotation) < -np.pi:
    headingDirection += 2 * np.pi
elif (headingDirection - rotation) > np.pi:
    headingDirection -= 2 * np.pi
# Complementary Filter
headingDirection = ALPHA * (headingDirection - rotation) + (1 - ALPHA) * np.mean(azimuth[np.array(stepNo.tolist()) == i])
if headingDirection < -np.pi:
    headingDirection += 2 * np.pi
elif headingDirection > np.pi:
    headingDirection -= 2 * np.pi

评论

这不是那么简单,因为它涉及以下麻烦制造者:

  1. 方向传感器读数从0-PI,然后直接跳到+PI逐渐回到0via +PI/2
  2. 陀螺仪读数的集成也带来了一些麻烦。我应该添加dR到方向还是减去dR

在给出确认答案之前,请先参考 Android 文档。

估计的答案无济于事。

4

4 回答 4

9

方向传感器实际上是从真实的磁力计和加速度计获得读数。

我想这也许是混乱的根源。这在文档中在哪里说明?更重要的是,文档是否明确指出陀螺仪读数被忽略?据我所知,该视频中描述的方法已实现:

Android 设备上的传感器融合:运动处理的革命

这种方法使用陀螺仪并整合它们的读数。这几乎使问题的其余部分变得毫无意义。不过我会尽量回答。


方向传感器已经为您集成了陀螺仪读数,这就是您获得方向的方式。我不明白你为什么要自己做。

您没有正确集成陀螺仪读数,它比CurrentOrientation += dR(这是不正确的)更复杂。如果您需要整合陀螺仪读数(我不明白为什么,SensorManager 已经为您完成了),请阅读方向余弦矩阵 IMU:理论如何正确执行(方程式 17)。

不要尝试与欧拉角(又名方位角、俯仰角、滚动角)积分,不会有任何好的结果。

请在计算中使用四元数或旋转矩阵,而不是欧拉角。如果您使用旋转矩阵,您始终可以将它们转换为欧拉角,请参阅

Gregory G. Slabaugh 从旋转矩阵计算欧拉角

(四元数也是如此。)(在非退化的情况下)有两种表示旋转的方法,也就是说,你会得到两个欧拉角。选择您需要的范围内的那个。万向锁的情况下,欧拉角有无穷多个,见上面的PDF)。只需保证在将旋转矩阵转换为欧拉角后,您不会在计算中再次开始使用欧拉角。

目前尚不清楚您在使用互补过滤器做什么。您可以基于方向余弦矩阵 IMU:理论手稿,基本上是一个教程,实现一个非常好的传感器融合。这样做并非易事,但我认为您不会找到比这份手稿更好、更易于理解的教程。

当我根据这份手稿实现传感器融合时,我必须自己发现的一件事是可能会发生所谓的积分饱和。我通过限制TotalCorrection(第 27 页)来处理它。如果你实现了这个传感器融合,你就会明白我在说什么。



更新:在这里,我回答您在接受答案后在评论中发布的问题。

我认为指南针通过使用重力和磁场为我提供了当前方向,对吗?指南针中是否使用陀螺仪?

是的,如果手机或多或少静止至少半秒钟,您可以仅使用重力和指南针来获得良好的方向估计。这是怎么做的:谁能告诉我重力传感器是否可以作为倾斜传感器来提高航向精度?

不,指南针中不使用陀螺仪。

您能否解释一下为什么我所做的整合是错误的?我知道如果我的手机的音调向上,欧拉角就会失败。但是我的集成还有其他问题吗?

有两件不相关的事情:(i)积分应该以不同的方式进行,(ii)由于万向节锁,欧拉角很麻烦。我再说一遍,这两个是无关的。

至于集成:这是一个简单的示例,您可以如何实际查看集成有什么问题。设 x 和 y 为房间内水平面的轴。把手机拿在手里。将手机绕 x 轴(房间)旋转 45 度,然后绕 y 轴(房间)旋转 45 度。然后,从头开始重复这些步骤,但现在先绕 y 轴旋转,然后绕 x 轴旋转。手机最终以完全不同的方向结束。如果您按照您的方式进行集成,CurrentOrientation += dR您将看不到任何区别!如果您想正确进行积分,请阅读上面链接的方向余弦矩阵 IMU:理论手稿。

至于欧拉角:它们破坏了应用程序的稳定性,我不使用它们进行 3D 中的任意旋转就足够了。

我还是不明白你为什么要自己做,为什么不想使用平台提供的方向估计。机会是,你不能做得比这更好。

于 2013-08-08T15:22:50.297 回答
2

我认为您应该避免使用过时的“方向传感器”,并使用已经实现了 Invensense 的融合算法的传感器融合方法,例如 getRotationVector、getRotationMatrix,它们已经使用了陀螺仪数据。

如果您想要一个称为平衡滤波器的简单传感器融合算法(请参阅http://www.filedump.net/dumped/filter1285099462.pdf),可以使用。方法如下

http://postimg.org/image/9cu9dwn8z/

这集成了陀螺仪以获取角度,然后对结果进行高通滤波以消除漂移,并将其添加到平滑的加速度计和罗盘结果中。集成的、高通滤波的陀螺仪数据和加速度计/罗盘数据以这样的方式相加,即这两个部分相加,因此输出是有意义的单位的准确估计。对于平衡滤波器,可以调整时间常数以调整响应。时间常数越短,响应越好,但允许通过的加速度噪声越多。

要了解它是如何工作的,假设您将最新的陀螺仪数据点(以 rad/s 为单位)存储在陀螺仪中,来自加速度计的最新角度测量值存储在 angle_acc 中,并且是从最后一个陀螺仪数据到现在的时间。然后你的新角度将使用

角度 = b * (角度 + 陀螺仪*dt) + (1 - b) *(angle_acc);

例如,您可以从尝试 b = 0.98 开始。您可能还想使用快速陀螺仪测量时间 dt,这样在进行下一次测量之前,陀螺仪的漂移不会超过几度。平衡滤波器有用且易于实现,但不是理想的传感器融合方法。Invensense 的方法涉及一些巧妙的算法,可能还包括某种形式的卡尔曼滤波器。

资料来源:Professional Android Sensor Programming,Adam Stroud。

于 2013-08-08T22:29:43.327 回答
2

如果由于磁干扰导致方位角值不准确,据我所知,您无法采取任何措施来消除它。如果 TYPE_GRAVITY 不可用,要获得稳定的方位角读数,您需要过滤加速度计值。如果 TYPE_GRAVITY 不可用,那么我很确定该设备没有陀螺仪,因此您可以使用的唯一滤波器是低通滤波器。以下代码是使用 TYPE_GRAVITY 和 TYPE_MAGNETIC_FIELD 的稳定指南针的实现。

public class Compass  implements SensorEventListener
{
    public static final float TWENTY_FIVE_DEGREE_IN_RADIAN = 0.436332313f;
    public static final float ONE_FIFTY_FIVE_DEGREE_IN_RADIAN = 2.7052603f;

    private SensorManager mSensorManager;
    private float[] mGravity;
    private float[] mMagnetic;
    // If the device is flat mOrientation[0] = azimuth, mOrientation[1] = pitch
    // and mOrientation[2] = roll, otherwise mOrientation[0] is equal to Float.NAN
    private float[] mOrientation = new float[3];
    private LinkedList<Float> mCompassHist = new LinkedList<Float>();
    private float[] mCompassHistSum = new float[]{0.0f, 0.0f};
    private int mHistoryMaxLength;

    public Compass(Context context)
    {
         mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
         // Adjust the history length to fit your need, the faster the sensor rate
         // the larger value is needed for stable result.
         mHistoryMaxLength = 20;
    }

    public void registerListener(int sensorRate)
    {
        Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (magneticSensor != null)
        {
            mSensorManager.registerListener(this, magneticSensor, sensorRate);
        }
        Sensor gravitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
        if (gravitySensor != null)
        {
            mSensorManager.registerListener(this, gravitySensor, sensorRate);
        }
    }

    public void unregisterListener()
    {
         mSensorManager.unregisterListener(this);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy)
    {

    }

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

    private void getOrientation()
    {
        float[] rotMatrix = new float[9];
        if (SensorManager.getRotationMatrix(rotMatrix, null, 
            mGravity, mMagnetic))
        {
            float inclination = (float) Math.acos(rotMatrix[8]);
            // device is flat
            if (inclination < TWENTY_FIVE_DEGREE_IN_RADIAN 
                || inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
            {
                float[] orientation = sensorManager.getOrientation(rotMatrix, mOrientation);
                mCompassHist.add(orientation[0]);
                mOrientation[0] = averageAngle();
            }
            else
            {
                mOrientation[0] = Float.NAN;
                clearCompassHist();
            }
        }
    }

    private void clearCompassHist()
    {
        mCompassHistSum[0] = 0;
        mCompassHistSum[1] = 0;
        mCompassHist.clear();
    }

    public float averageAngle()
    {
        int totalTerms = mCompassHist.size();
        if (totalTerms > mHistoryMaxLength)
        {
            float firstTerm = mCompassHist.removeFirst();
            mCompassHistSum[0] -= Math.sin(firstTerm);
            mCompassHistSum[1] -= Math.cos(firstTerm);
            totalTerms -= 1;
        }
        float lastTerm = mCompassHist.getLast();
        mCompassHistSum[0] += Math.sin(lastTerm);
        mCompassHistSum[1] += Math.cos(lastTerm);
        float angle = (float) Math.atan2(mCompassHistSum[0] / totalTerms, mCompassHistSum[1] / totalTerms);

        return angle;
    }
}

在您的活动中实例化一个 Compass 对象,例如 onCreate、onResume 中的 registerListener 和 onPause 中的 unregisterListener

private Compass mCompass;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    mCompass = new Compass(this);
}

@Override
protected void onPause()
{
    super.onPause();

    mCompass.unregisterListener();
}

@Override
protected void onResume()
{
    super.onResume();

    mCompass.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
}
于 2013-08-12T04:03:23.353 回答
2

最好让android的方向检测实现来处理它。现在,是的,您得到的值是从 -PI 到 PI,您可以将它们转换为度数 (0-360)。一些相关部分:

保存要处理的数据:

@Override
public void onSensorChanged(SensorEvent sensorEvent) {
    switch (sensorEvent.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            mAccValues[0] = sensorEvent.values[0];
            mAccValues[1] = sensorEvent.values[1];
            mAccValues[2] = sensorEvent.values[2];
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            mMagValues[0] = sensorEvent.values[0];
            mMagValues[1] = sensorEvent.values[1];
            mMagValues[2] = sensorEvent.values[2];
            break;
    }

}

计算横滚、俯仰和偏航(方位角)。mR并且mIarrys 保存旋转和倾斜矩阵,mO是一个临时数组。该数组mResults最后以度为单位的值:

    private void updateData() {
    SensorManager.getRotationMatrix(mR, mI, mAccValues, mMagValues);

    /**
     * arg 2: what world(according to app) axis , device's x axis aligns with
     * arg 3: what world(according to app) axis , device's y axis aligns with
     * world x = app's x = app's east
     * world y = app's y = app's north
     * device x = device's left side = device's east
     * device y = device's top side  = device's north
     */

    switch (mDispRotation) {
        case Surface.ROTATION_90:
            SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, mR2);
            break;
        case Surface.ROTATION_270:
            SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, mR2);
            break;
        case Surface.ROTATION_180:
            SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_MINUS_X, SensorManager.AXIS_MINUS_Y, mR2);
            break;
        case Surface.ROTATION_0:
        default:
            mR2 = mR;
    }

    SensorManager.getOrientation(mR2, mO);


    //--upside down when abs roll > 90--
    if (Math.abs(mO[2]) > PI_BY_TWO) {
        //--fix, azimuth always to true north, even when device upside down, realistic --
        mO[0] = -mO[0];

        //--fix, roll never upside down, even when device upside down, unrealistic --
        //mO[2] = mO[2] > 0 ? PI - mO[2] : - (PI - Math.abs(mO[2]));

        //--fix, pitch comes from opposite , when device goes upside down, realistic --
        mO[1] = -mO[1];
    }

    CircleUtils.convertRadToDegrees(mO, mOut);
    CircleUtils.normalize(mOut);

    //--write--
    mResults[0] = mOut[0];
    mResults[1] = mOut[1];
    mResults[2] = mOut[2];
}
于 2013-08-15T07:41:12.943 回答