4. 状态估计模块:卡尔曼滤波、扩展卡尔曼滤波、粒子滤波在飞控中的实现架构

状态估计,说白了就是飞控的「眼睛」和「耳朵」。

传感器数据有噪声,IMU会漂,GPS会跳。你想想看,如果直接把原始数据拿来做控制,飞机会抖成什么样?我见过一个新手,直接把加速度计数据当姿态用,结果飞机在天上跳了一支「霹雳舞」。

所以我们需要一个模块,能把各种不靠谱的传感器数据融合起来,给出一个靠谱的状态估计。这就是状态估计模块干的事。

4.1 卡尔曼滤波:线性系统的最优解

卡尔曼滤波,说白了就是「预测+修正」的循环。我习惯把它理解成一个「有脑子的滤波器」——它不光看当前测量值,还根据系统模型预测下一步会怎样。

核心思想:

  • 预测步:根据上一时刻的状态,预测当前时刻的状态和误差协方差
  • 更新步:用当前测量值修正预测结果,得到最优估计

在飞控里,卡尔曼滤波最经典的应用就是GPS+IMU融合。我记得有一次做无人机编队,GPS信号被遮挡,纯靠卡尔曼滤波的预测值撑了3秒,飞机居然没偏太多。嗯,这就是预测步的威力。

// 卡尔曼滤波核心代码(简化版)
// 预测步
x_pred = A * x_est + B * u;          // 状态预测
P_pred = A * P_est * A' + Q;         // 协方差预测

// 更新步
K = P_pred * H' * inv(H * P_pred * H' + R);  // 卡尔曼增益
x_est = x_pred + K * (z - H * x_pred);       // 状态更新
P_est = (I - K * H) * P_pred;                // 协方差更新

避坑指南:我曾经在调Q矩阵(过程噪声协方差)时,把值设得太小,结果滤波器对测量值过度信任,GPS一抖动,姿态就跟着跳。后来我总结了一个经验:Q矩阵先给一个保守值,然后慢慢调大,直到响应速度和噪声抑制达到平衡。

4.2 扩展卡尔曼滤波:非线性系统的利器

卡尔曼滤波假设系统是线性的。但飞控里哪有那么多线性系统?姿态四元数、地球坐标系转换,全是非线性。这时候就得请出扩展卡尔曼滤波(EKF)了。

EKF的思路很简单:把非线性函数在估计点附近做一阶泰勒展开,也就是求雅可比矩阵。说白了就是「用切线近似曲线」。

EKF vs KF 的关键区别:

对比项 卡尔曼滤波 扩展卡尔曼滤波
系统模型 线性 非线性(需线性化)
状态转移 矩阵乘法 非线性函数+雅可比
计算量 中(需计算雅可比)
适用场景 GPS+IMU融合 姿态估计、视觉惯性里程计

我建议在飞控里,姿态估计用EKF是标配。为什么呢?因为四元数动力学方程是非线性的,用线性KF会引入不可忽略的误差。我曾经试过用线性KF做姿态估计,结果在高速旋转时,估计值直接发散——嗯,那次炸机让我记住了这个教训。

// EKF中雅可比矩阵的计算示例(姿态估计)
// 状态向量: [q0, q1, q2, q3, bgx, bgy, bgz] 四元数+陀螺零偏
// 状态转移函数 f(x) 对状态 x 求雅可比

// 简化后的雅可比矩阵(部分)
F[0][0] = 1;  F[0][1] = -dt/2 * wx;  F[0][2] = -dt/2 * wy;  F[0][3] = -dt/2 * wz;
F[1][0] = dt/2 * wx;  F[1][1] = 1;  F[1][2] = dt/2 * wz;  F[1][3] = -dt/2 * wy;
// ... 实际代码会更复杂,这里只展示思路

注意:EKF的线性化误差在强非线性系统中会累积。如果你发现滤波器在高动态场景下性能下降,可以考虑用无迹卡尔曼滤波(UKF)或粒子滤波。我个人习惯在四旋翼飞控中用EKF,但在固定翼或倾转旋翼机中,我会切换到UKF。

4.3 粒子滤波:非线性非高斯问题的终极方案

粒子滤波,说白了就是「用一堆随机点去逼近真实状态分布」。你想想看,如果系统是非线性、非高斯的,EKF的线性化假设就不成立了。这时候粒子滤波就派上用场了。

粒子滤波的核心思想:用N个带权重的粒子来表示状态的后验概率分布。每个粒子就是一个假设的状态,权重表示这个假设的可信度。

粒子滤波的四个步骤:

  1. 初始化:在状态空间撒N个粒子,权重均匀
  2. 预测:每个粒子根据系统模型向前传播
  3. 更新:根据测量值更新每个粒子的权重
  4. 重采样:根据权重重新采样,权重高的粒子多复制,权重低的粒子被淘汰

我在项目中遇到过一个问题:用粒子滤波做室内定位,粒子数设了1000个,结果在STM32F4上跑,帧率只有5Hz。后来我把粒子数降到200个,配合自适应重采样策略,帧率提到了20Hz,精度损失不到5%。

// 粒子滤波重采样(系统重采样法)
void resample(Particle* particles, int N) {
    float cum_weights[N];
    cum_weights[0] = particles[0].weight;
    for (int i = 1; i < N; i++) {
        cum_weights[i] = cum_weights[i-1] + particles[i].weight;
    }
    
    float step = cum_weights[N-1] / N;
    float start = rand_float() * step;
    
    Particle new_particles[N];
    int idx = 0;
    for (int i = 0; i < N; i++) {
        float target = start + i * step;
        while (cum_weights[idx] < target) idx++;
        new_particles[i] = particles[idx];
        new_particles[i].weight = 1.0 / N;  // 重置权重
    }
    memcpy(particles, new_particles, sizeof(Particle) * N);
}

经验之谈:我曾经在无人机避障系统中用粒子滤波做障碍物跟踪。一开始粒子数设了500个,效果很好但CPU占用太高。后来我发现,在障碍物运动平缓时,100个粒子就够了;只有在快速机动时才需要500个。于是我做了动态粒子数调整——嗯,这个技巧让CPU占用降了60%。

4.4 三种滤波器的选型架构

在实际飞控中,我不会只用一种滤波器。我习惯把三种滤波器组合起来,各司其职。

飞控状态估计模块架构 传感器层 IMU (加速度计+陀螺仪) GPS / 气压计 / 磁力计 视觉 / 激光雷达 数据预处理 & 时间同步 卡尔曼滤波 (KF) GPS+IMU融合 位置/速度估计 扩展卡尔曼 (EKF) 姿态估计 四元数+零偏 粒子滤波 (PF) 障碍物跟踪 室内定位 状态估计输出 姿态 / 位置 / 速度 / 障碍物位置

从架构图可以看出,我习惯把三种滤波器并行部署:

  • KF 处理线性度好的传感器融合(GPS+IMU位置估计)
  • EKF 处理非线性姿态估计(核心模块,必须稳定)
  • PF 处理强非线性、多模态的跟踪问题(避障、室内定位)

性能考量:粒子滤波的计算量是O(N),N是粒子数。在嵌入式平台上,我建议N不要超过500。如果资源紧张,可以考虑用UKF替代PF——UKF的计算量是O(L³),L是状态维度,对于低维状态(L<10),UKF比PF更高效。

4.5 实现架构中的关键设计模式

在实际代码中,我习惯用「策略模式」来管理不同的滤波器。这样切换滤波器时,上层代码完全不用改。

// 策略模式实现滤波器切换
typedef struct {
    void (*init)(void* self, float* init_state);
    void (*predict)(void* self, float* control, float dt);
    void (*update)(void* self, float* measurement);
    void (*get_state)(void* self, float* state_out);
} FilterOps;

// KF、EKF、PF各自实现这个接口
FilterOps kf_ops = { kf_init, kf_predict, kf_update, kf_get_state };
FilterOps ekf_ops = { ekf_init, ekf_predict, ekf_update, ekf_get_state };
FilterOps pf_ops = { pf_init, pf_predict, pf_update, pf_get_state };

// 使用时只需切换ops指针
FilterOps* current_filter = &ekf_ops;  // 默认用EKF
current_filter->predict(filter_instance, control, dt);

我的习惯:在飞控启动时,我会先跑一段「滤波器自检」——给滤波器输入已知的测试数据,检查输出是否在预期范围内。如果EKF的残差(innovation)持续偏大,我会自动切换到KF或者触发报警。这个机制救过我一次,那次IMU的零偏突然漂移,EKF差点发散,自检及时发现了问题。

好了,状态估计模块就讲到这里。三种滤波器各有千秋,选型时记住一句话:线性用KF,弱非线性用EKF,强非线性用PF。但实际工程中,我建议优先用EKF——它在大多数场景下够用,而且实现复杂度适中。粒子滤波虽然强大,但计算代价高,只在必要时才用。


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