卡尔曼滤波与 EKF
轮式里程计会慢慢漂走,GPS 又抖又跳,单靠哪一个都不靠谱。卡尔曼滤波 (Kalman filter) 用一团高斯分布表示"我大概在哪":预测时把这团信念挪过去、再吹胖一点;更新时把它和测量相乘,得到一团更瘦、更可信的信念。把线性模型换成雅可比矩阵,它就成了机器人状态估计里的老黄牛:扩展卡尔曼滤波 (EKF)。
1为什么机器人需要它
一辆送货小车在园区里跑。它有两个信息来源:轮子上的编码器(里程计)告诉它"我刚才往前走了 0.52 米、左转了 3°";GPS 每秒报一次"你在 (x, y)"。
里程计短期内又准又平滑,可每一步的小误差都会累积:轮子打滑一下、地面颠一下,十分钟后它可能以为自己在隔壁楼里。GPS 不会累积误差,但每次读数都在真值附近乱跳好几米,进了楼还会丢信号。
一个"短期可信、长期漂移",一个"长期可信、短期乱跳"。卡尔曼滤波按各自的可信程度把两者融合起来;在线性、高斯的假设下,它给出的估计是最优的。无人机飞控、自动驾驶定位、手机里 IMU 与 GPS 的融合、SLAM 的前端,到处都有它。它就是 4.7 节贝叶斯滤波在"高斯世界"里的精确实现:信念不再是一张直方图,而只用两个量描述,均值和协方差。
2直觉:一团会移动、会胖瘦的信念
机器人从来不知道自己确切在哪,它只有一个信念 (belief):一个高斯分布。均值是"最可能的位置",宽度(标准差 σ)是"我有多拿不准"。不妨把它想成桌上的一堆沙子。
卡尔曼滤波只做两件事,交替进行:
- 预测 (predict):机器人按运动模型往前走,沙堆跟着挪过去。运动本身有噪声,所以沙堆同时摊开一些:不确定性变大了。
- 更新 (update):来了一个测量,它自己也是一堆沙子(传感器噪声)。把两堆沙子相乘,得到一堆更窄的新沙堆,位置落在两者之间,偏向更有把握的那一方。
"偏向谁、偏多少"由一个 0 到 1 之间的数决定:卡尔曼增益 (Kalman gain) K。K 越接近 1,越相信测量;越接近 0,越相信自己的预测。先来亲手体会一次更新。
- 让 K ≈ 0.5:预测和测量各占一半。这时两条曲线的宽度有什么关系?
- 让后验几乎和测量重合(K > 0.95)。用一句话说出这对应什么样的传感器。
把刚才看到的写成公式(这里 \(\mu^-, (\sigma^-)^2\) 是预测的均值与方差,\(z, \sigma_z^2\) 是测量及其噪声方差):
提示:把鼠标悬停在公式中带底色的符号上,FIG 1 中对应的曲线或箭头会被高亮。
卡尔曼增益就是一个信任比例:K = 我的不确定性 ÷(我的不确定性 + 传感器的不确定性)。谁更拿不准,谁就让步。更新后的方差 \((1-K)(\sigma^-)^2\) 一定比先验小:只要模型是对的,多一个测量永远不会让你更糊涂。
3动手玩:追踪一辆小车
现在让时间流动起来。一辆小车在轨道上来回跑:大体匀速,但会随机加减速,到了两端被缓冲器弹回。一个很烂的"GPS"每 0.2 秒给出一个位置读数(紫色),噪声标准差 1.2 米。滤波器的状态是位置和速度 \((p, v)\),运动模型是"匀速":\(p \leftarrow p + v\,\Delta t\)。
你手里有两个旋钮,它们是滤波器自以为的噪声水平,不改变真实世界:
- Q(过程噪声):滤波器认为"匀速模型"有多不靠谱,用随机加速度 σₐ 表示。Q 大 = "小车随时可能变速,别太信预测"。
- R(测量噪声):滤波器认为 GPS 有多不靠谱,用 σ_z 表示。R 大 = "GPS 很烂,别太信测量"。
先一步一步按 预测、更新,看信念的团怎么变胖、变瘦;然后打开自动播放,转动旋钮。
- 把滤波器调得迟钝:让位置的卡尔曼增益低于 0.08,并至少跑 20 次更新。小车被弹回时,黄线跟得上吗?
- 把它调得神经质:增益高于 0.9,跑 20 次更新。黄线和紫点一样抖,滤波器等于没开。
- 打败传感器:找一组 Q、R,在同一组设置下连续 50 次更新,让估计误差 (RMSE) 低于 GPS 误差的 70%。
对均值来说,真正起作用的是 Q 与 R 的比值:Q/R 小,滤波器相信模型,输出平滑但反应慢;Q/R 大,滤波器相信传感器,反应快但抖。把 Q、R 同时放大 10 倍,K 和黄线都不会变,只有 ±2σ 带会变宽。这说明 Q、R 的绝对大小决定的是滤波器"自称"有多准,这一点在第 7 节还会咬人。
4第二个视角:相空间里的协方差椭圆
FIG 2 只画了位置那一维,可滤波器心里装的是二维状态 \((p, v)\)。二维高斯的"团"是一个椭圆(4.4 节):横轴是位置,纵轴是速度。
预测那一步 \(p \leftarrow p + v\,\Delta t\),写成矩阵就是 \(F = \begin{bmatrix}1 & \Delta t\\ 0 & 1\end{bmatrix}\)。这正是 1.3 节里的剪切:椭圆上速度大的那些"可能的我"跑得更远,速度小的落在后面,椭圆于是被拉歪,位置和速度变得相关。按 4.5 节的规则,协方差变成 \(FPF^\top\),再加上 Q。
更新那一步只测位置。测量在相空间里是一条竖直的条带:它说"p 大概在这儿",对 v 只字未提。后验是椭圆与条带的交集。神奇的地方来了:因为椭圆是歪的,横向挤一挤,竖向也跟着变窄。速度从没被测过,却也变准了。
- 只用位置测量,把速度的标准差 σ_v 从 0.80 压到 0.40 以下。
FIG 2 的位置 ±2σ 带,就是 FIG 3 椭圆在横轴上的"影子"。FIG 2 里滤波器明明只看到位置读数,却能估出速度、提前把团挪到小车要去的地方,靠的正是椭圆里的这份相关性。拖动 z 时留意:测量偏右,后验均值会斜着往右上方走,因为"小车比预想的更靠前"意味着"它大概也比预想的更快"。
5再看数学
提示:悬停下面公式中带底色的符号,FIG 3 中对应的椭圆、条带或箭头会高亮(FIG 2 右侧的实时公式也可以悬停)。
一维:两个高斯相乘
贝叶斯更新(4.7 节)说:后验 ∝ 似然 × 先验。两个都是高斯,相乘后指数部分仍然是 x 的二次式,所以结果还是高斯:
比较 \(x^2\) 和 \(x\) 的系数,得到两条非常好记的规则:精度(方差的倒数)相加,均值是按精度加权的平均:
令 \(K = (\sigma^-)^2 / ((\sigma^-)^2 + \sigma_z^2)\),整理一下就是第 2 节的形式 \(\mu = \mu^- + K(z-\mu^-)\),\(\sigma^2 = (1-K)(\sigma^-)^2\)。两种写法完全等价,"增益 + 新息"的写法可以直接推广到多维。
一般形式:卡尔曼滤波
系统的状态 \(\x\) 按线性模型演化,传感器线性地观测它,两处都有零均值高斯噪声:
信念是 \(\mathcal N(\hat{\x}, P)\)。每个时刻做两步:第一行是预测,后两行是更新。
| 符号 | 含义 | 在图里 |
|---|---|---|
| \(\hat{\x}, P\) | 信念:均值与协方差 | 黄色的团 / 椭圆 |
| \(F\) | 运动模型:状态怎样演化 | FIG 3 里的剪切 |
| \(Q\) | 过程噪声:模型有多不准 | 预测时团变胖 |
| \(H\) | 测量模型:状态 → 读数 | 只测位置时 \(H = [1\;\,0]\) |
| \(R\) | 测量噪声 | 紫色条带的宽度 |
| \(\mathbf z - H\hat{\x}^-\) | 新息 (innovation):测量带来的"意外" | 紫色虚线箭头 |
| \(S = HP^-H^\top + R\) | 新息的协方差:预期的意外有多大 | 预测团与测量团宽度的合成 |
| \(K\) | 卡尔曼增益:信任比例 | FIG 1 的信任条 |
令 \(F = H = 1\),\(P^- = (\sigma^-)^2\),\(R = \sigma_z^2\),上面的 \(K = P^-/(P^- + R)\) 就退化成 FIG 1 的公式。多维时 \(K\) 是一个矩阵:它不仅决定"信多少",还决定一个位置上的意外该怎样分摊到速度等没被测到的分量上,FIG 3 里斜着走的那支箭头就是它干的。
扩展卡尔曼滤波 (EKF)
真实机器人几乎都不是线性的:小车朝 \(\theta\) 方向开,位置的变化里有 \(\cos\theta\);激光测到路标的距离里有平方根,方位里有 \(\operatorname{atan2}\)。模型变成
EKF 的思路就是 3.3 节的雅可比:在当前估计附近,把 \(f\) 和 \(h\) 看成线性的。均值照常走真正的非线性函数,协方差则用雅可比矩阵传播(4.5 节的 \(J\Sigma J^\top\)):
其余公式和线性 KF 一字不差。以第 6 节的小车为例,状态 \(\x = (x, y, \theta)\),控制量是里程计给出的线速度 \(v\) 与角速度 \(\omega\),传感器测量到路标 \(\mathbf m\) 的距离与方位。记 \(\Delta x = m_x - x\),\(\Delta y = m_y - y\),\(r = \sqrt{\Delta x^2 + \Delta y^2}\):
读一读 \(F\) 的第三列:朝向的一点误差,会按 \(v\Delta t\) 的比例变成侧向的位置误差。这就是为什么里程计的航向一偏,位置椭圆会沿着垂直于行驶方向的方向迅速变长。
6机器人中的它:用路标做 EKF 定位
一台小车沿着椭圆形路线兜圈。它用里程计做预测,用激光测到附近路标的距离和方位做更新(假设它认得每个路标是谁,也就是数据关联已知)。路标都在地图上,位置是已知的。
看椭圆:在没有路标的那一侧,只有预测、没有更新,椭圆会越长越大,而且是横着长(航向误差变成侧向误差)。一看到路标,它就猛地缩回去。红色虚线是纯里程计(航位推算 dead reckoning),它永远不会被纠正。
- 关掉所有路标(或把量程调到 0),等椭圆长到超过 1.5 米(看右侧读数),再打开路标,看它缩回 0.35 米以内。
ROS 的 robot_localization 包用 EKF 把轮式里程计、IMU 和 GPS 融合成一个位姿;PX4 飞控的 EKF2 同时估计无人机的姿态、速度、位置和传感器零偏;视觉惯性里程计里的 MSCKF 也是卡尔曼滤波的一个变种。对付三维旋转时,工程上常用"误差状态"卡尔曼滤波 (error-state KF):把小的旋转误差放在李代数里(第 5 章)当作向量来滤,就绕开了旋转矩阵不能直接相加的麻烦。
7常见误区
R 描述的是传感器,可以测出来:让机器人静止,录一段读数,算方差就行。Q 描述的是"模型有多错",更难确定,通常要调。两者含义不同,不要把 R 调小来"让滤波器跟得更快",那是在对滤波器撒谎;跟不上说明模型不够好,应该加大 Q,或者换更好的模型。
P 是滤波器自称的不确定性,不是真实误差。Q 设得太小,P 会一路缩到几乎为零,K 也跟着变成零,滤波器从此对测量充耳不闻。真实世界稍一变化,估计就一去不回,而 ±2σ 带还信誓旦旦地很窄。这叫发散 (divergence)。一个好滤波器应该"诚实":真值大约 95% 的时间落在 ±2σ 带里。下面的 FIG 5 让你亲手把滤波器调到撒谎。
- 让滤波器过度自信:把覆盖率压到 50% 以下。然后想一想:只看黄线和黄带,你能发现它在撒谎吗?
预测方位是 −179°,测到 179°。直接相减得到新息 358°,滤波器会以为差了将近一圈,把机器人猛地转回去;正确答案是 −2°。凡是涉及角度的新息和状态,都要归一化到 (−180°, 180°]。FIG 4 每一次更新都做了这件事,否则小车绕到左边时就会抽风。
雅可比只在估计点附近可信。如果不确定性比函数"弯曲"的尺度还大(比如离一个很近的路标测方位,或者初始位姿完全不知道),线性化就会出错,滤波器可能变得过度自信甚至发散。对策:给 Q 留点余量,用迭代 EKF 重新线性化,或者换成无迹卡尔曼滤波 (UKF)。另外,数值误差会让 P 失去对称正定,实际代码常用 Joseph 形式 \((I-KH)P^-(I-KH)^\top + KRK^\top\) 来更新 P。
卡尔曼滤波的信念永远是单峰的。一条走廊里有两扇一模一样的门,机器人看到一扇门时,真实的信念应该有两个峰;"被绑架"的机器人(突然被搬到别处)则需要在整张地图上撒开的信念。一个高斯只能取中间值或者二选一,这两种都是错的。这种时候要用 4.10 节的粒子滤波。
8小练习
9延伸阅读
- S. Thrun, W. Burgard & D. Fox, Probabilistic Robotics, MIT Press, 2005, Ch. 3 — 高斯滤波器:KF、EKF、UKF 与信息滤波,以及第 7 章的 EKF 定位
- R. Labbe, Kalman and Bayesian Filters in Python — 免费的交互式 Jupyter 教材,从 g-h 滤波一路讲到 EKF 与 UKF
- G. Welch & G. Bishop, An Introduction to the Kalman Filter, UNC-Chapel Hill TR 95-041 — 简短经典的入门讲义,本节的 x̂⁻、P⁻ 记号即来自这里
- T. Babb, How a Kalman filter works, in pictures — 用图讲清"两个高斯相乘"的经典博文
- R. E. Kalman, "A New Approach to Linear Filtering and Prediction Problems", Journal of Basic Engineering, 1960 — 原始论文