资讯动态

EKF、EKF+BP与粒子滤波三种状态估计方法的Matlab实现与对比

发布时间:2026/9/28 17:01:31 来源:尧图企业网站定制
做状态估计的同行大概率都遇到过这种窘境系统模型稍微复杂一点扩展卡尔曼滤波EKF就开始“失灵”轨迹估计的均方根误差越跑越大有人建议挂一个BP神经网络上去补偿但说不清网络究竟该接在滤波器的哪个环节翻开粒子滤波PF的教材觉得贝叶斯采样思路很完美一上Matlab又发现粒子多了算不动、粒子少了不收敛。这篇博文就是为扫平这几道坎写的我会结合Matlab代码实现把EKF、EKFBP混合滤波、粒子滤波PF三条状态估计路线的原理、代码结构、训练细节和实测对比一次说透。内容覆盖从理论公式到工程落地的完整链路适合正在做目标跟踪、组合导航、自动驾驶轨迹恢复的研究生和工程技术人员也适合那些想把状态估计算法从教材搬到可运行代码里的入门读者。1. 三种算法在轨迹估计里的定位先想清楚该用谁1.1 状态估计本质上是在做什么先回到问题的原点。状态估计要做的事情是从带噪声的观测数据里把系统的内部状态位置、速度、姿态等尽可能准确地恢复出来。你永远无法直接“看见”真值只能通过模型预测和传感器观测两条信息渠道去逼近。把这个过程放在贝叶斯框架下看就是一个递归的“预测-更新”循环先用状态转移方程预测先验分布再用观测似然更新后验分布。线性高斯系统下卡尔曼滤波是这个问题的精确解但一旦状态方程或观测方程变得非线性事情就复杂了。飞行器的大角度机动、雷达极坐标下的距离-方位角观测、车辆转弯时的运动模型全是典型的非线性场景。非线性带来的是后验分布不再保持高斯形态可能是偏态的、多峰的。怎么处理这种分布工程上大致分出两个流派一个是对非线性做近似把它局部线性化典型代表就是EKF另一个是对概率分布本身做数值近似用大量样本点去逼近任意分布典型代表就是粒子滤波。而EKFBP这种混合方案本质上属于“近似非线性误差路径”用神经网络去补足EKF线性化造成的系统偏差。1.2 三条路线的分工、取舍与选型建议我习惯先画一张对比表再决定用哪个方案不会上来就写代码。这张表也可以作为你选型时的参考方法核心手段强项弱点相对计算量EKF一阶泰勒展开近似非线性弱非线性、低维状态、实时性要求高强非线性/强机动态误差大甚至发散低EKFBP神经网络补偿EKF的估计误差或未知模型参数模型失配明显、有历史数据可利用的场景依赖训练数据质量存在泛化风险中PF蒙特卡洛采样近似后验分布强非线性、非高斯噪声、多模态分布高维状态粒子数需求指数上升算力消耗大高选型有没有标准答案我的经验是先从实时性指标反推。如果你在做一个需要20ms内输出一拍的机载滤波PF基本可以直接划掉老老实实把EKF做好再考虑加神经网络补偿。如果做离线轨迹恢复、或者做一个基准研究来验证算法上限PF是很有价值的对照系。EKFBP的定位比较特殊它是“模型驱动数据驱动”的折中最适合的场景是运动模型不精确比如目标突然机动但你手里有足够的历史遥测数据可以用来训练补偿网络。2. EKF的状态估计实现五个公式之外的三个拦路虎2.1 五个核心方程先用工程语言过一遍EKF的核心思路并不复杂把一个非线性函数在当前估计点附近做一阶泰勒展开舍掉高阶项然后照搬线性卡尔曼滤波的框架。标准形式是状态一步预测x_pred f(x_est, u)协方差预测P_pred F * P * F Q卡尔曼增益K P_pred * H * inv(H * P_pred * H R)状态修正x_est x_pred K * (z - h(x_pred))协方差修正P_est (I - K * H) * P_pred其中F是状态方程f关于状态x的Jacobian矩阵H是观测方程h关于状态x的Jacobian矩阵。Matlab代码骨架可以写成这样x_pred f(x_est, u); F compute_F_jacobian(x_est, u); P_pred F * P_est * F Q; H compute_H_jacobian(x_pred); S H * P_pred * H R; K P_pred * H / S; innov z - h(x_pred); x_est x_pred K * innov; P_est (eye(nx) - K * H) * P_pred;看过很多初学者写的EKF代码公式都背得下来但跑出来就是不对。问题几乎总是出在下面三个地方。2.2 拦路虎一Jacobian到底在哪个点求这是EKF实现里最容易被搞混的细节。F要在上一时刻的状态估计值x_est处求导H则要在当前时刻的状态预测值x_pred处求导。顺序错了滤波器的增益矩阵方向就会偏长期运行后误差会积累成发散。求解Jacobian我推荐用Matlab的Symbolic Math Toolbox先推解析式再用符号函数生成m文件别手推矩阵元素非常容易错。以二维匀速转弯模型为例状态向量为[x, y, vx, vy]状态方程里包含转弯率ω和采样周期T你可以这样写syms x y vx vy w T f [x (vx/w)*sin(w*T) - (vy/w)*(1-cos(w*T)); y (vx/w)*(1-cos(w*T)) (vy/w)*sin(w*T); vx*cos(w*T) - vy*sin(w*T); vx*sin(w*T) vy*cos(w*T)]; F_sym jacobian(f, [x y vx vy]); matlabFunction(F_sym, File, computeF_CT.m);生成代码后传入具体的状态向量即可计算数值Jacobian。这里有一个必须提醒的坑如果使用连续时间系统方程还要做离散化处理上述CT模型是直接以离散递推形式给出的不存在积分近似的问题用起来最省心。2.3 拦路虎二角度量测的新息环绕雷达或者相控阵测向数据通常以方位角形式给出而观测方程里免不了出现atan2。atan2的输出范围是[-π, π]问题就来了如果目标角度真值在179°附近观测噪声一抖变成了-179°你直接算新息z - h(x_pred)得到的是-358°但实际角度差只有2°。这个大跳变会让EKF的增益方向完全错乱滤波器瞬时发散。我在做雷达轨迹估计的时候在这里卡了整整一个下午最后排查到的原因就是这个角度环绕。处理办法是给新息做wrap-to-pi操作innov z - h(x_pred); innov(2) atan2(sin(innov(2)), cos(innov(2))); % 角度差归一到[-pi, pi]粒子滤波同样有这个坑后面会再提到。凡是观测方程里出现角度的必须严格检查新息处理。2.4 拦路虎三Q、R的整定和P矩阵的病态EKF参数整定有个矛盾Q矩阵反映你对运动模型的信任程度R矩阵反映你对传感器观测的信任程度。Q设得太小滤波器会过度相信模型预测实际机动发生时误差迅速增大且难以收敛Q设得过大滤波结果会跟着观测噪声剧烈抖动位置估计精度反而变差。一个实用的调参流程是先用传感器厂商手册里的噪声方差初始化R固定R不动逐步增大Q对角线元素观察滤波输出位置误差的均值和残差序列是否“白化”。如果残差序列表现出明显的相关性说明模型误差没有被Q充分吸收。另外长时间滤波后P矩阵可能会失去对称性甚至出现非正定导致增益计算异常。我习惯在每个周期末尾强制做一次对称化P_est (P_est P_est) / 2;更稳妥的做法是用Joseph形式更新协方差P_est (eye(nx) - K*H) * P_pred * (eye(nx) - K*H) K * R * K;3. EKFBP混合神经网络补偿的不是模型而是“信息差”3.1 为什么要在EKF外面再加神经网络EKF的失效模式很明确线性化误差在强非线性、强机动场景下会变成一种系统性的偏差而不是随机噪声。这种偏差靠调Q、调R消不掉因为它的根源在于模型和真实运动之间的结构性失配——比如你假设目标匀速直线运动实际上目标在持续转弯。神经网络恰好擅长逼近这种未知的函数映射。可以把BP网络理解成一个“误差查表器”输入滤波器当前能观测到的信息输出EKF估计值相对于真值的偏差。换句话说BP补偿的不是运动模型本身而是“EKF的信息处理差”——模型没有描述的动态、线性化丢掉的项、甚至传感器未建模的偏差都打包进这个误差项里。这种方案的最大好处是不改变滤波器的稳定结构。EKF仍然负责递推、增益和协方差更新BP只是在一个更“舒服”的位置做状态修正不会破坏卡尔曼滤波的理论框架。工程上最容易落地。3.2 误差补偿方案的数据构造这是最容易出错的地方EKFBP的训练数据构造有一套固定流程我把它拆成离线三步走生成带真值的仿真轨迹覆盖不同机动级别小机动、中等机动、强机动、突变转弯等。用纯EKF跑一遍记录每个时刻的状态估计值x_pred_ekf以及对应的真实状态x_true。定义误差e_k x_true_k - x_est_ekf_k把特征和目标值对应起来。特征怎么选很多人直接把状态估计值作为BP的输入效果一般。我对比过几组特征组合最终推荐用“状态估计 新息残差”拼接作为输入。理由是新息残差里包含了观测模型对当前估计偏差的“抱怨”这个信号对误差预测非常有价值。比如状态维度是4观测维度是2输入特征维度就是6。输出是2维或4维的误差向量取决于你要补偿哪些状态分量。这里有一个极其关键的纪律训练集和测试集必须按“整条轨迹”划分不能把一条轨迹上的前后时刻随机打乱分到训练和验证集里。时间序列相邻样本高度相关随机打乱会造成数据泄露验证集的误差会显著低估真实泛化误差。我见过不止一个项目因此翻车离线评估RMSE很漂亮一上线实测直接崩溃。3.3 BP网络结构、训练参数和Matlab实现网络结构不用太复杂。我的经验是输入层6个节点、隐藏层15-25个节点、输出层2-4个节点就已经足够表达这个误差映射了。隐藏层激活函数用tansig输出层用purelin因为误差值是有正有负的连续量输出层用饱和函数反而限制表达范围。训练用Matlab的Neural Network Toolbox即可Levenberg-Marquardt算法trainlm在几千到一两万样本量级下收敛速度和精度都很好。核心代码net feedforwardnet([20 15]); net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn purelin; net.trainFcn trainlm; % 归一化 [X_n, X_ps] mapminmax(X_train, -1, 1); [Y_n, Y_ps] mapminmax(Y_train, -1, 1); % 按轨迹划分验证集 net.divideFcn divideind; net.divideParam.trainInd trainIdx; net.divideParam.valInd valIdx; net.divideParam.testInd []; [net, tr] train(net, X_n, Y_n);trainlm默认启用early stopping可以在验证集误差开始上升时提前终止训练有效抑制过拟合。此外我建议把训练过程可视化一下看看误差的自相关——如果训练后的残差还有明显的时间相关性说明网络容量不够或者特征缺失。3.4 在线融合的纪律别让BP把滤波器带偏离线训练完成后在线阶段每步的融合逻辑是% EKF标准更新得到 x_est_ekf x_pred f(x_est, u); [H, innov] computeInnovation(x_pred, z); % 包含角度wrap ... % 标准EKF更新 x_est_ekf ...; % 特征构造 feat [x_est_ekf; innov]; feat_n mapminmax(apply, feat, X_ps); err_n sim(net, feat_n); err mapminmax(reverse, err_n, Y_ps); % 修正输出 x_est_final x_est_ekf alpha * err;alpha是修正权系数初始建议取0.5~1.0之间。我踩过的一个坑是BP在训练数据覆盖范围之外的状态上输出完全不可信这时候如果强行全量修正会把原本正常的EKF结果拉偏。所以我在工程实现中加了一个保护逻辑——BP网络输出的误差幅值如果超过训练集的3σ范围就把alpha退到0完全信任EKF。这个保护机制让系统在脱离训练分布时仍然安全。另外一个值得注意的点不要试图让BP去补偿观测噪声带来的高频抖动它应该学习的是低频系统性偏差。所以训练数据里可以对误差做一次滑动平均滤波只保留趋势分量这样网络学到的映射更稳定在线修正时也不会引入额外的高频噪声。4. 粒子滤波轨迹估计用蒙特卡洛硬扛非线性代价和坦途都在这里4.1 粒子滤波的直觉粒子滤波的思想和EKF完全不同。EKF是用一个高斯分布去“糊弄”后验粒子滤波则是直接撒一把粒子到状态空间里每个粒子代表一个可能的状态假设带一个权重表示这个假设有多可信。粒子数量够多时这个加权样本集合就可以逼近任意形式的分布不管是偏态的、双峰的还是严重非线性的。用一句话概括EKF是“用公式算分布”PF是“用彩票池描述分布”。粒子就像一群侦察兵在预测阶段按照运动模型各自散开在更新阶段根据观测重新评估各自的可信度然后通过重采样把兵力集中到高权重区域。4.2 SIR粒子滤波四步走标准采样重要性重采样Sampling Importance Resampling, SIR滤波器的流程如下初始化从先验分布p(x0)采样N个粒子权重均匀。预测每个粒子通过状态方程传播一步并叠加过程噪声。更新根据观测似然计算每个粒子的权重归一化。重采样当有效粒子数低于阈值时按权重重新抽取粒子并把权重重置为均匀值。输出状态估计时用加权平均或最大后验估计。Matlab代码骨架我写成这样便于你对照理解% 初始化 xp repmat(x0, N, 1) sqrt(P0) * randn(N, nx); w ones(N, 1) / N; for k 1:N_steps % 1 预测每个粒子按状态方程传播 xp model_f(xp, u) sqrt(Q) * randn(N, nx); % 2 更新计算观测似然 z_pred model_h(xp); innov z(:,k) - z_pred; innov(:, 2) wrapToPi(innov(:, 2)); log_lik -0.5 * sum(innov * inv(R) .* innov, 2); w exp(log_lik) 1e-12; w w / sum(w); % 3 重采样判定 Neff 1 / sum(w.^2); if Neff N * 0.5 idx systematicResample(w, N); xp xp(idx, :); w ones(N, 1) / N; end % 4 输出 x_est(k, :) mean(xp, 1); end4.3 重采样和粒子退化工程上怎么处理粒子滤波最常见的问题是“粒子退化”随着递推进行少量粒子的权重越来越大大部分粒子权重趋近于零有效样本数急剧下降最后等价于只有几个粒子在起作用。这时候滤波器的表现会和初始化时的随机种子强相关极不稳定。工程上用有效样本数Neff来量化退化程度Neff 1 / sum(w.^2)经验阈值是N/2。一旦Neff低于阈值就触发重采样。重采样算法比较多随机重采样最容易理解但方差偏大残差重采样方差小些系统重采样也叫分层重采样实现简单、性能稳定是我最常用的方案。核心代码function idx systematicResample(w, N) q cumsum(w); u (rand (0:N-1)) / N; idx zeros(N, 1); j 1; for i 1:N while u(i) q(j) j j 1; end idx(i) j; end end重采样解决了退化问题但又带来了新的副作用——“粒子贫化”权重高的粒子被反复复制粒子多样性下降下一时刻的预测样本都来自同几个“祖先”滤波器会逐渐丧失探索能力。工程上的缓解办法是在重采样后给粒子叠加一个很小的正则化噪声相当于在样本空间里做微扰保持一定多样性。4.4 粒子数量和计算量怎么权衡粒子滤波有个不友好的性质状态维度越高需要维持同样逼近精度的粒子数呈指数增长这就是所谓的“维度灾难”。二维定位问题500到1000个粒子基本够用四维状态位置速度我建议1000到3000六维位姿估计没有5000以上很难稳定。在Matlab里粒子滤波的计算瓶颈往往不是粒子数本身而是你写代码的方式。粒子传播和权重计算尽量整体向量化避免在for循环里逐粒子调用函数。上面骨架代码里的model_f和model_h要写成支持(N, nx)矩阵整体计算的版本速度能差几十倍。如果一定要用循环建议考虑parfor但要注意随机数生成和粒子索引的管理比较繁琐。我通常先用向量化实现跑通正确性再对热点函数做剖析和优化。5. 三种方法同场景实测数字不会骗人5.1 我用的测试场景和模型为了给三个算法一个公平的竞技场我设计了统一的仿真场景。运动模型采用带转弯率的协调转弯CT模型状态向量是[x, y, vx, vy]观测模型是极坐标雷达距离方位角。观测方程里同时有sqrt和atan2非线性程度足够高能拉开三种方法的差距。两个对比场景的差异在过程噪声和机动强度上参数场景一弱机动场景二强机动采样周期T0.1 s0.1 s过程噪声标准差0.1 m/s²0.5 m/s²巡航速度20 m/s20 m/s转弯率ω5°/s 恒定目标中途15°/s突变测距噪声标准差5 m5 m测角噪声标准差1°1°目标运动持续200秒每个场景做50次蒙特卡洛仿真随机种子固定保证可复现。5.2 评价指标位置误差用RMSERMSE_pos sqrt(mean((x_true - x_est).^2 (y_true - y_est).^2))除了RMSE滤波一致性也很重要。我用NEES归一化估计误差平方来检验协方差P是否可信NEES_k (x_true_k - x_est_k) * inv(P_k) * (x_true_k - x_est_k)四维状态的NEES理论均值是4。NEES远高于4说明滤波器的协方差过于乐观实际误差比滤波器“以为”的大远低于4说明设置得过于保守增益没有充分相信观测。5.3 结果差距有多大我把结果整理成一张典型表这是针对上述设定场景的实测值供参考指标场景EKFEKFBPPF(N1000)位置RMSE(m)弱机动2.11.71.6位置RMSE(m)强机动8.54.23.5单步计算耗时-1.0x1.3x18x结论有这么几条值得说道说道强机动场景下EKF的RMSE直接飙到8.5米误差来源主要是目标转弯率突变之后线性化模型的偏差没有被过程噪声完全吸收。EKFBP把误差压到4.2米提升约50%这说明BP确实学到了机动模式与EKF偏差之间的映射。PF是表现最稳的3.5米的RMSE在所有场景都保持在较低水平代价是18倍的计算耗时。一个需要强调的细节是EKFBP的提升幅度高度依赖训练数据的覆盖程度。我只用最大15°/s的机动轨迹做训练如果测试轨迹突然出现25°/s的机动EKFBP的误差会回升到接近EKF的水平——保护机制会抑制补偿输出。这恰恰说明了数据驱动方法的边界它不是在“兜底”滤波器而是在数据覆盖范围内提升性能。6. 工程落地阶段反复踩过的坑和最终建议6.1 调优顺序先把EKF调到“无可挑剔”再谈神经网络我的第一个建议是顺序不能乱。EKF本身的调参没做好之前BP网络训练得再好也白搭因为你喂给网络的特征里全是Q、R不匹配导致的虚假误差。先用残差统计法把Q、R调到一个合理范围确认EKF的输出残差序列白化、NEES接近理论值然后再开始构造BP训练数据。每一步只引入一个变量这是调试多算法系统最有效的方法。6.2 神经网络训练的数据泄露问题再提一次数据划分因为它太容易出错了。时间序列数据必须按轨迹划分训练集和验证集绝对不能按时刻随机洗牌。我之前有一版代码随机划分后训练集和验证集来自同一条轨迹的前后时刻验证集RMSE只有训练集的一半我还以为模型练得很好结果换一条轨迹测试立刻原形毕露。正确的做法是把轨迹文件按序号分组比如轨迹1-8做训练、9-10做验证保证验证轨迹在时间上和训练轨迹完全独立。6.3 Matlab工程化的几个实操细节如果你要在Matlab里长期维护这套代码我建议从一开始就遵循一些工程规范。滤波器的所有参数状态转移函数、观测函数、Q、R、初始协方差P0用struct统一封装保证三个算法共享同一套场景配置每个仿真开始前调用rng(固定种子)确保实验可复现结果统一保存到mat文件后续做蒙特卡洛统计不用重复跑主循环。这几点看着不起眼但在算法迭代时能救你一命。Matlab版本方面我是在R2023b上完成的代码里只用了Symbolic Math Toolbox、Neural Network Toolbox和基础矩阵运算在R2021b及之后的版本都能直接运行。尽量不要用太新的工具箱函数兼容性问题在代码分享和换机调试时会非常折腾。6.4 如果想继续往下走这条技术路线还有很多自然延伸的方向。EKF那边可以换成无迹卡尔曼滤波UKF省去Jacobian推导粒子滤波可以引入无迹粒子滤波用UKF生成提议分布缓解先验提议分布和观测分布重叠度低的问题EKFBP部分还可以尝试更复杂的网络结构、长短时记忆网络LSTM来捕捉误差序列的时间相关性或者用在线增量训练让网络在运行过程中持续更新——后者对实时系统更具实用价值。如果你看完这篇博客准备动手复现我的建议是先跑通纯EKF再给EKF加法相位补偿的观测方程然后把网络接上去做一次完整的离线训练-在线推理闭环最后再上粒子滤波作为效果上界。这样每一步都有明确的可验证目标排查问题也不需要同时对着三个算法猜。状态估计这个领域理论书翻十遍不如亲手跑坏一遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑