ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

扩展卡尔曼滤波(EKF)原理与工程实践:非线性系统状态估计指南

2026/9/7 10:01:42 拓冰建站 浏览量
扩展卡尔曼滤波(EKF)原理与工程实践:非线性系统状态估计指南 简介面向惯性导航、机器人姿态估计与传感器融合方向的研究者这份资源依据一篇针对9轴IMU姿态跟踪的双级卡尔曼滤波论文实现了扩展卡尔曼滤波EKF与误差状态卡尔曼滤波ESKF两种算法。资源包为zip格式大小约2.23MB下载后即可解压查看源码与相关配置。该主题已吸引441人学习说明其在IMU融合领域有较强的借鉴意义。内容上资源同时包含基于论文的实现、EKF-IMU实现和ESKF-IMU实现三套方案方便读者对比标准EKF与误差状态滤波在状态建模、协方差传播及量测更新中的差异也能帮助理解双级滤波框架如何将姿态解算任务分担给集成处理器结合论文与代码还可进一步分析四元数与旋转矩阵在不同坐标系下的转换以及加速度计、陀螺仪数据的融合细节。对于正在学习卡尔曼滤波、需要从零搭建IMU姿态解算系统的开发者这份资源是一份可直接参考的工程化样例。 做导航、自动驾驶或者机器人定位的朋友可能都遇到过这个尴尬的瞬间明明控制模型里只有几个三角函数普通卡尔曼滤波却把状态估计得一塌糊涂。我第一次在组合导航里踩这个坑时还以为是自己调参不对后来把扩展卡尔曼滤波EKF从理论到公式完整啃了一遍才明白问题的根源既不在参数也不在代码而在“线性”这两个字。EKF是卡尔曼滤波在非线性系统上的自然延伸也是最实用的那一类工程里绝大多数模型都不是线性高斯模型而它只要在估计点附近做一次一阶泰勒展开就能把老一套递推框架继续用下去。这个思路像开车时只看车灯照亮的一小段路每一段都近似笔直组合起来就能走完整条山路。这篇文章适合两类人一类是刚接触滤波算法、想知道EKF和经典卡尔曼有什么区别的初学者另一类是已经在用EKF、但多次遇到状态发散、不知道从哪里排查的中级开发者。我会把原理、推导、代码、工程坑一次说清楚尽量不绕弯子。1. 从“线性假设”到“非线性现实”EKF到底在解决什么问题1.1 经典卡尔曼滤波的严格前提回到最基础的地方。经典卡尔曼滤波之所以好用是因为它建立在两个非常扎实的假设上系统的状态转移和观测方程都是线性的过程噪声、观测噪声都是高斯白噪声。只有在这两个假设同时成立时高斯分布经过线性变换之后仍然保持高斯滤波的每一步递推才能严格用均值和协方差两个量完整描述。一个典型的线性系统长这样x_k A x_{k-1} B u_k w_kz_k H x_k v_k其中A是状态转移矩阵H是观测矩阵w_k、v_k分别是过程噪声和观测噪声。在这个框架下经典卡尔曼滤波的递推就是大家熟悉的那套预测x_k^- A x_{k-1}、预测协方差P_k^- A P_{k-1} A^T Q再算卡尔曼增益K P_k^- H^T (H P_k^- H^T R)^{-1}最后用观测更新状态和协方差。这套东西在数学上非常漂亮但工程上能严格满足“线性”的系统少得可怜。这也是很多初学者照着教程调公式效果却远不如教程的原因——教程里的A、H是现成的而你的系统根本不是那个形状。1.2 非线性到底藏在哪里拿无人车和无人机最常见的运动模型举例。一个最简单的两轮车模型状态包括位置x、y和航向角θ输入是速度和前轮转角。预测下一时刻位置时要写x_{k1} x_k v*dt*cos(θ_k)y_{k1} y_k v*dt*sin(θ_k)θ_{k1} θ_k v*dt*tan(δ)/L只要把cos和sin放进去这个系统就不是线性的了A矩阵根本不存在因为你无法用一个固定矩阵把[x, y, θ]线性映射到下一时刻。再来看观测方程。GPS或者雷达给出的往往是距离和方位角而不是直接的x、y坐标。从位置向量[x, y]到距离r sqrt(x²y²)、方位角atan2(y, x)这个映射同样是非线性的。IMU的姿态更新里还有四元数乘法、矩阵指数运算非线性程度更高。还有更隐蔽的比如温度对传感器零偏的影响、电池电压对执行器增益的影响。只要模型里出现乘法、除法、平方根、三角函数、指数或者任意两个状态量相乘就都进入了非线性系统的范畴。遇到这些模型仍然直接套用线性卡尔曼滤波的A、H矩阵从第一步预测开始误差就会被放大后面滤波就算没发散估计结果也会严重偏置。1.3 强行近似的代价和意义面对非线性系统工程上曾经有三种主流思路。第一种是“既然系统非线性那我就把它硬近似成线性”这就是EKF走的路线第二种是“用一组采样点去传播均值和协方差”这是无迹卡尔曼滤波UKF的思路第三种是“干脆用一堆随机粒子去逼近真实分布”这是粒子滤波的思路。后两种后面会提到但EKF作为最朴素、最轻量的一种至今仍是工业界落地最多的选择。EKF的思路可以概括成一句话在每个时刻把非线性函数在当前状态估计值附近展开成线性函数然后沿用经典卡尔曼滤波的递推框架。它不追求全局精确只追求“这一段误差不大”。这个近似策略代价也很直接如果系统非线性太强、或者当前估计误差太大一阶近似的误差会让滤波结果不稳定甚至发散。理解了这个“代价”后面看EKF的公式就不会觉得它神秘也能解释为什么有时候它不如UKF。2. EKF的唯一秘密在当前估计点做局部线性化2.1 Taylor展开把弯曲的马路用切线近似说到局部线性化最常见也最直观的解释是Taylor展开。任意一个光滑函数f(x)在某个点x0附近可以写成f(x) ≈ f(x0) f(x0)*(x - x0) 高阶项如果把高阶项丢掉f(x)就变成了一个关于(x - x0)的线性函数。换句话说EKF把真正的非线性系统在状态估计值附近替换成了一个小范围内的线性系统。我习惯用一个特别生活的比喻来记这件事你在山路上开车导航不知道整条路的完整形状但它知道你当前位置附近一段路的方向。只要车没开到岔口用这个方向往前推一小段误差小到可以接受。EKF做的就是这件事——每走一步重新拿手电筒照一下当前所在位置的那一小段路而不是去画整张地图。需要特别强调的是这个线性化的对象是系统方程但线性化的“锚点”不是零而是当前最优估计。这是EKF和很多简化模型最大的区别不同时刻、不同状态下系统的等效增益矩阵都不一样。2.2 雅可比矩阵状态间的局部斜率表一阶Taylor展开里的f(x0)放在多变量系统里就是雅可比矩阵。设状态向量是x ∈ R^n状态转移函数是f(x) ∈ R^n那么雅可比矩阵F的第i行第j列就是∂f_i/∂x_j。可以这么理解雅可比矩阵就是一张“局部斜率表”。第i行第j列表示如果第j个状态分量变化一点点第i个状态分量的预测值会跟着变化多少。在线性卡尔曼滤波里这张表是固定的A矩阵在EKF里这张表每个时刻都要重新算因为它依赖当前估计值。观测方程同样要算雅可比通常记作H。比如观测是距离r sqrt(x²y²)那么H在某个状态点上就是[∂r/∂x, ∂r/∂y] [x/r, y/r]。这个向量告诉我们当前状态下x方向移动1米和y方向移动1米对观测量r的影响分别是多少。这个信息最后会影响卡尔曼增益的分配——哪个方向更值得信任就更多依赖观测。2.3 为什么不能直接在真实状态处展开有人可能会问既然要线性化为什么不选真实状态作为展开点那样近似不是更准吗答案是如果能拿到真实状态那还要滤波器干什么。我们手上只有当前的最优估计x̂它和真实状态之间有误差这个误差又被协方差矩阵P描述着。EKF能做的就是在已知信息的范围内选择最可能接近真实状态的x̂去展开。这带来一个工程上的连锁反应卡尔曼增益K不再是一个预先算好的常量而是和当前状态估计、当前协方差绑定的时变矩阵。所以EKF代码循环里的每一步都要重新计算F、H、P、K计算量比线性KF大一些但这正是它能适应非线性的原因。还有一个新手容易忽略的细节预测协方差公式里用的是F_k但F_k是在x̂_{k-1}上计算的观测雅可比H_k则是在预测状态x̂_k^-上计算的。两个线性化点不同写代码的时候不能搞混否则协方差传播会错上一整套。3. 完整推导与最小可运行实现3.1 从系统模型到五条递推公式假设系统模型是x_k f(x_{k-1}) w_kw_k ~ N(0, Q)z_k h(x_k) v_kv_k ~ N(0, R)这里的f是状态转移函数h是观测函数Q、R分别是过程噪声与观测噪声协方差。EKF的整个递推过程可以压缩成五条公式预测步状态预测x̂_k^- f(x̂_{k-1})协方差预测P_k^- F_k P_{k-1} F_k^T Q更新步 3. 卡尔曼增益K_k P_k^- H_k^T (H_k P_k^- H_k^T R)^{-1}4. 状态更新x̂_k x̂_k^- K_k (z_k - h(x̂_k^-))5. 协方差更新P_k (I - K_k H_k) P_k^-和线性卡尔曼相比唯一的区别就是把A换成了F_k、把H换成了H_k并且状态预测、状态更新的过程中使用的是真实的非线性函数f、h而不是线性化之后的矩阵。很多人第一次上手时会想既然已经在预测状态时用了f(x̂_{k-1})那能不能直接用H_k x̂_k^-算预测观测不行。残差项z_k - h(x̂_k^-)必须用真实的h去算因为h(x̂_k^-)才是预测观测最准确的近似用H_k x̂_k^-就相当于在已经近似过的模型上再做一次近似会白白引入误差。3.2 一个经典标量非线性系统的完整代码理论说再多不如跑一个最小例子来得直观。我选一个在很多滤波论文里出现过的基准模型x_k 0.5*x_{k-1} 2*x_{k-1}/(1x_{k-1}²) sin(0.1*k) w_kz_k x_k²/20 v_k这个系统的状态转移和观测都是非线性的但求导很容易适合用来验证EKF代码没有写错。import numpy as np import matplotlib.pyplot as plt # 系统模型 def f(x, k): return 0.5 * x 2 * x / (1 x * x) np.sin(0.1 * k) def h(x): return x * x / 20 # 雅可比 def F_jac(x): return 0.5 2 * (1 - x * x) / ((1 x * x) ** 2) def H_jac(x): return x / 10 # 参数 Q, R 1.0, 1.0 N 100 np.random.seed(42) # 生成真实状态与观测 true_x, x_true, z_obs 0.1, [], [] for k in range(N): true_x f(true_x, k) np.random.normal(0, np.sqrt(Q)) x_true.append(true_x) z_obs.append(h(true_x) np.random.normal(0, np.sqrt(R))) # EKF 滤波 x_hat, P, estimates 0.5, 1.0, [] for k in range(N): # 预测 x_pred f(x_hat, k) F F_jac(x_hat) P_pred F * P * F Q # 更新 H H_jac(x_pred) S H * P_pred * H R K P_pred * H / S x_hat x_pred K * (z_obs[k] - h(x_pred)) P (1 - K * H) * P_pred estimates.append(x_hat) # 可视化 plt.plot(x_true, labeltrue) plt.plot(estimates, labelEKF) plt.legend() plt.show()运行之后你会看到即使初始状态给的是0.5和真实初始值0.1并不一致滤波轨迹也能很快贴近真实状态。这里最值得注意的细节是F_jac(x_hat)用的是上一时刻更新后的估计值而H_jac(x_pred)用的是当前时刻的预测值两个线性化点别写反。3.3 从标量到高维矩阵形式没变但到处都是细节上面的代码是标量版本公式里所有的“矩阵”都退化成数字所以看不出维度问题。换成真实的多维系统比如[x, y, θ, v]状态向量代码结构几乎不用变只是F变成n×n矩阵P_k^- F P F.T QH变成m×n矩阵S H P_pred H.T RK变成n×m矩阵x_hat x_pred K (z - h(x_pred))注意是矩阵乘法和逐元素乘法*要严格区分高维情况下最常见的错误是把F P F.T写成F * P * F.T或者把H P H.T的矩阵维度写反。我建议每次写完都打印一下各矩阵的shape至少能挡住一半的低级错误。另一个高维独有的细节是状态更新后的协方差可能因为数值原因不对称稳妥的做法是用P (P P.T) / 2强制对称严重时改用Joseph形式的更新公式。4. 工程实践中的坑与进阶技巧4.1 雅可比矩阵手工、数值还是自动微分如果你真实项目里的f和h有几十行手工求雅可比是一件让人头大的事而且特别容易出错。我记得自己第一次给捷联惯性导航写EKF时姿态误差的雅可比推了两天最后还是用数值差分把解析结果验出了一个符号错误。所以关于雅可比我有三条建议原型阶段用数值雅可比跑通流程。中心差分公式很简单F[:, j] (f(x eps*e_j) - f(x - eps*e_j)) / (2*eps)eps一般取1e-6或1e-7。因为只验证逻辑不用太关心效率。部署前用解析雅可比提高效率和数值精度。解析求导虽然费脑但计算量小、没有步长选择问题是嵌入式平台的首选。无论用哪种都建议在仿真数据上对比解析雅可比和数值雅可比的结果差别超过1e-4就该检查代码。数值雅可比的eps不是越小越好。eps太小浮点截断误差会淹没差分信息eps太大Taylor展开余项会增大。实践上中心差分用1e-6左右比较稳前向差分可以用1e-7。这个经验值在多数场景都适用不需要每个项目都重新折腾。4.2 Q、R怎么调以及状态发散的第一排查顺序EKF调参和线性卡尔曼本质一样都绕不开Q、R这两个矩阵。但EKF还有一个新的麻烦同样的Q、R在不同运动状态下表现可能完全不同因为雅可比在变。我的调参顺序一般是用传感器标称精度去设R。比如位置观测噪声标准差是0.1米R就设0.01方差 标准差的平方。Q先用小量试比如状态单位都为SI制时从1e-3量级开始再逐步增大直到滤波跟随速度正常。观察新息innovation z - h(x̂_k^-)。若新息均值长期不归零说明模型偏差大优先增大Q或者检查状态转移方程若新息方差远小于理论值S说明滤波器过度相信预测适当增大Q。如果状态发散第一件事不是调参而是检查雅可比。经验里八成发散问题是雅可比公式写错或者线性化点取错导致的。还有一个很容易忽略的点P矩阵不断更新时可能因为舍入误差失去正定性表现出来就是滤波在某个时刻突然“崩”掉。我在代码里通常会做两层保护——更新后强制对称并给P的对角线加上一个很小的1e-9量级的正则项这在长时间运行里非常有用。常见问题可以对照着看现象最常见原因优先排查动作新息均值长期不为零模型偏差或Q过小检查状态转移方程适度增大QP发散到很大雅可比错误或线性化点取错核对F、H计算对比数值雅可比P失去正定性数值舍入误差强制对称加正则项滤波结果明显滞后R过大或Q过小适当减小R、增大Q4.3 状态增广、EKF的边界以及什么时候该换UKF或粒子滤波EKF还有一个很常用的扩展技巧叫状态增广。简单来说就是把你觉得有偏差、需要在线估计的量比如陀螺零偏、加速度计比例因子塞进状态向量里跟位置、速度一起估计。比如IMU组合导航里陀螺零偏通常就是状态的一部分跟姿态一起被估计。状态增广的好处是模型更贴近真实世界代价是状态维数上升雅可比矩阵跟着膨胀矩阵求逆的代价增大。而且增广状态如果不可观会导致P矩阵病态。比如只靠GPS位置观测一般没办法同时把加速度计零偏也估准这一点在系统设计阶段就要想清楚。那么EKF有没有失灵的时候有。典型的两个场景一是初始误差太大线性化点离真实状态太远一阶近似根本不成立滤波器很容易发散二是系统非线性强到分布变成多峰或严重偏斜高斯假设不再成立。遇到这种问题先别急着调参可以考虑UKF它不计算雅可比而是用Sigma点集合来传播均值和协方差对强非线性的适应能力通常优于EKF。粒子滤波则更适合非线性又非高斯的极端场景代价是计算量通常大几个量级。我在实际项目里的取舍标准很简单计算资源有限、模型非线性中等、有成熟解析雅可比优先EKF模型复杂或求导成本高直接用UKF分布严重非高斯才上粒子滤波。工程不是考试不需要用最难的算法够用、可控、易排查才是第一位的。本文还有配套的精品资源点击获取