2. 加速度计数据融合姿态估计

任务说明

本案例实现运动相机EIS中的基础姿态估计模块:融合三轴陀螺仪角速度与三轴加速度计测量值,通过互补滤波在频域上结合两者优势——陀螺仪短时精度高但存在积分漂移,加速度计长期稳定但易受运动加速度干扰——输出相机当前姿态的欧拉角(横滚角φ、俯仰角θ)。该输出将作为后续防抖算法中坐标系变换与画面补偿的输入。

核心算法与实现

互补滤波的核心思想是:姿态角由陀螺仪积分得到高频分量,由加速度计静态解算得到低频分量,两者按权重系数α融合。α由滤波时间常数τ与采样周期dt决定:α = τ / (τ + dt)。本实现采用旋转矩阵形式的互补滤波,避免欧拉角奇异性问题,计算流程如下:

  1. 由加速度计读数归一化后,通过叉积计算当前姿态与重力参考方向之间的误差;
  2. 将误差经比例增益Kp补偿到陀螺仪角速度上,修正积分漂移;
  3. 对修正后的角速度进行四元数积分更新;
  4. 将四元数归一化后转换为欧拉角输出。
陀螺仪 ω (rad/s) 加速度计 a (m/s²) 四元数积分 q ⊗ Δq(ω·dt) 重力对齐误差 e = a_norm × g_ref PI补偿 ω' = ω + Kp·e 欧拉角输出 φ, θ 反馈:归一化四元数旋转重力参考方向 互补滤波姿态解算流程

代码实现


#include <math.h>
#include <stdio.h>

typedef struct {
    float w, x, y, z;  // 四元数
} Quat;

typedef struct {
    float phi, theta;  // 横滚角、俯仰角(弧度)
} Euler;

// 采样周期与滤波参数
#define DT      0.01f   // 100Hz采样
#define TAU     0.5f    // 滤波时间常数
#define Kp      2.0f    // 比例增益

static Quat q = {1.0f, 0.0f, 0.0f, 0.0f};

// 四元数归一化
static void quat_normalize(Quat *q) {
    float n = sqrtf(q->w*q->w + q->x*q->x + q->y*q->y + q->z*q->z);
    if (n < 1e-6f) { q->w = 1.0f; return; }
    q->w /= n; q->x /= n; q->y /= n; q->z /= n;
}

// 互补滤波单步更新
Euler complementary_filter(float gx, float gy, float gz,
                           float ax, float ay, float az) {
    // 1. 加速度计归一化
    float a_norm = sqrtf(ax*ax + ay*ay + az*az);
    if (a_norm < 1e-6f) a_norm = 1.0f;
    ax /= a_norm; ay /= a_norm; az /= a_norm;

    // 2. 由当前四元数计算重力参考方向(旋转矩阵第三行)
    float gx_ref = 2.0f*(q.x*q.z - q.w*q.y);
    float gy_ref = 2.0f*(q.y*q.z + q.w*q.x);
    float gz_ref = q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z;

    // 3. 叉积计算误差
    float ex = ay*gz_ref - az*gy_ref;
    float ey = az*gx_ref - ax*gz_ref;
    float ez = ax*gy_ref - ay*gx_ref;

    // 4. 比例补偿到角速度
    gx += Kp * ex;
    gy += Kp * ey;
    gz += Kp * ez;

    // 5. 四元数积分(一阶龙格-库塔)
    Quat dq;
    dq.w = 0.5f * (-q.x*gx - q.y*gy - q.z*gz) * DT;
    dq.x = 0.5f * ( q.w*gx + q.y*gz - q.z*gy) * DT;
    dq.y = 0.5f * ( q.w*gy - q.x*gz + q.z*gx) * DT;
    dq.z = 0.5f * ( q.w*gz + q.x*gy - q.y*gx) * DT;

    q.w += dq.w; q.x += dq.x; q.y += dq.y; q.z += dq.z;
    quat_normalize(&q);

    // 6. 转换为欧拉角(ZYX顺序)
    Euler e;
    float sinr = 2.0f*(q.w*q.x + q.y*q.z);
    float cosr = 1.0f - 2.0f*(q.x*q.x + q.y*q.y);
    e.phi = atan2f(sinr, cosr);
    float sinp = 2.0f*(q.w*q.y - q.z*q.x);
    if (fabsf(sinp) >= 1.0f)
        e.theta = copysignf(M_PI/2.0f, sinp);
    else
        e.theta = asinf(sinp);
    return e;
}

// 测试主函数
int main() {
    // 模拟静止状态:陀螺仪零角速度,加速度计仅受重力
    float gx=0.0f, gy=0.0f, gz=0.0f;
    float ax=0.0f, ay=0.0f, az=9.81f;
    Euler e;
    for (int i = 0; i < 100; i++) {
        e = complementary_filter(gx, gy, gz, ax, ay, az);
    }
    printf("静止姿态: phi=%.4f rad, theta=%.4f rad\n", e.phi, e.theta);

    // 模拟绕X轴旋转:角速度0.5 rad/s,持续2秒
    // 加速度计仍近似指向重力(忽略运动加速度)
    gx = 0.5f; gy = 0.0f; gz = 0.0f;
    ax = 0.0f; ay = 0.0f; az = 9.81f;
    for (int i = 0; i < 200; i++) {
        e = complementary_filter(gx, gy, gz, ax, ay, az);
    }
    printf("旋转2秒后: phi=%.4f rad (理论约1.0 rad)\n", e.phi);
    return 0;
}

编译与运行


gcc -o attitude_filter attitude_filter.c -lm
./attitude_filter

验证方法

运行程序后观察输出:

  • 静止测试:前100次迭代后,phi和theta应接近0.0 rad(误差小于0.01 rad),证明滤波器无漂移且能收敛到水平姿态;
  • 旋转测试:以0.5 rad/s绕X轴旋转2秒,理论横滚角变化为1.0 rad。输出phi应接近1.0 rad(允许0.05 rad误差),证明陀螺仪积分路径正确且加速度计修正未过度干扰动态响应。

判定标准:两项测试输出均在上述容差范围内,即验证互补滤波实现正确。若静止测试出现明显漂移,检查Kp是否过小;若旋转测试角度偏差过大,检查四元数积分符号或DT设置。