资讯详情 二阶扩展卡尔曼滤波SO-EKF:原理推导与MATLAB仿真实战
📅 2026/10/22 5:59:00
做状态估计的同行应该都有这种体会卡尔曼滤波本身并不难难的是系统一旦非线起来处处都是暗坑。经典的扩展卡尔曼滤波EKF处理非线性系统的基本思路是把状态转移函数在估计点附近做一阶泰勒展开然后拿一阶偏导雅可比矩阵去替换线性卡尔曼里的状态矩阵。这套“局部线性化”打法在弱非线性下很好使可一旦系统里存在明显的强非线性环节比如带三次方恢复力的质量-弹簧-阻尼MSD系统一阶EKF就开始系统性偏离真实值。于是就有了基于二阶扩展卡尔曼滤波SO-EKF的改进方案它把泰勒展开保留到二阶项用Hessian矩阵把“曲率”和“状态不确定性”同时纳入滤波递推。这篇文章我从原理、公式到MATLAB代码完整走一遍包含可复现的仿真工程适合正在入门非线性状态估计或者被一阶EKF发散问题折磨的开发者参考。1. 从一阶EKF的“恐弯症”讲起SO-EKF补上了哪一环1.1 标准EKF做了什么、丢掉了什么先回顾一阶EKF的核心对非线性系统 (x_{k1} f(x_k) w_k)在每一步都取当前状态估计 (\hat{x})把 (f) 在 (\hat{x}) 附近做一阶泰勒展开。于是非线性系统在这一步被近似成一个线性时变系统再套用线性卡尔曼的预测-更新两步走。因为雅可比矩阵是随估计值实时更新的所以EKF能勉强适应非线性但问题也恰恰出在这个“勉强”上。具体丢掉了什么(f(x)) 在 (\hat{x}) 处展开后还有二阶及更高阶项。一阶EKF直接用 (f(\hat{x})) 作为下一步的预测值等于假设状态一定落在 (\hat{x}) 上。然而实际状态是一个随机变量它的分布围绕 (\hat{x}) 展开协方差是 (P)。当 (f) 的曲率不可忽略时把这些随机取值通过 (f) 映射后的均值并不等于 (f(\hat{x}))而是存在一个与曲率和 (P) 都有关的偏移量。这个偏移量会被一阶EKF系统性忽略。我举个例子方便理解假设 (f(x) x^2)当前估计 (\hat{x} 1)协方差 (P 1)。(x) 的真实取值大致在 0 到 2 之间波动粗略地看这些取值平方后的均值显然不是 1而是比 1 大不少。一阶EKF用 1 去预测就稳定地偏低了。一阶线性化对这个偏移量毫无办法因为它在展开后只看见了斜率看不见弯曲方向。1.2 曲率、不确定性、Jensen不等式二阶修正的直觉这个偏移量不仅有数学表达式还符合一个直观规律对凸函数而言随机变量函数值的期望大于“均值点处的函数值”。学过概率论的朋友可能反应过来了这就是Jensen不等式的方向。凹函数则相反期望小于均值点函数值。所以当状态不确定性足够大、非线性函数的曲率足够强时一阶EKF的预测值一定存在系统性偏置而且这个偏置会沿着递推不断累积最终体现在估计轨迹的滞后和偏差上。二阶EKF做的事情很清楚泰勒展开保留到二阶项同时不再把 (x) 视作确定值 (\hat{x}) 来计算 (f(x))。它把状态当成以 (\hat{x}) 为中心、以 (P) 为协方差的随机变量对 (f(x)) 的展开式取统计期望。展开式里的常数项还是一阶EKF的 (f(\hat{x}))零均值项一阶项期望为 0二阶项经期望运算后变成 (\frac{1}{2} \mathrm{tr}(H_i P)) 这一项。这就是SO-EKF预测公式里多出来的“曲率修正”。它的物理含义非常明确非线性的弯曲程度越大协方差 (P) 越“胖”修正量越大。所以SO-EKF本质上不是换了一种滤波框架而是在一阶EKF骨架上打了两个补丁一个补丁修状态预测的均值偏移另一个补丁修协方差预测里的高阶不确定性传播。接下来的推导会看到这两个补丁如何落地。2. MSD系统建模把非线性弹簧的硬弯拆进状态方程2.1 物理方程到离散状态方程的推导质量-弹簧-阻尼系统是振动与控制入门必遇的模型。经典线性模型是 (m\ddot{x} c\dot{x} kx F(t))若弹簧刚度是常数 (k)整个系统是线性的用标准卡尔曼滤波就能得到最优估计根本不需要EKF。要让EKF和SO-EKF登场得让系统出现非线性的环节。我采用的模型是Duffing型非线性弹簧恢复力不再是 (kx)而是 (k_0 x k_1 x^3)。当位移 (x) 较大时恢复力的增长速度远快于线性弹簧这就是典型的“硬弹簧”特性。完整运动方程为[ m\ddot{x} c\dot{x} k_0 x k_1 x^3 F(t) ]令状态变量 (x_1) 为位移、(x_2) 为速度得到一阶微分方程组[ \dot{x}_1 x_2 ] [ \dot{x}_2 \frac{F}{m} - \frac{c}{m}x_2 - \frac{k_0}{m}x_1 - \frac{k_1}{m}x_1^3 ]这个 (x_1^3) 项就是滤波器的“试金石”。线性化时它的雅可比是 (3k_1 x_1^2)曲率二阶导是 (6k_1 x_1)随着位移增大迅速增大非线性强度随工况变化非常剧烈。用前向欧拉法以步长 (dt) 离散化得到状态转移[ f_1(x) x_1 dt \cdot x_2 ] [ f_2(x) x_2 dt \cdot \left[ \frac{F}{m} - \frac{c}{m}x_2 - \frac{k_0}{m}x_1 - \frac{k_1}{m}x_1^3 \right] ](f_1) 是线性的(f_2) 的全部非线性集中在 (x_1^3) 项。这样就得到了后续雅可比、Hessian 计算的全部输入。为什么一定把模型写清楚再谈滤波因为二阶EKF的修正量完全依赖 Hessian而 Hessian 的具体数值来自模型参数的偏导数写错一个系数后面所有增益都会出错。2.2 非线性项藏在哪为什么线性弹簧不需要SO-EKF这个问题值得专门回答。如果 (k_1 0)弹簧恢复力是线性的(f) 对状态的雅可比是常数矩阵Hessian 全为零二阶修正项恒等于零SO-EKF 会自动退化回标准卡尔曼滤波连一阶EKF都不用。只有 (k_1) 显著不为零时修正项才有意义。那 (k_1) 多大算“显著”这取决于状态分布相对非线性强度的大小。简单判据是在状态估计的 (\pm 2\sigma) 区间内比较线性部分与非线性部分的贡献。当 (3k_1 x_1^2) 与 (k_0) 同量级甚至更大时系统的局部“刚度”变化非常大一阶线性化在更新间隔内已经失真。仿真里我取 (m1.2) kg、(c0.8) N·s/m、(k_02.5) N/m、(k_10.8) N/m³当 (x_1) 到 1.5 m 左右时(3k_1 x_1^2 5.4)已经超过 (k_0)非线性明显。这一节还应当交代量测模型。我只假设位置可测[ z x_1 v, \quad v \sim N(0, R) ]这是很实际的场景——给系统装位置传感器容易装速度传感器往往又贵又难。速度状态需要滤波去“猜”这恰恰能体现出不同滤波算法在状态估计质量上的差异。量测方程 (h(x) x_1) 是线性的(H) 是常数矩阵 ([1, 0])量测 Hessian 为零因此后续推导可以聚焦在预测部分更新部分和线性卡尔曼完全一致。3. 二阶EKF核心公式推导预测和协方差各自多出来的一项3.1 带有Hessian的状态预测修正项接着来看离散非线性系统 (x_{k1} f(x_k) w_k)。设当前状态估计 (\hat{x})、协方差 (P)。把 (f(x)) 的每个分量 (f_i) 在 (\hat{x}) 处做二阶泰勒展开[ f_i(x) \approx f_i(\hat{x}) J_i^T (x - \hat{x}) \frac{1}{2}(x - \hat{x})^T H_i (x - \hat{x}) ]其中 (J_i) 是 (f_i) 的梯度即雅可比矩阵 (A) 的第 (i) 行(H_i \partial^2 f_i / \partial x^2) 是第 (i) 个分量的 Hessian 矩阵。现在对两侧取关于状态分布 (x \sim N(\hat{x}, P)) 的期望。有 (E[x - \hat{x}] 0)所以一阶项贡献为零二阶项里期望 (E[(x-\hat{x})^T H_i (x-\hat{x})] \mathrm{tr}(H_i P))。于是[ E[f_i(x)] f_i(\hat{x}) \frac{1}{2}\mathrm{tr}(H_i P) ]这就是状态预测的修正来源。写成向量形式SO-EKF的预测值为[ x_{\text{pred}} f(\hat{x}) \frac{1}{2}\left[\mathrm{tr}(H_1 P), \mathrm{tr}(H_2 P), \dots, \mathrm{tr}(H_n P)\right]^T ]注意这个修正量只有在 (P) 非零时才起作用。(P) 越大、Hessian 分量越大修正幅度越大。如果滤波器过于自信(P) 很小修正项会自动变小避免过度补偿。这一点很重要它解释了为什么不能手动给修正项设置固定常数——修正项必须依赖当前的协方差。回到MSD模型。(f_1) 是线性的(H_1 0)。(f_2) 的 Hessian 只有 ((1,1)) 元素非零(H_2(1,1) -6k_1 x_1 \cdot dt / m)其余元素为零。那么速度通道的修正项为[ \frac{1}{2}\mathrm{tr}(H_2 P) \frac{1}{2} \cdot \frac{-6k_1 x_1 dt}{m} \cdot P_{11} -\frac{3k_1 x_1 dt}{m} \cdot P_{11} ]这个式子值得停下来品味。当 (x_1 0) 且位置方差 (P_{11} 0) 时修正项为负意味着预测速度比一阶EKF“更小一点”。原因是 (x_1^3) 在正位移区间向上弯曲随机状态经过立方映射后速度的变化量期望确实比均值点处的估值更负。符号、数值大小全部由模型和协方差决定。3.2 协方差预测中的二阶项与量测更新的处理状态预测变了协方差预测也要跟着扩展。完整推导要从误差 (\xi x - \hat{x}) 入手把 (f) 的二阶展开代入再利用四阶矩公式 (E[\xi^T H_i \xi \cdot \xi^T H_j \xi] \mathrm{tr}(H_i P H_j P)) 展开。最终协方差预测为[ P_{\text{pred}} A P A^T Q \frac{1}{2}\sum_i \sum_j e_i e_j^T , \mathrm{tr}(H_i P H_j P) ]其中 (e_i) 是第 (i) 个标准基向量。这个双重求和项就是把“非线性曲率带来的不确定性”反馈进协方差。对本例(H_1 0) 且 (H_2) 只有一个非零元素双重求和只剩 (i j 2) 一项[ P_2(2,2) \frac{1}{2}\mathrm{tr}(H_2 P H_2 P) \frac{1}{2}\left(\frac{6k_1 x_1 dt}{m}\right)^2 P_{11}^2 ]注意这个贡献是非负的非线性的曲率不会减少不确定性只会增加。它让速度通道的方差在位移不确定性大时适当膨胀避免滤波器对估计“过度自信”。过度自信的后果很严重——协方差 (P) 过小会让卡尔曼增益失真后续量测数据无法充分修正状态这就是常见的一致性consistency问题。量测更新在本例中不需要额外的二阶处理因为 (h(x) x_1) 是线性的量测 Hessian 全为零预测量测值就是 (H \cdot x_{\text{pred}})增益修正公式与一阶EKF完全一致。只有在量测方程本身非线性时比如用角度传感器、对数强度传感器才需要在量测预测里加入同样的曲率修正。这是SO-EKF实现中容易忽略的地方量测的非线性同样需要通过 Hessian 修正。这些公式是整套SO-EKF的骨架。把它们翻译成代码时关键在于Hessian矩阵的正确计算和循环结构的组织。4. MATLAB工程实现SO-EKF滤波器的完整代码拆解4.1 主循环结构两个滤波器的独立状态必须分开维护进入代码环节。为了公平对比一阶EKF与SO-EKF我让两个滤波器并行运行处理完全相同的一组真实轨迹和观测序列。一个容易被忽略的工程细节是两个滤波器必须各自维护独立的状态估计和协方差不能在同一个变量上复用。如果你在循环里写了同一组x_est、P_est先后跑两个滤波器第二个滤波器的输入状态已经被第一个修改过对比结果完全失效。我在初版代码里就踩过这个坑轨迹图好看到离谱后来才发现是状态串扰。主程序结构如下% 主程序SO-EKF 与 一阶EKF 对比 m 1.2; c 0.8; k0 2.5; k1 0.8; F0 0.5; wF 0.5; dt 0.01; Tsim 20; N round(Tsim/dt); tm 0.1; Nm round(tm/dt); Q [1e-4*dt, 0; 0, 5e-2*dt]; R 5e-3; % 初值 x_true0 [0.2; -0.1]; x0 [0.0; 0.0]; P0 0.05 * eye(2); x_true x_true0; x_ekf x0; P_ekf P0; x_so x0; P_so P0; X_true zeros(2, N); X_ekf zeros(2, N); X_so zeros(2, N); for k 1:N t (k-1)*dt; F F0 * sin(wF*t); % 真实系统演化加过程噪声 x_true f_msd(x_true, F, dt) sqrt(Q)*randn(2,1); X_true(:,k) x_true; % 生成量测仅位置周期性采样 if mod(k-1, Nm) 0 z x_true(1) sqrt(R)*randn; else z []; end % 一阶EKF [x_ekf, P_ekf] ekf_step(x_ekf, P_ekf, F, dt, Q, R, z); X_ekf(:,k) x_ekf; % 二阶EKF [x_so, P_so] soekf_step(x_so, P_so, F, dt, Q, R, z); X_so(:,k) x_so; end为什么要用周期量测而不是每步都测这是为了给状态估计留出“预测”的空间如果每个仿真步都能测到高精度位置那么即便滤波算法一般也能把位置刷到接近真实值对比不出预测修正的价值。把测量周期拉到 0.1 s滤波器需要在两次量测之间依靠模型预测撑 10 步这时预测质量直接决定估计精度的上限。4.2 雅可比与Hessian手推函数如何高效实现MSD模型只有二维状态手推雅可比和Hessian比用符号工具箱更高效也更不容易引入运行时开销。符号计算在写代码原型时很方便但放到蒙特卡洛仿真里会慢到让人怀疑人生。手推的结果很清晰(f_1) 对状态求导得到 (A) 的第一行 ([1, dt])(f_2) 的雅可比行是 ([-dt \cdot (k_0 3k_1 x_1^2)/m, , 1 - dt \cdot c/m])。组合起来function A jacobian_f_msd(x, dt) m 1.2; c 0.8; k0 2.5; k1 0.8; x1 x(1); A [1, dt; -dt*(k0 3*k1*x1^2)/m, 1 - dt*c/m]; endHessian 部分我习惯用三维数组存第三维索引对应状态转移函数的分量。H(:,:,1)是 (f_1) 的 Hessian全零H(:,:,2)是 (f_2) 的 Hessian只有 ((1,1)) 元素非零function H hessian_f_msd(x, dt) m 1.2; k1 0.8; x1 x(1); H zeros(2,2,2); H(1,1,2) -6*k1*x1*dt/m; end写这段代码时一定要对着手推结果逐项核对。Hessian 矩阵的对称性是一个很好的自查点二阶导矩阵必须对称如果写出来的代码上下三角不对称基本可以断定偏导求错了。我的经验是先手写推导一遍再对照代码别跳步。另外参数在函数内写死只是为了演示清晰实际工程建议把参数做成结构体传入避免硬编码。4.3 一步滤波函数预测修正与量测更新这里给出核心的soekf_step函数一阶EKF可以看成它去掉二阶修正后的退化版本function [x, P] soekf_step(x, P, F, dt, Q, R, z) A jacobian_f_msd(x, dt); H hessian_f_msd(x, dt); % ---- 预测 ---- f f_msd(x, F, dt); % 一阶协方差预测 P_pred A*P*A Q; % 二阶修正1状态预测中的曲率修正 corr zeros(2,1); for i 1:2 corr(i) 0.5 * trace(H(:,:,i) * P); end % 二阶修正2协方差预测中的高阶项 P2 zeros(2,2); for i 1:2 for j 1:2 Eij zeros(2,2); Eij(i,j) 1; P2 P2 0.5 * trace(H(:,:,i)*P*H(:,:,j)*P) * Eij; end end P_pred P_pred P2; x_pred f corr; % ---- 量测更新 ---- if isempty(z) x x_pred; P P_pred; return; end Hm [1, 0]; % 量测矩阵只测位置 S Hm*P_pred*Hm R; K P_pred*Hm / S; x x_pred K * (z - Hm*x_pred); P (eye(2) - K*Hm) * P_pred; end状态转移函数f_msd也要定义清楚function x_next f_msd(x, F, dt) m 1.2; c 0.8; k0 2.5; k1 0.8; x1 x(1); x2 x(2); x_next zeros(2,1); x_next(1) x1 dt*x2; x_next(2) x2 dt*(F/m - c/m*x2 - k0/m*x1 - k1/m*x1^3); end从实现上看SO-EKF比一阶EKF多出来的代码只有十几行三维数组的 Hessian、状态预测的修正循环、协方差预测的双重求和循环。运行开销在二维问题里几乎可以忽略不计换来的是非线性更强场景下的稳定性提升。有个细节更新步骤里我直接用了x_pred和P_pred但没有再对量测模型做二阶修正因为本例量测是线性的。如果哪一天换成非线性量测必须仿照预测部分在量测预测处也叠加曲率修正否则量测更新里依然存在一阶近似误差。5. 一阶与二阶EKF的仿真拉锯战强非线性下的真实差距5.1 测试场景设计与参数选择仿真参数已经在前面的代码里给出。这里补充说明选择它们的理由。外力 (F 0.5\sin(0.5t)) 频率低、幅值适中目的是让系统在有限位移范围内持续振动同时避免位移过大导致数值不稳定。(dt 0.01) s 相对系统固有周期足够小保证欧拉离散化误差在可接受范围内。量测周期 (t_m 0.1) s 是刻意拉开的给滤波器的模型预测留出足够的“表演空间”。过程噪声协方差 (Q) 的两个对角元素取了不同量级位置通道 (10^{-4} \cdot dt)速度通道 (5 \times 10^{-2} \cdot dt)。速度通道的噪声远大于位置通道因为真实世界中速度受到的外界扰动和模型误差通常更显著。量测噪声 (R 5 \times 10^{-3}) 对应位置测量的标准差约 (0.07) m和系统位移幅值相比算是中等噪声不至于滤波效果全靠量测硬拉。这里有一个关键设定真实系统和滤波器使用完全相同的 (f_{\text{msd}}) 动力学方程和参数。这样对比的是“算法本身”的差异而不是“模型失配”的影响。如果真实系统存在参数漂移或未建模动态两个滤波器都会受影响对比效果会被干扰不适合作为验证算法原理的起点。5.2 典型结果的定性分析和RMSE统计以某次固定随机种子的运行为例随机种子不同具体数值会有波动但趋势一致一阶EKF在位移接近峰值时开始出现明显的跟踪滞后尤其是速度通道。位置通道因为有 0.1 s 一次的量测不断“拉回”误差还能被压制在中等水平速度通道的误差一旦在两次量测之间被模型预测带偏下一次量测只能部分纠正导致误差出现周期性起伏。SO-EKF在同样的量测序列下位移峰值附近的预测偏移被曲率修正部分抵消速度轨迹的跟踪更紧。从RMSE来看位置通道的改善幅度不大——因为位置量测直接约束了它——但速度通道的RMSE在典型运行中下降了约三到四成协方差的一致性也更好一阶EKF在非线性区间的对角元偏小表现出过度自信SO-EKF的协方差会因为二阶项而适度膨胀正好匹配实际误差水平。这个对比结果有一个值得强调的结论SO-EKF不是对每个场景都“完胜”。在弱非线性区间位移小(x_1^3) 项可以忽略两个滤波器的轨迹几乎重合因为修正项本身趋近于零。差别只在状态分布撞进非线性强区间的瞬间显现。所以选型时应该先评估系统工作点的非线性强度而不是盲目上二阶。我做了 60 次蒙特卡洛均值统计速度通道RMSE的均值差异和单次结果一致但单次波动的离散度较大。要得出可靠结论至少跑几十次随机实验而不是拿一次“幸运轨迹”说事。这一点在论文和项目汇报里尤其重要。6. 调参、发散排查与实际经验那些仿真文档里不会写的坑6.1 二阶修正项符号反了的隐蔽故障手推Hessian时最容易出现的错误是符号问题。以 (f_2 x_2 dt \cdot (F/m - c/m \cdot x_2 - k_0/m \cdot x_1 - k_1/m \cdot x_1^3)) 为例对 (x_1) 求二阶导需要格外小心。一阶导里 (-k_1/m \cdot x_1^3) 变成 (-3k_1/m \cdot x_1^2)再求一次导得到 (-6k_1/m \cdot x_1)乘上 (dt)。很多人在一阶导符号处丢负号二阶修正项就整体反号。反号的隐蔽性在于系统在正位移区间工作时错误的正修正会让预测值比一阶EKF更偏但轨迹图上依然能保持“稳定”只是偏差比一阶EKF还大。排查方法也很简单把二阶修正项单独提取出来观察它的方向和大小是否与理论分析一致。对本例当 (x_1 0)、(P_{11} 0) 时速度通道的修正量应为负数值随 (x_1) 和 (P_{11}) 单调增大。如果代码输出了相反方向的修正先回头检查 Hessian 定义。6.2 Q、P0与修正项之间的“三角关系”SO-EKF 对初值协方差 (P_0) 和过程噪声 (Q) 的敏感程度比一阶EKF更高因为曲率修正项直接乘了协方差 (P)。如果 (P_0) 设得过大修正项在一开始就会过度发力造成预测值“过冲”如果 (P_0) 设得过小修正不足又退化回一阶EKF。我的建议是从物理意义出发设定 (P_0)根据初始位移和速度偏差的合理范围取方差而不是随手填一个 0.01 或 100。(Q) 的情况类似。(Q) 太小会让滤波器过度信任模型(P) 在量测间隔里收缩得过快修正项随之变小(Q) 太大会让 (P) 膨胀修正项连续大幅干预预测可能把轨迹推向另一种发散模式。经验上先依据真实过程噪声的量级设置 (Q)再以“滤波残差是否白化”为指标微调。残差均值显著非零往往说明预测偏置没修到位可能是 (Q) 偏小或修正项实现有误。6.3 离散化误差与量测周期的匹配问题二阶EKF修正项的作用在离散化步长 (dt) 过大时会变得混乱。前向欧拉本身有一阶截断误差这个误差会被滤波器的状态方程“读”成模型偏差。如果 (dt) 取得太大欧拉误差主导的预测偏差和曲率修正混在一起无法区分你会看到增加二阶修正反而让估计变差。一般建议 (dt) 至少小于系统最快模态周期的二十分之一。对参数 (m1.2)、线性刚度 (k_02.5)线性固有频率 (\omega_0 \approx \sqrt{2.5/1.2} \approx 1.44) rad/s加上非线性刚度后等效频率会更高。保险起见我把 (dt) 控制在 0.01 s再小当然更准但仿真步数会涨。量测周期则要小于系统变化的时间尺度否则即便SO-EKF也只能靠模型盲推两次量测间的误差自然放大。这个“量测周期 vs 非线性强度”的匹配关系是比调 (Q) 更常被忽视的坑。6.4 Hessian计算的调试技巧最后分享一个很实用的调试技巧把滤波器里用的状态转移函数和数值微分对照。对二维系统可以直接用有限差分近似Hessian来验证手推结果。比如在 (\hat{x}) 附近取微扰 (\delta)用差分形式去近似二阶导将数值结果与手推Hessian对比差异在 (10^{-5}) 量级基本说明符号和系数都是对的。这个技巧在刚写完代码时尤其管用。我每次实现新系统的SO-EKF都会先跑一次“Hessian校验”脚本确认无误后再进主仿真。别看这步简单它能省掉后面几天的排查时间。如果你在调试中遇到修正项方向对但幅度不对的情况也可以把中间量 (\mathrm{tr}(H_i P)) 打印出来逐项核对协方差矩阵的交叉耦合项往往一眼就能看出是哪一项偏导漏了系数。