3、卡尔曼滤波入门:状态空间模型、预测与更新方程、一维温度滤波实战

各位同学,欢迎来到第三讲。

前两章我们聊了传感器噪声、数据融合的基本思路。今天要讲的卡尔曼滤波,可以说是嵌入式数据融合领域的「瑞士军刀」。我做了十几年嵌入式,处理过无数传感器数据,卡尔曼滤波是我工具箱里最趁手的家伙之一。

说白了,卡尔曼滤波就是一套数学公式,它能从一堆带噪声的测量值里,把真实状态给「猜」出来。而且它算得快,适合在单片机上跑。嗯,咱们今天就从最基础的一维温度滤波开始,把它的核心思想掰开揉碎了讲清楚。

3.1 状态空间模型:把问题装进数学框子里

要理解卡尔曼滤波,先得理解它看待世界的方式。它把任何动态系统都抽象成两个模型:

  • 状态方程:描述系统内部状态怎么随时间变化。
  • 观测方程:描述我们怎么通过传感器看到这个状态。

拿一维温度滤波举例。假设你有一个温度传感器,每秒采集一次温度。真实温度是 \(x_k\),传感器读数是 \(z_k\)。

状态方程可以写成:

x_k = x_{k-1} + w_k

这里 \(w_k\) 是过程噪声——温度本身会有微小波动,比如空气流动、热源干扰。我习惯把它设成高斯白噪声,均值0,方差 \(Q\)。

观测方程:

z_k = x_k + v_k

\(v_k\) 是测量噪声,来自传感器自身精度限制。方差记作 \(R\)。

你看,就这么简单。真实温度是我们要估计的「状态」,传感器读数是我们能拿到的「观测」。卡尔曼滤波要做的,就是利用这两个方程,把真实温度从噪声里「滤」出来。

核心思想:卡尔曼滤波不是凭空猜,而是结合「模型预测」和「实际观测」,做一个加权平均。权重由不确定性(方差)决定。

3.2 预测与更新方程:卡尔曼滤波的两步舞

卡尔曼滤波就两个步骤,循环往复:预测更新

3.2.1 预测步骤(Time Update)

这一步,我们用状态方程,根据上一时刻的最优估计,预测当前时刻的状态。

// 预测状态
x_pred = x_est_prev

// 预测误差协方差
P_pred = P_est_prev + Q

这里 \(x_{pred}\) 是预测值,\(P_{pred}\) 是预测的不确定性(方差)。Q 是过程噪声方差,它会让预测的不确定性随时间增大——你想想看,如果模型不准,预测时间越长,结果越不可靠。

我记得第一次在STM32上实现时,Q设得太小,滤波结果几乎不响应温度变化,反应迟钝。后来把Q调大了一点,效果就好多了。

3.2.2 更新步骤(Measurement Update)

拿到传感器新读数后,我们用观测方程来修正预测值。

// 卡尔曼增益
K = P_pred / (P_pred + R)

// 更新状态估计
x_est = x_pred + K * (z - x_pred)

// 更新误差协方差
P_est = (1 - K) * P_pred

卡尔曼增益 \(K\) 是关键。它决定了我们更相信预测值还是测量值:

  • 如果测量噪声小(R小),K接近1,我们更相信传感器读数。
  • 如果预测很准(P_pred小),K接近0,我们更相信模型预测。

说白了,卡尔曼增益就是一个动态权重,它根据当前的不确定性自动调节。

个人经验:调参时,我一般先固定R(根据传感器数据手册),然后调整Q。Q越大,滤波结果越「跟随」测量值;Q越小,结果越「平滑」。具体取多少,得看你的应用场景——是追求响应速度还是平滑度。

3.3 一维温度滤波实战:从公式到代码

理论讲完了,咱们直接上代码。下面是一个完整的一维卡尔曼滤波实现,用C语言写的,可以直接移植到任何嵌入式平台上。

#include <stdio.h>

typedef struct {
    float Q;       // 过程噪声方差
    float R;       // 测量噪声方差
    float x;       // 状态估计值
    float P;       // 估计误差协方差
    float K;       // 卡尔曼增益
} KalmanFilter;

// 初始化卡尔曼滤波器
void Kalman_Init(KalmanFilter *kf, float init_temp) {
    kf->Q = 0.01f;      // 过程噪声,根据实际情况调整
    kf->R = 0.25f;      // 测量噪声,来自传感器精度
    kf->x = init_temp;  // 初始温度估计
    kf->P = 1.0f;       // 初始估计误差,设大一点没关系
}

// 执行一步卡尔曼滤波
float Kalman_Update(KalmanFilter *kf, float measurement) {
    // 预测
    float x_pred = kf->x;           // 状态预测
    float P_pred = kf->P + kf->Q;   // 协方差预测

    // 更新
    kf->K = P_pred / (P_pred + kf->R);          // 卡尔曼增益
    kf->x = x_pred + kf->K * (measurement - x_pred); // 状态更新
    kf->P = (1.0f - kf->K) * P_pred;            // 协方差更新

    return kf->x;
}

int main() {
    KalmanFilter kf;
    Kalman_Init(&kf, 25.0f);  // 假设初始温度25°C

    // 模拟一组带噪声的温度测量值
    float measurements[] = {25.1f, 25.3f, 24.8f, 25.2f, 25.0f, 24.9f, 25.4f, 25.1f};
    int n = sizeof(measurements) / sizeof(measurements[0]);

    printf("原始测量值\t滤波后值\n");
    for (int i = 0; i < n; i++) {
        float filtered = Kalman_Update(&kf, measurements[i]);
        printf("%.2f\t\t%.2f\n", measurements[i], filtered);
    }

    return 0;
}

运行这段代码,你会看到滤波后的温度值比原始测量值平滑很多。这就是卡尔曼滤波的威力——它把噪声给「吃」掉了。

避坑指南:我曾经在一个项目中,把Q和R设得特别小,结果卡尔曼增益几乎不变,滤波变成了简单的加权平均,失去了自适应能力。记住,Q和R的比值决定了滤波器的行为,不是绝对值。

3.4 参数调优:让滤波器为你工作

卡尔曼滤波好不好用,全看参数调得怎么样。下面这个表格是我多年总结的经验值:

参数 物理含义 调大效果 调小效果 典型值范围
Q 过程噪声方差 更跟随测量值,响应快 更平滑,响应慢 0.001 ~ 0.1
R 测量噪声方差 更相信预测值,平滑 更相信测量值,波动大 0.01 ~ 1.0
P_init 初始估计误差 收敛慢 收敛快(但可能不准) 0.1 ~ 10.0

调参没有银弹。我个人的习惯是:先用传感器数据手册上的精度值作为R的初始值,然后现场调试Q。调的时候,让传感器快速变化(比如用手捂一下),看滤波结果跟不跟得上。跟得太慢就增大Q,抖得太厉害就减小Q。

小技巧:如果你不确定Q和R怎么设,可以先用R = (传感器精度)^2,Q = R/10 作为起点。然后根据实际效果微调。这个经验公式我用了很多年,基本靠谱。

3.5 本章小结

这一章我们干了三件事:

  • 理解了状态空间模型——把温度滤波问题装进数学框子。
  • 掌握了预测与更新方程——卡尔曼滤波的两步舞。
  • 实战了一维温度滤波——从公式到C代码,再到参数调优。

卡尔曼滤波入门其实不难,难的是理解它背后的思想——用不确定性来指导融合。下一章我们会把这个一维滤波器扩展到多维,处理更复杂的传感器数据。到时候你会发现,核心思想完全一样,只是矩阵运算代替了标量运算。

嗯,今天就到这里。回去把代码跑一跑,调一调参数,感受一下卡尔曼滤波的魔力。有任何问题,咱们下节课见。