13、互补滤波:融合陀螺仪与加速度计,实现稳定的姿态估计

各位同学,欢迎来到第十三章。前面我们分别讲了陀螺仪和加速度计,你可能会想:这两个传感器各有优缺点,能不能把它们结合起来,取长补短?

答案是肯定的。这就是我们今天要聊的——互补滤波。说白了,就是用加速度计去修正陀螺仪的漂移,用陀螺仪去弥补加速度计的动态响应慢。两者一结合,就能得到一个相对稳定、准确的姿态估计。

核心思想:陀螺仪短期准,长期漂;加速度计长期准,短期噪。互补滤波就是让它们各司其职。

13.1 为什么需要融合?

先说说我个人的经历。早期做无人机飞控时,我试过只用陀螺仪积分算角度。刚开始几秒还行,但过了十几秒,角度就开始慢慢偏了。为什么?因为陀螺仪有零偏,积分一次,误差就累积一次。时间一长,角度就飞到天上去了。

反过来,只用加速度计呢?静止时很准,但手机一动,加速度计测到的就不仅仅是重力了,还有运动加速度。这时候算出来的角度,抖得跟筛子似的。

所以,我们需要一个方法:短期相信陀螺仪,长期相信加速度计。互补滤波就是干这个的。

13.2 互补滤波的原理

互补滤波的数学表达式其实很简单:

角度 = α × (角度 + 陀螺仪角速度 × dt) + (1 - α) × 加速度计角度

这里 α 是一个系数,范围在 0 到 1 之间。通常 α 取 0.98 左右。

你想想看:

  • α 越大,越相信陀螺仪。系统响应快,但长期会漂。
  • α 越小,越相信加速度计。系统稳定,但动态响应慢。

为什么会这样?因为陀螺仪的积分项是高频的,加速度计的角度是低频的。互补滤波本质上就是一个低通+高通的组合滤波器。

我的经验:α 取 0.98 是一个不错的起点。如果你发现角度抖动厉害,可以稍微减小 α;如果发现跟随慢,可以增大 α。具体调参时,我习惯先设 0.98,然后跑一个旋转测试,看响应是否跟手。

13.3 算法流程图

下面这张图,是我画的一个简化流程。它展示了互补滤波的核心逻辑:

互补滤波姿态估计流程图 陀螺仪 (角速度 ω) 加速度计 (ax, ay, az) 积分: θ_gyro += ω·dt 计算: θ_acc = atan2(ay, az) 互补融合 θ = α·θ_gyro + (1-α)·θ_acc 稳定姿态角 θ 反馈修正

嗯,这里要注意:反馈回路的意思是,每次融合后的角度,会作为下一次积分的初始值。这样陀螺仪的漂移就会被加速度计慢慢拉回来。

13.3 代码实现

下面是一个完整的互补滤波实现。我建议你直接复制到你的项目里试试。

public class ComplementaryFilter {
    private static final float ALPHA = 0.98f;
    private static final float DT = 0.01f; // 采样周期 10ms

    private float angle = 0.0f; // 最终融合角度

    /**
     * 更新姿态角
     * @param gyroRate 陀螺仪角速度 (rad/s)
     * @param accAngle 加速度计计算出的角度 (rad)
     * @return 融合后的角度 (rad)
     */
    public float update(float gyroRate, float accAngle) {
        // 陀螺仪积分
        float gyroAngle = angle + gyroRate * DT;

        // 互补融合
        angle = ALPHA * gyroAngle + (1.0f - ALPHA) * accAngle;

        return angle;
    }

    /**
     * 重置角度
     */
    public void reset() {
        angle = 0.0f;
    }
}

避坑指南:我曾经在采样周期 DT 上吃过亏。如果你用 10ms 采样,但实际循环跑下来是 12ms,那积分就会偏大。建议用 System.nanoTime() 精确测量两次调用之间的时间差,而不是写死 DT。

13.4 加速度计角度怎么算?

你可能注意到了,上面的代码里有个 accAngle。这个角度怎么来的?

对于绕 X 轴旋转(横滚角 Roll),公式是:

roll = atan2(ay, az)

对于绕 Y 轴旋转(俯仰角 Pitch),公式是:

pitch = atan2(-ax, sqrt(ay*ay + az*az))

这里有个细节:atan2 返回的是弧度,记得转成角度方便观察。

注意:加速度计在剧烈运动时,测到的加速度不全是重力。这时候算出来的角度是错的。互补滤波虽然能缓解,但如果你做的是高动态运动(比如无人机特技飞行),建议考虑更高级的卡尔曼滤波。

13.5 参数调优建议

互补滤波只有一个参数 α,但调起来也有讲究。我整理了一个表格,供你参考:

场景 推荐 α 说明
静止或慢速运动 0.95 ~ 0.98 更相信陀螺仪,响应快
中等运动(手持设备) 0.98 ~ 0.99 平衡响应与稳定
高动态运动(无人机) 0.99 ~ 0.995 减少加速度计噪声影响

我个人习惯从 0.98 开始,然后跑一个来回旋转的测试。如果角度回不到原点,说明 α 太大,漂移没被完全修正。如果角度抖动明显,说明 α 太小,加速度计噪声进来了。

13.6 实际项目中的坑

最后分享几个我踩过的坑:

  • 初始角度问题:互补滤波启动时,如果 angle 初始化为 0,而手机是倾斜的,那前几帧会有跳变。我建议启动时先用加速度计算一个初始角度赋值给 angle。
  • 坐标系对齐:陀螺仪和加速度计的坐标系可能不一致。比如有的传感器 X 轴方向相反。记得先校准,否则融合出来的角度是错的。
  • 采样率不匹配:如果陀螺仪和加速度计的采样率不同,比如陀螺仪 100Hz,加速度计 50Hz,那融合时要注意对齐时间戳。我一般用最近一次的加速度计数据。

总结一下:互补滤波不是最完美的算法,但它简单、高效、够用。对于大多数 Android 传感器应用——比如手机屏幕旋转、VR 头显、平衡车——它都能给出不错的结果。如果你追求更高精度,可以学卡尔曼滤波或 Mahony 滤波,但那是后话了。

好了,这一章就到这里。代码已经给你了,参数也给了建议。剩下的就是动手试试。记住,纸上得来终觉浅,绝知此事要躬行。

公众号:蓝海资料掘金营,微信deep3321