简介:面向雷达目标跟踪与多目标数据关联学习者的 MATLAB 算法实现合集,围绕 NNDA、PDA、JPDA 与 IMM 四类经典方法展开,可用于理解最近邻关联、概率数据关联、联合概率数据关联以及交互多模型的建模思路与仿真验证。压缩包共 7 个文件,全部为 .m 脚本,体积约 16KB,分别负责数据生成、单目标与多目标关联主流程、IMM 交互模型测试等环节;代码体量轻便,便于在 MATLAB 中直接运行与逐行调试。已有 2334 人学习下载,具备一定的参考基础。读者可据此对照状态方程、观测模型与卡尔曼滤波框架,观察不同关联策略在杂波、目标临近与机动场景下的表现差异,并在此基础上调整门限、关联概率与模型转移概率等参数,完成从算法复现到性能对比的完整练习,适合课程设计、课题预研与跟踪算法入门者按需取用。
1. 从第 12 帧的航迹串线说起:NNDA/PDA/JPDA/IMM 这套 MATLAB 代码能替你验证什么
两架目标在 8 km 处交叉,杂波每帧 5 个点,最近邻关联跑到第 12 帧就把两条航迹换了过来——这套 NNDA / PDA / JPDA / IMM 数据关联算法 MATLAB 代码,就是我把同一份量测喂进四种关联器对拍时用的工具。它把最近邻关联、概率数据关联、联合概率数据关联三种关联器和交互式多模型滤波器拆成可以互换的函数,仿真真值、量测生成、关联、滤波更新、指标评估各占一层,换一个cfg.assoc字符串就能跑出完全不同的一条航迹。适合三类人:做雷达或红外多目标跟踪课程设计、需要一套能跑通的参考实现的学生;要给自己的滤波器找基线的算法工程师;还有论文复现时发现公式和代码对不上、想找个能单步调试的参照物的研究者。它的价值不在于性能多强,而在于四种关联器共用同一套接口、同一份量测、同一套指标,差异能直接看出来。
2. 四种关联器的分工:NNDA、PDA、JPDA 与 IMM 的选型依据
2.1 关联的骨架只有四步:预测、波门、权重、更新
不管挂什么名字,单帧关联都逃不掉四步。第一步预测,把上一帧的后验x(k-1|k-1)、P(k-1|k-1)推到x(k|k-1)、P(k|k-1);第二步算新息协方差S = H*P*H' + R,用新息ν = z - H*x的二次型ν'*S^-1*ν与卡方门限比大小,落在门内的量测才进入候选集;第三步给候选量测分权重,这一步是四种算法真正的分水岭;第四步按权重做状态与协方差更新。
门限γ由卡方分布反函数给出,二维量测、P_G = 0.99时γ ≈ 9.21,三维时约 11.34。这个数值背后有一层容易被忽略的含义:门限越大,落入波门的杂波越多,PDA的权重被稀释得越厉害;门限越小,真值被漏掉的概率越高,航迹容易断。我在调试时习惯把P_G固定在 0.99,先保证不漏,再回头压杂波。
提示:波门是椭球不是圆。目标距离 2 km 和 20 km 时,同样的方位角标准差对应的横向位置误差差 10 倍,波门形状会跟着变,这一点在直角坐标下直接用固定半径做门限一定会出问题。
2.2 NNDA 的「取最小」与 PDA 的「全加权」差在哪
NNDA(最近邻)只做一件事:在门内候选里挑马氏距离平方最小的那个量测,其余全部丢弃,然后按标准卡尔曼更新。它的计算量和单目标单量测的卡尔曼完全一样,工程上极其好实现,代价是对杂波毫无抵抗力——门内只要出现一个比真值更靠近预测的杂波点,这一帧的更新就被带偏,而卡尔曼的协方差还会因为「用了一个量测」而收缩,等于把错误当成了确信。
PDA换了思路:门内所有量测都保留,按似然分配权重,再算一个组合新息。权重公式是
β_i = e_i / (b + Σ_j e_j), β_0 = b / (b + Σ_j e_j) e_i = N(ν_i; 0, S) b = λ * (1 - P_D * P_G) / P_Dβ_0是「门内没有来自目标的量测」这一事件的概率,它同时覆盖了漏检和量测落在门外两种情况,所以分子里出现的是1 - P_D*P_G而不是1 - P_D。λ是杂波密度,单位必须和波门体积配套:用「每帧每平方米多少个杂波点」这种面密度,波门体积就按面积算;用「每帧每个波门多少个杂波点」,那b里就不能再乘体积。这一条我在后面的排查章节还会再拎出来说一次,因为它是PDA跑出诡异结果的头号原因。
PDA的协方差更新和标准卡尔曼不一样,多了一项新息散布补偿:
P = β_0*P(k|k-1) + (1-β_0)*P_c + K*[Σ β_i ν_i ν_i' - ν ν']*K'P_c是常规更新项。中间那个括号衡量的是门内量测的分散程度,量测越散、权重越平均,这一项越大,协方差不会像NNDA那样被错误收缩。很多人抄PDA代码只抄状态更新不抄这一项,跑出来的航迹看着还行,但滤波器的协方差早就失真了,后面做自适应门限或者做多模型混合时会连锁出错。
2.3 JPDA 比 PDA 开销大的原因:联合事件与确认矩阵
PDA假设只有一个目标,所有门内量测要么来自它、要么是杂波。多目标场景下这个假设塌了:一个量测可能同时落在两条航迹的波门里,PDA会把同一个点算给两个目标,两条航迹被拉到一起,这就是典型的「航迹合并」。
JPDA的做法是先建确认矩阵Ω,行是航迹、列是量测,Ω(t,j)=1表示量测j落在航迹t的门内;再加一列全 1 的「虚警列」表示该航迹这一帧没有量测。然后在矩阵上枚举所有可行联合事件:每个量测最多分配给一条航迹,每条航迹最多拿一个量测。每个联合事件算一个联合概率,把同一个(t,j)组合在所有事件中的概率加起来,就是边缘关联概率β_tj。
代价就在这里。航迹数nT、门内量测数m时,联合事件数量级接近m!/(m-nT)!,nT=4, m=6就已经上千,nT=6, m=8直接上万,纯 MATLAB 循环跑实时仿真会卡死。工程上必须做两件事:一是簇分解,把通过共享量测连通的航迹分成独立的小簇,每簇单独枚举,簇之间互不影响;二是给枚举加剪枝,联合概率低于阈值的直接丢掉,或者干脆退到Cheap JPDA(Fitzgerald 的一阶近似,只保留「量测只属于一条航迹」和「量测不属于任何航迹」两类事件)。这套代码里两条路径都留了开关,后面 4.3 会写怎么切。
2.4 关联与滤波的耦合顺序:先 IMM 还是先关联
这个问题在单模型下不存在,上了IMM就绕不开。两种顺序:
第一种是先用各模型的混合预测合成一个x_pred、P_pred,拿它算S和波门,做关联拿到β,然后每个模型用同一组β各自更新,最后按模型概率组合。好处是关联只做一次,计算量省;坏处是混合协方差通常比单模型协方差大(模型间存在分歧),波门被撑大,杂波进得更多。
第二种是每个模型各自算S_j、各自算似然,关联时用各模型的门限并集,模型概率更新用各模型自己的似然。好处是模型分辨力强,机动段和非机动段的行为差异明显;坏处是计算量翻倍,而且并集门限更大。
我的默认做法是折中:波门用混合预测算,控制计算量;模型似然用各模型自己的新息和新息协方差算,保证模型概率能正确反映「哪个模型更解释当前量测」。这套代码的predictTrack就是按这个顺序拆的,assocPDA里拿到的β会原样传给每个模型的更新函数。要注意的是,PDA的协方差补偿项必须每个模型各算一遍,不能拿混合后的β和混合后的新息散布去糊弄,否则模型概率会在机动段抖动得很厉害。
2.5 选型对照表:五种典型场景怎么配
| 场景 | 推荐组合 | 理由 | 代价 |
|---|---|---|---|
| 单目标、杂波少(每帧 ≤1 个/波门) | NNDA + 单模型 KF | 计算量最小,航迹平滑 | 一个杂波点就能带偏一帧 |
| 单目标、杂波密度中等 | PDA + 单模型 KF | 权重自动抑制杂波,实现简单 | 协方差补偿项不能省 |
| 多目标、间距大于 3 倍波门 | PDA 逐航迹独立跑 | 门几乎不重叠,JPDA 没收益 | 无 |
| 多目标、密集交叉 | JPDA + 簇分解 | 从根上避免航迹合并 | 事件枚举,需要剪枝 |
| 目标有机动(转弯、加减速) | JPDA + IMM(CV + CT) | 模型概率自动切换,门限自适应 | 调转移矩阵,参数敏感 |
表格里的「3 倍波门」是个经验值,不是理论界。门重叠率低的时候JPDA和PDA的结果几乎重合,我一般在仿真里先跑一遍重叠率统计,低于 5% 就直接上PDA,没必要为了一点点精度把实时性搭进去。
3. 把代码跑通:从 config 到单帧中间量的保存
3.1 目录与入口脚本的对应关系
拿到一个 MATLAB 算法包,我第一件事不是跑main,而是看目录怎么分层。这套代码的分层是清晰的,通常长这样:
| 路径 | 作用 | 什么时候会改它 |
|---|---|---|
main_demo.m | 单次仿真入口 | 换场景、换关联器 |
main_mc.m | 蒙特卡洛入口 | 出对比曲线 |
config/defaultConfig.m | 全部参数 | 调参只改这里 |
sim/simulateScene.m | 真值航迹与量测生成 | 改机动形式、杂波强度 |
assoc/assocNNDA.m、assocPDA.m、assocJPDA.m | 三种关联器 | 改门限、改权重公式 |
filter/immStep.m、kfUpdate.m | 滤波核心 | 加模型、换非线性滤波 |
util/gateMeasurements.m、evalMetrics.m | 波门与指标 | 换评价方式 |
判断一个包能不能用,看两点就够:参数是不是集中在一个地方,算法函数是不是没有全局变量。这两条都满足,说明作者是按「能对拍」的思路写的,改起来不会牵一发动全身。
3.2 把四种算法收进一份 config
function cfg = defaultConfig() % 四种关联器共用一份参数结构体,改这里就够了 cfg.K = 100; % 仿真帧数 cfg.T = 1.0; % 采样周期,s cfg.sigmaR = 50; % 距离量测标准差,m cfg.sigmaB = 0.3*pi/180; % 方位量测标准差,rad cfg.PD = 0.9; % 检测概率 cfg.PG = 0.99; % 波门概率 cfg.lambda = 1e-5; % 杂波面密度,个/(帧·m^2) cfg.gamma = chi2inv(cfg.PG, 2); % 二维量测的卡方门限 cfg.assoc = 'JPDA'; % 'NNDA' | 'PDA' | 'JPDA' cfg.nMc = 100; % 蒙特卡洛次数 cfg.seed = 20240501; % IMM 模型集:CV + CT cfg.models = {'CV','CT'}; cfg.qCV = 0.5; % CV 模型加速度噪声标准差,m/s^2 cfg.qCT = 0.05; % CT 模型转弯率噪声标准差,rad/s cfg.PI = [0.95 0.05; 0.05 0.95]; % 模型转移矩阵,行和必须为 1 endcfg.lambda这一项最容易拍脑袋。正确做法是拿实际数据标定:统计每帧的杂波点数除以监视区面积,得到一个面密度。如果手上只有「每个波门平均几个杂波点」这种统计,那就不要用面密度,直接把b写成λ_gate * (1-PD*PG)/PD,λ_gate是无量纲的每波门杂波数,此时波门体积V_g不参与计算。
cfg.gamma用chi2inv算而不是写死 9.21,是为了量测维度变化时不用改代码。方位量测在直角坐标下做转换后,噪声不再严格高斯,实际噪声协方差比标称值大,我一般会把gamma再乘 1.2~1.5 的膨胀系数兜底,否则交叉点附近容易丢点。
cfg.PI的对角线不是随便填的。它跟模型驻留时间挂钩,p_ii ≈ 1 - T/τ,τ是目标停留在某个机动模式的平均时长。采样周期 1 s、平均 20 s 转一次弯,p_ii就该是 0.95 左右。填 0.99 会让模型切换迟钝,机动开始后好几帧还压在 CV 模型上;填 0.8 会让模型概率乱跳,航迹抖得厉害。
3.3 主循环骨架:真值、量测、关联、更新、指标
cfg = defaultConfig(); rng(cfg.seed, 'twister'); [truth, Z] = simulateScene(cfg); % 真值 X: 4×K×nT,量测 Z: K×1 cell tracks = initTracks(truth, cfg); % 用前两帧起批,避免起始误差污染指标 history = struct('k',{},'x',{},'P',{},'beta',{}); for k = 1:cfg.K % 1) 全体航迹预测(IMM 内部做混合,输出组合预测) for t = 1:numel(tracks) tracks(t) = predictTrack(tracks(t), cfg); end % 2) 关联:JPDA 要跨航迹算,单独分支 switch cfg.assoc case {'NNDA','PDA'} for t = 1:numel(tracks) [idx, d2] = gateMeasurements(Z{k}, tracks(t).zPred, ... tracks(t).S, cfg.gamma); if strcmp(cfg.assoc,'NNDA') tracks(t) = assocNNDA(tracks(t), Z{k}, idx, d2); else tracks(t) = assocPDA(tracks(t), Z{k}, idx, cfg); end end case 'JPDA' tracks = assocJPDA(tracks, Z{k}, cfg); % 内部含簇分解 end % 3) 滤波更新 + 记录中间量 for t = 1:numel(tracks) tracks(t) = updateTrack(tracks(t), cfg); history(end+1) = struct('k',k,'x',tracks(t).x, ... 'P',tracks(t).P,'beta',tracks(t).beta); end % 4) 起批与删除 tracks = manageTracks(tracks, Z{k}, cfg); end metrics = evaluateTracks(history, truth, cfg); fprintf('RMSE=%.2f m 保持率=%.3f OSPA=%.2f\n', ... metrics.rmse, metrics.keepRate, metrics.ospa);这段骨架里,predictTrack做两件事:调用immStep的预测段,算出混合后的xPred、Ppred,同时把zPred = H*xPred和S = H*Ppred*H' + R缓存到结构体里,供关联函数直接用。缓存这一步很关键,PDA和JPDA都要反复用S的逆和行列式,重复算是纯浪费。
gateMeasurements返回的idx是门内量测的行索引,d2是对应的马氏距离平方。NNDA只用idx里d2最小的那个,PDA用全部idx算权重。assocJPDA因为要跨航迹建确认矩阵,签名跟另外两个不一样,返回值是整个tracks数组,这是接口上唯一不对称的地方,改代码时要注意。
history用结构体数组增长,仿真帧数上千时会有性能问题,正式跑之前把它改成预分配或者只存关键帧。我在第一次跑 500 帧的时候没注意,MATLAB 在循环里反复扩容,光记录就吃掉了一半时间,一度以为是JPDA枚举慢。
3.4 单帧对照验证:把 β 和 S 抓出来
算法跑通不等于算对。我的习惯是选一帧有代表性的(通常是航迹交叉那一帧),把中间量存下来,用手算或者一个独立的最小脚本对一遍。
% debugFrame.m —— 复现第 k0 帧的 PDA 权重,与主程序结果比对 k0 = 12; t = 1; xPred = history(find([history.k]==k0 & [history.t]==t, 1)).x;更直接的做法是在assocPDA里加一段条件断点:
if cfg.debug && k == cfg.debugK && t == cfg.debugT fprintf('S = \n'); disp(S); fprintf('e = %s\n', mat2str(e', 6)); fprintf('beta = %s\n', mat2str(beta', 6)); fprintf('Vg = %.3f, b = %.3e\n', Vg, b); end盯三个数就够了。第一看S的对角线,第一个元素应该接近σ_r² + P(1,1)量级,第二个元素在远距离目标上应该随距离平方增长,如果不增长说明转换量测的雅可比没带上,波门形状是错的。第二看β的和,sum(beta) + beta0必须严格等于 1,不相等就是归一化写错了。第三看b和sum(e)的相对大小,b比sum(e)大两三个数量级说明杂波密度给高了,β_0接近 1,滤波器基本不更新,航迹会拖在预测位置上不跟目标。
注意:
β_0长期大于 0.8 是一个很强的告警信号。要么λ给大了,要么P_D给小了,要么波门开太小真值进不来。三种原因的排查顺序是:先看门内量测数(长期为 0 就是波门问题),再看P_D,最后才怀疑λ。
4. 逐层拆解:波门、PDA 权重、JPDA 事件枚举与 IMM 混合
4.1 波门:马氏距离与卡方门限
function [idx, d2] = gateMeasurements(Z, zPred, S, gamma) % Z: m×nz,每行一个量测;zPred: nz×1;S: nz×nz 新息协方差 if isempty(Z), idx = []; d2 = []; return; end D = Z - zPred.'; % m×nz 残差 d2 = sum(D .* (S \ D.').', 2); % 马氏距离平方,逐行 idx = find(d2 <= gamma); endS \ D.'是解线性方程组而不是显式求逆,数值上稳得多,S接近奇异时也不会炸。D .* (...).'再按行求和,等价于对每个量测算ν'*S^-1*ν,避免写循环。
参数上唯一要动的是gamma。二维量测P_G=0.99时 9.21,P_G=0.999时 13.8。加大门限的收益是漏检率下降,代价是门内杂波数线性上升,PDA的计算量和权重稀释同时变差。我的经验是密集杂波场景反而要把P_G降到 0.95 左右(γ≈6.0),用稍微高一点的漏检换干净的权重,航迹保持率通常更好看。
还有一点:S必须用当前帧的预测协方差算,不能用上一帧的后验协方差。机动开始的那一帧,P(k|k-1)会因为过程噪声突然变大,S跟着变大,波门自动张开把机动中的目标兜住。如果用旧的S,机动第一帧就丢点,后面全靠预测硬撑,很容易断批。
4.2 PDA 的 β 计算与协方差补偿项
function [xUpd, PUpd, beta, beta0] = pdaUpdate(xPred, PPred, Z, H, R, cfg) m = size(Z,1); nz = size(H,1); S = H*PPred*H' + R; Sinv = inv(S); % 1) 各量测的似然(高斯密度,含归一化常数) e = zeros(m,1); for i = 1:m nu = Z(i,:).' - H*xPred; e(i) = exp(-0.5*nu.'*Sinv*nu) / ((2*pi)^(nz/2)*sqrt(det(S))); end % 2) 波门体积与无量测事件密度 cn = pi^(nz/2) / gamma(nz/2+1); % 二维时 cn = pi Vg = cn * cfg.gamma^(nz/2) * sqrt(det(S)); b = cfg.lambda * Vg * (1 - cfg.PD*cfg.PG) / cfg.PD; den = b + sum(e); beta0 = b / den; beta = e / den; % 3) 组合新息与状态更新 nuC = zeros(nz,1); for i = 1:m nuC = nuC + beta(i) * (Z(i,:).' - H*xPred); end K = PPred*H'*Sinv; xUpd = xPred + K*nuC; % 4) 协方差:常规项 + 新息散布补偿 Pc = (eye(size(PPred)) - K*H) * PPred; Scatter = zeros(nz); for i = 1:m nu = Z(i,:).' - H*xPred; Scatter = Scatter + beta(i) * (nu*nu.'); end Scatter = Scatter - nuC*nuC.'; PUpd = beta0*PPred + (1-beta0)*Pc + K*Scatter*K';注意b里乘了Vg。这里用的是面密度λ,所以要把「单位面积杂波数」换算成「这个波门里期望有多少杂波」,λ*Vg才是无量纲的波门内杂波数。如果λ本身就是按波门统计的,这一项必须去掉——这就是 2.2 里提到的那个坑,代码里两种写法只差一个Vg,结果差出几个数量级。
gamma(nz/2+1)是 Gamma 函数,不是 MATLAB 的gammainc。nz=2时cn = pi,nz=4时cn = pi^2/2,这两个值可以直接记。三维以上量测(比如带多普勒)时cn会变小,波门体积增长慢于γ^(nz/2),别想当然按二维外推。
Scatter那一项看着像噪声,实际上它决定了PDA在杂波密集时会不会过度自信。门内 5 个点、权重比较平均时,Scatter会明显非零,协方差不会收缩;门内只有 1 个点、β≈1时,Scatter退化为 0,PUpd回到标准卡尔曼。这个连续性正是PDA比NNDA稳的原因。
Pc用的是标准形式(I-KH)P,在数值条件好的时候没问题。如果R很小、P接近奇异,建议换成 Joseph 形式(I-KH)P(I-KH)' + KRK',代价是多一次矩阵乘法,换来的是协方差永远正定。
4.3 JPDA:确认矩阵、联合事件与簇分解
先建确认矩阵,再枚举可行联合事件,最后边缘化。
function events = enumEvents(Omega) % Omega: nT×m 确认矩阵(不含虚警列),返回 cell,每个元素是 1×nT 分配向量 events = recEnum(1, zeros(1,size(Omega,1)), {}, Omega); end function out = recEnum(t, cur, out, Omega) [nT, m] = size(Omega); if t > nT out{end+1} = cur; return; end out = recEnum(t+1, cur, out, Omega); % 航迹 t 本帧无量测 for j = 1:m if Omega(t,j) == 1 && ~any(cur == j) % 量测 j 尚未被占用 cur2 = cur; cur2(t) = j; out = recEnum(t+1, cur2, out, Omega); end end end递归的剪枝条件就一条:~any(cur == j)。检测这个条件的同时也保证了「一个量测最多给一条航迹」。t > nT是终止条件,此时cur就是一个完整的联合事件,cur(t)=0表示航迹t这一帧没有关联量测。
事件数量在nT和m都小的时候完全可控,一旦两边都到 6 以上就会爆。工程上的处理是先做簇分解:
function clusters = splitClusters(Omega) % 把通过共享量测连通的航迹分到同一簇,簇之间独立枚举 nT = size(Omega,1); adj = double(Omega * Omega.' > 0); % 共享至少一个量测即连通 adj = adj | adj.' | eye(nT); clusters = {}; seen = false(1,nT); for i = 1:nT if seen(i), continue; end comp = i; frontier = i; seen(i) = true; while ~isempty(frontier) nb = find(any(adj(frontier,:),1)) ; nb = nb(~seen(nb)); seen(nb) = true; comp = [comp nb]; frontier = nb; %#ok<AGROW> if isempty(frontier), break; end end clusters{end+1} = comp; end end分完簇以后,每个簇的航迹数通常不超过 3,事件数立刻回到几十的量级,实时性就回来了。交叉场景里最坏情况是整个簇有 4 条航迹,此时事件数在几百,MATLAB 单帧也能跑进毫秒级。
联合概率必须在对数域算,否则量测一多,exp全部下溢成 0:
logP = zeros(numel(events),1); for e = 1:numel(events) as = events{e}; lp = 0; for t = 1:nT if as(t) > 0 nu = Z(as(t),:).' - zPred(:,t); lp = lp + log(cfg.PD) - 0.5*nu.'*(Sinv(:,:,t)*nu) ... - 0.5*log(det(2*pi*S(:,:,t))); else lp = lp + log(1 - cfg.PD*cfg.PG); end end lp = lp + (m - nnz(as)) * log(cfg.lambda * Vg0); % 未关联量测记为杂波 logP(e) = lp; end logP = logP - max(logP); p = exp(logP) / sum(exp(logP));边缘化就是按事件累加:
beta = zeros(nT, m+1); % 最后一列是 beta0 for e = 1:numel(events) as = events{e}; for t = 1:nT beta(t, as(t)+1) = beta(t, as(t)+1) + p(e); end endbeta(t,1)是航迹t的无量测概率,后面依次对应各量测。拿到beta(t,2:end)之后,每条航迹直接调一次pdaUpdate里的更新段就能出结果——JPDA和PDA的唯一差别就在权重怎么来,更新公式是同一份。这也是这套代码把两者拆成「权重计算」和「状态更新」两层的原因,改JPDA的枚举不影响更新逻辑。
枚举剪枝的阈值我一般设1e-4,联合概率低于这个值的事件直接不参与边缘化,误差远小于量测噪声,换来的是事件数能砍掉一大半。别设成1e-2,那样会把权重很小的正确关联也丢掉,β_0被系统性高估。
4.4 IMM:混合、似然与组合的三个易错点
function [xOut, POut, muOut, mdl] = immStep(mdl, mu, PI, z, H, R) n = numel(mdl); cbar = PI.' * mu; % 预测模型概率 cbar_j = sum_i p_ij*mu_i mix = (PI .* mu.') ./ cbar.'; % mix(i,j) = p_ij*mu_i / cbar_j % 1) 混合:先算混合均值,再算混合协方差 for j = 1:n xm = zeros(size(mdl{j}.x)); for i = 1:n, xm = xm + mix(i,j)*mdl{i}.x; end Pm = zeros(size(mdl{j}.P)); for i = 1:n d = mdl{i}.x - xm; Pm = Pm + mix(i,j)*(mdl{i}.P + d*d.'); end mdl{j}.x = xm; mdl{j}.P = Pm; end % 2) 逐模型预测 + 更新 + 似然 logL = zeros(n,1); for j = 1:n [mdl{j}.x, mdl{j}.P, nu, S] = kfUpdate(mdl{j}.x, mdl{j}.P, z, ... H, R, mdl{j}.F, mdl{j}.Q); logL(j <p> <a href="https://download.csdn.net/download/zmh20062157/9734607" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>