打RM比赛的同学应该都有这种经历:明明代码逻辑看起来没问题,陀螺仪解算出来的角度却总是飘;明明视觉给的目标坐标很准,云台打过去就是差那么一截;超级电容的功率闭环调了半天,电压还是上下抖。这些问题看着是控制问题,追到根上,都是同一个东西没吃透——你手里的数据不是真值,是带噪声的观测,而你处理不确定性的数学工具没跟上。
卡尔曼滤波就是解决这类问题的经典工具,但绝大多数电控新手一上来就啃那五个公式,直接被矩阵、协方差、状态转移砸晕,最后只能复制开源代码改参数,滤波效果好坏全凭运气。我见过太多队友卡在这一步,所以我准备用几篇文章把卡尔曼滤波从头到尾拆开讲。这一篇先打地基,重点讲概率统计基础——卡尔曼滤波的每一个公式背后,都是概率统计的思想在支撑。把这一篇吃透,后面讲协方差矩阵、讲状态预测、讲测量更新,你才能真正看懂每一步在干什么,而不是只会调参。
这篇内容的目标读者很明确:准备入门卡尔曼滤波的RM电控队员、在视觉或嵌入式方向处理传感器数据的学生,以及所有想系统搞懂卡尔曼滤波原理的朋友。我会尽量用大白话和代码实验把概念讲透,保证你有高等数学和线代基础就能跟上。
1. 为什么RM电控要先补概率统计
1.1 传感器数据不是“真值”,是“观测”
刚进实验室那会儿,我一直有一个很天真的想法:陀螺仪输出什么角度,车当前就是什么角度;编码器读到的速度是多少,轮子就是在以多少速度转。直到有一次调底盘,我把编码器数值直接用进PID,结果小车低速时一顿一顿地走,怎么调参数都改善不大。后来才发现,低速时编码器在相邻采样周期之间往往只增几个脉冲,量化噪声和传感器本身的误差叠加在一起,导致速度反馈本身就带毛刺,PID拿到这种反馈自然输出不稳。
这是RM电控里最常见的“隐性坑”:机械结构、电子噪声、AD采样精度、电磁干扰,都会让传感器的输出偏离真实值。我们拿到手的不是一个精确的数字,而是“真实值+噪声”的混合体。噪声是随机的,我们不能精确知道真实值是多少,但我们可以用概率分布来描述它对观测的影响。这就是概率统计进入电控领域的第一个入口:你不可能消除噪声,但你可以描述噪声,并在此基础上估计真实值。
卡尔曼滤波解决的问题,本质上就是“如何在带噪声的观测中,估计出系统的真实状态”。它不追求单次读数有多准,而是通过多帧数据的融合,把真实状态的不确定性不断缩小。而要量化“不确定性”,概率统计是唯一自然的语言。没有这个认知,后面所有公式都只是符号游戏。
1.2 卡尔曼滤波=预测+修正,概率统计是它的语法
卡尔曼滤波的核心思想可以压缩成一句话:先根据运动模型预测状态,再用传感器观测修正预测。这听起来简单,但两个问题随之而来:预测不可能完全准确,因为模型有误差,控制输入有噪声;观测也不可能完全准确,因为传感器有噪声。那预测和修正各自应该信多少,怎么融合?
答案就藏在高斯分布和贝叶斯思想里。卡尔曼滤波假设系统状态服从高斯分布,预测得到的是一个高斯分布,观测得到的是另一个高斯分布,两者相乘之后依然是高斯分布,这个新的高斯分布的均值就是融合后的最优估计。这个“相乘”的数学操作,就是概率统计里的贝叶斯公式。
所以你看,卡尔曼滤波的每一步都能对应到概率统计概念:状态均值是随机变量的期望,协方差矩阵描述状态的不确定性,过程噪声和观测噪声是方差,预测是对随机变量做线性变换,更新是用条件概率求后验。这一篇把概率统计的底子打好,后面再讲卡尔曼的五个公式,你会有一种“原来如此”的通透感,而不是背公式背得云里雾里。
2. 随机变量、期望与方差:数据背后的“平均水平”与“波动幅度”
2.1 随机变量:把不确定的世界翻译成数学
我们在代码里定义float angle = imu.read();,这个angle在数学上就是一个随机变量。它的真实取值不完全确定——你连续读100次IMU,每次得到的数值都略有不同,这些不同不是程序逻辑造成的,而是传感器内部电路噪声、量化误差、环境扰动等随机因素叠加的结果。
随机变量不是“没有规律”,而是“有分布规律的未知数”。它可能取某个范围内的任意值,但取不同值的概率是不同的。比如一个性能正常的陀螺仪,静止时测量零偏可能在-0.5°到+0.5°之间,但大概率集中在0°附近,偏离越远概率越低。这种“中间多、两边少”的分布形态,在数学里就是高斯分布(正态分布)的典型特征。
这里要敲一下黑板:随机变量描述的是数据生成的过程,并不是某一次具体的采样值。我们在电控代码里每调用一次传感器读取函数,就是从某个随机变量的一次“抽样”,而概率统计就是研究这些抽样整体规律的学科。搞懂这个区别很关键,因为卡尔曼滤波处理的正是“多次抽样后如何估计真实状态”的问题。
2.2 期望与方差:平均水平是均值,波动大小是方差
期望(均值)描述随机变量的中心位置。如果我把IMU静止状态读取1000次,把读数求平均,得到的结果就接近这个随机变量的数学期望。在实际中,陀螺仪出厂时标定的零偏就是通过大量测量求均值得到的。期望告诉我们:如果只看平均水平,这个传感器读出来的数大概是多少。
但光有期望不够。两个传感器可能期望都是0°,一个数据波动在±0.1°以内,另一个波动在±5°之间,后者的噪声显然更大,不能直接用于精密控制。方差 (Var(X)=E[(X-\mu)^2]) 就是用来量化这个波动程度的:它计算每个样本偏离均值的平方的平均值,偏离越大,方差越大。实际工程中更常用标准差 (\sigma=\sqrt{Var(X)}),因为它的量纲和原始数据一致,比如“陀螺仪零漂标准差是0.02°”,听起来直观,也方便和阈值做比较。
在卡尔曼滤波里,方差和标准差还有一个更深刻的作用:它们代表了对某个估计的“信任程度”。方差小,说明这个估计比较靠谱,融合时应该给它更大的权重;方差大,说明这个估计很不确定,融合时应该少信它一点。这个“方差小多信、方差大少信”的原则,就是卡尔曼滤波中卡尔曼增益的直觉来源。
2.3 小实验:用Python感受期望和方差
理论知识容易飘,跑一遍代码就很扎实。下面用Python生成一组带噪声的位置测量数据,模拟IMU或编码器输出,然后计算它的均值和方差。
import numpy as np # 设置随机种子,保证结果可复现 np.random.seed(42) # 模拟真实值为 100,测量噪声为标准差 0.5 的高斯噪声 true_value = 100.0 measurements = true_value + np.random.normal(0, 0.5, 1000) mean_value = np.mean(measurements) variance_value = np.var(measurements) std_value = np.std(measurements) print(f"测量均值: {mean_value:.4f}") print(f"测量方差: {variance_value:.4f}") print(f"测量标准差: {std_value:.4f}") # 对比:测量值离真实值的最大偏差 max_deviation = np.max(np.abs(measurements - true_value)) print(f"最大偏差: {max_deviation:.4f}")运行结果大概是:均值非常接近100(因为噪声零均值,样本多了就抵消了),标准差接近0.5,最大偏差大约在1.5到2之间,也就是3个标准差的范围内。这个实验解释了一个重要现象:单次测量的误差不可控,但大量测量的统计特性是稳定可预测的。卡尔曼滤波不需要依赖单次测量有多准,而是靠长期融合统计规律来收敛。
顺便提一句,三西格玛法则在电控里的应用很常见:对于高斯分布,大约99.7%的样本落在均值正负3个标准差范围内。做滤波或故障检测时,如果某次测量偏离预测值超过3倍标准差,大概率不是正常噪声,而是传感器故障或野值,这时候可以考虑丢弃该数据或降低它的权重。
3. 协方差与多维高斯分布:卡尔曼滤波的真实战场
3.1 协方差:两个变量是不是“一起飘”
期望和方差处理的是单个变量,但RM里的状态从来不是一个变量——云台角度和角速度会同时变化,底盘位置和速度也紧密耦合。你不可能独立地估计它们,因为它们之间有关联。描述这种关联的数学量叫协方差:(Cov(X,Y)=E[(X-\mu_X)(Y-\mu_Y)])。
协方差直观解释是:如果两个变量总是同时偏离各自的均值,一个偏大另一个也偏大,那协方差为正;如果一个偏大另一个反而偏小,协方差为负;如果两者没什么关系,协方差接近零。举个例子,云台在匀速转动时,角度和角速度之间有一定正相关性——角度变化快的区间,角速度读数通常也偏大。这种关联信息,在卡尔曼滤波里非常宝贵,因为它让系统可以通过一个量的观测值推断另一个量的变化趋势。
实际工程里更常用协方差矩阵,因为系统中的变量往往不止两个,我们需要知道所有变量两两之间的协方差关系。协方差矩阵是一个方阵,对角线元素是各变量自身的方差,非对角线元素是变量两两之间的协方差。它完整地描述了系统状态分布的形状和方向:不仅有多分散,还有往哪个方向偏。
3.2 协方差矩阵与多维高斯分布
如果一个随机向量 (\mathbf{x} = [x_1, x_2, ..., x_n]^T) 服从多维高斯分布,它的概率密度函数会写成带协方差矩阵 (P) 的形式: [ f(\mathbf{x}) = \frac{1}{\sqrt{(2\pi)^n |P|}} \exp\left(-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^T P^{-1} (\mathbf{x}-\boldsymbol{\mu})\right) ] 这个式子看起来吓人,但你可以把它理解成一个多维的“钟形曲线”。均值向量 (\boldsymbol{\mu}) 决定曲线的中心位置,协方差矩阵 (P) 决定曲线的形状和朝向。矩阵的行列式 (|P|) 决定钟的“胖瘦”,矩阵的逆 (P^{-1}) 出现在指数里,说明 (P) 越“小”(不确定性越低),概率密度在均值附近越尖锐,说明我们对状态的把握越大。
搞懂多维高斯分布,就基本拿到了卡尔曼滤波的一半钥匙。因为卡尔曼滤波里的状态向量往往是多维的,比如云台状态可以是 ([角度, 角速度]^T),每时每刻我们估计的不只是一个数,而是这个二维随机向量的均值和协方差矩阵。预测和更新过程,实际上就是在不断更新这个均值和协方差矩阵。
3.3 状态向量怎么排,协方差矩阵就怎么长
在写卡尔曼滤波代码时,第一件事就是定义状态向量。常见做法是把位置和速度放一起,比如一维运动的状态向量: [ \mathbf{x} = \begin{bmatrix} p \ v \end{bmatrix} ] 对应的协方差矩阵就是2×2: [ P = \begin{bmatrix} Var(p) & Cov(p, v) \ Cov(v, p) & Var(v) \end{bmatrix} ] 对角线上是位置的不确定度和速度的不确定度,副对角线上是位置和速度的关联度。程序初始化时通常把 (P) 设为单位矩阵乘一个较大系数,表示初始时对状态很不确定。随着滤波推进,(P) 会被不断修正,逐渐收敛到稳定值。
我在实际调车中发现一个很实用的习惯:把状态向量的维度整理清楚,并标注好每一维的物理含义,再对照协方差矩阵的每个元素,这样查起bug来快很多。比如云台滤波时发现角度估计平滑但角速度估计抖得厉害,就去看协方差矩阵中对应角速度的方差项是否一直没降下来,再检查角速度的测量输入和过程噪声设置,问题往往能立刻定位。
4. 条件概率与贝叶斯公式:卡尔曼更新步骤的灵魂
4.1 条件概率:已知一部分信息之后的概率
条件概率 (P(A|B)) 表示在事件 (B) 已经发生的条件下,事件 (A) 发生的概率。它和我们平时说的“概率”最大的区别是:它利用了额外信息来修正判断。比如出门前我看天气APP说降水概率30%——这是无条件概率;但如果我看到窗外乌云密布,那么“今天下雨”的概率就会显著提高,这就是在已知“乌云”这个信息后更新得到的条件概率。
电控里处处都是这种思维。你提前知道云台大概率朝某个方向转(先验),然后读取视觉给的目标位置(观测),你的最终判断就是结合了这两种信息的结果。条件概率告诉我们:手握额外信息时,不要死守初始判断,而是要用新的证据去修正。
4.2 贝叶斯公式:先验、似然与后验
条件概率的进阶是贝叶斯公式: [ P(A|B) = \frac{P(B|A)P(A)}{P(B)} ] 拆开看三部分:(P(A)) 是看到观测之前的初始信念,叫先验概率;(P(B|A)) 是在假设 (A) 为真的前提下,观测到 (B) 的可能性,叫似然;(P(A|B)) 是结合观测之后的修正信念,叫后验概率。贝叶斯公式的核心思想就是:用观测数据更新对事件的信念。
放到卡尔曼滤波的语境里:系统当前状态是未知的,我们对它有一个预测(先验,来自运动模型);传感器给了一个测量结果;贝叶斯更新就是利用这个测量结果,把预测和测量按各自的不确定性权重折中,得到更靠谱的后验估计。这个思路和人类做判断的方式完全一致——盯着的目标突然移动了,你不会完全相信旧位置,也不会完全被新读数带跑,而是结合两者给自己一个更稳的落点估计。
4.3 从贝叶斯到卡尔曼:预测-更新循环的影子
卡尔曼滤波的经典流程分成预测和更新两步。预测步用运动模型把上一时刻的后验分布推演到当前时刻,得到先验分布;更新步用当前时刻的传感器测量,结合贝叶斯公式得到新的后验分布。周而复始,就是整个滤波过程。
很多人一开始不理解为什么卡尔曼滤波是递归的——每次只用到上一时刻和当前时刻的数据,不需要存历史数据。这正是贝叶斯思想的优雅之处:上一时刻的后验分布已经包含了此前所有的历史信息,只要把它作为当前时刻的先验输入,就自动完成了历史信息的传递。这也是卡尔曼滤波适合嵌入式实时系统的核心原因:计算量固定,内存占用小,每一帧只需要一次预测和一次更新。
具体到RM场景,云台自稳就是个典型例子。运动模型预测云台在当前角度和角速度下大概会转到什么位置,这是先验;IMU和视觉给出的姿态测量提供观测。两者各带噪声、各有不确定性,贝叶斯更新把它们融合,输出一个比任何单一传感器都平滑、都准确的姿态估计。明白了这个循环,你就看懂了卡尔曼滤波的骨架,剩下的只是如何用矩阵和高斯分布把这些步骤形式化。
5. 动手验证:从概率统计到一维卡尔曼
5.1 一个完整的一维卡尔曼滤波实验
理论讲得再多,亲自实现一次才有体感。这里用一维情况做一个最简单的卡尔曼滤波:估计一个缓慢变化的位置,测量值带有高斯噪声。虽然模型简单,但覆盖了预测、更新、增益计算的完整流程,非常适合在进入多维矩阵之前建立直观感受。
import numpy as np import matplotlib.pyplot as plt # 真实运动:匀速移动,速度 0.2,共 100 帧 dt = 0.1 true_pos = np.zeros(100) for i in range(1, 100): true_pos[i] = true_pos[i-1] + 0.2 * dt # 测量值:真实值 + 标准差 0.1 的高斯噪声 np.random.seed(1) measurements = true_pos + np.random.normal(0, 0.1, 100) # 卡尔曼滤波初始化 x_est = 0.0 # 状态估计初值 P_est = 1.0 # 估计误差协方差初值,较大表示初始不确定高 Q = 0.0001 # 过程噪声方差,运动模型越准确该值越小 R = 0.01 # 观测噪声方差,直接从传感器标定或经验得到 F = 1.0 # 状态转移系数,一维匀速模型约等于 1 H = 1.0 # 观测系数,直接观测位置 filtered = [] for z in measurements: # 预测 x_pred = F * x_est P_pred = F * P_est * F + Q # 更新 K = P_pred * H / (H * P_pred * H + R) x_est = x_pred + K * (z - H * x_pred) P_est = (1 - K * H) * P_pred filtered.append(x_est) # 对比:直接测量 vs 滤波估计 vs 真值 import matplotlib.pyplot as plt plt.figure(figsize=(10, 4)) plt.plot(true_pos, label="True", linewidth=2) plt.plot(measurements, label="Measurement", alpha=0.6) plt.plot(filtered, label="Kalman Filter", linewidth=2) plt.legend() plt.xlabel("Time step") plt.ylabel("Position") plt.title("1D Kalman Filter Demo") plt.grid(True) plt.show()代码里几个参数需要说明:R是传感器噪声的方差,前面讲的方差在这里派上用场,你可以用传感器静止时采集多帧数据算方差来标定;Q是过程噪声方差,表示你对运动模型的信任程度,模型越粗糙、外力干扰越大,Q就应调得越大;初始P_est设得大一点,表示一开始完全不相信初始猜测,滤波会快速收敛。
5.2 实验结果带来的三个重要感受
跑完这段代码,如果你把测量序列和滤波序列画在一起,会看到几个明显现象。第一,滤波后的曲线明显比原始测量平滑,毛刺被削掉了;第二,滤波曲线比原始测量更贴近真实轨迹,整体误差更小;第三,刚开始几帧滤波误差可能比较大,但随着迭代它会迅速收敛,这个收敛过程就是协方差矩阵 (P) 从初值逐渐下降到稳定水平的过程。
我给同学演示这段代码时,总有人问我一个问题:为什么不直接用滑动平均?滑动平均本质上对每一帧数据等权相加,响应慢,而且无法引入运动模型。卡尔曼滤波强就强在它能根据运动模型做预测,再根据观测噪声方差动态调整权重:当测量噪声大时,它更信任模型预测;当模型不靠谱时,它逐渐增加测量的权重。这种自适应加权机制,让它在快速变化和噪声较大的场景下远优于普通均值滤波。
理解了这段代码,恭喜你,你已经知道卡尔曼滤波的核心流程了。后面要做的事情,无非是把F、H、P从标量换成矩阵,把状态从一维变成多维,处理变量的耦合关系。逻辑是完全一致的。
6. 常见问题与排查技巧实录
6.1 新手最容易踩的坑
我带过的电控组员里,至少有五六个卡在同一个地方:Q和R怎么调?问他们依据是什么,基本都答不上来,只能“试试看”。这里分享一个比较稳的流程:R一定是可以实测的,静态采集一组传感器数据,求方差就行;Q则要根据你的运动模型的信心来设,先把Q设得小一些,如果滤波结果明显“跟不上”快速变化,再逐步增大Q。记住一个口诀:测量噪声越可信,R越小,卡尔曼增益偏向测量多一点;模型噪声越可信,Q越小,滤波偏向预测多一点。
第二个坑是维度不匹配。多维卡尔曼里,矩阵乘法的维度一定要理清楚:状态向量是 (n) 维,那么 (P) 是 (n \times n),(F) 是 (n \times n),(H) 是 (m \times n)((m) 是测量维度),(R) 是 (m \times m),卡尔曼增益 (K) 是 (n \times m)。每写一行矩阵运算,先写注释标明形状,能救命。
第三个坑非常隐蔽:协方差矩阵变得不对称或非正定。理论里协方差矩阵是对称的、正半定的,但由于浮点运算误差,长时间迭代后矩阵可能轻微不对称,甚至对角线出现负数,导致滤波发散。解决办法是在每次更新后手动对称化:P = 0.5 * (P + P.T),并检查对角线是否为负。这个小技巧在很多开源库里都有,但新手自己实现时经常忽略。
6.2 快速自查清单
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 滤波结果震荡剧烈 | R设得太小或Q设得太大 | 实测传感器方差,正确标定R |
| 滤波响应迟钝、跟不上目标 | Q太小或R太大 | 增大Q,让模型更信任快速变化,或用更高阶模型 |
| 估计值发散、指数级增大 | 协方差矩阵不对称或非正定 | 检查矩阵运算维度,做对称化处理,查看P是否异常 |
| 初始化阶段收敛缓慢 | 初始P设得太小 | 初始P适当设大,让滤波更快收敛 |
| 滤波结果虽平滑但严重滞后 | 模型和真实运动偏差大 | 检查状态转移矩阵F是否准确,考虑增加状态维度 |
这张表几乎覆盖了我自己写滤波代码时遇到的大多数问题。真要排查起来,别只盯着参数,先用Python离线仿真把传感器数据跑通,再移植到单片机上,能少走很多弯路。
卡尔曼滤波看起来是一个算法,实际是一套“在不确定中做最优估计”的思维方式。概率统计基础就是这套思维的地基,把期望、方差、协方差、贝叶斯公式这几个概念吃透,你会发现后面所有复杂公式都是在回答同一个问题:如何根据已知信息,把对系统状态的不确定性压到最低。下一篇我会从这里出发,一步步把多维卡尔曼滤波的五个公式推导出来,用RM云台和超级电容的实例陪你走完整个落地过程。