简介这份资源是MHT多假设跟踪算法在Matlab环境下的实现程序面向具备Matlab编程基础、正在研究多目标跟踪的读者可用于雷达、视频监控等存在目标诞生、消失、分裂与合并等不确定场景的跟踪问题。压缩包共33个文件约45KB以m脚本文件为主辅以c源码、mexw32与mexglx等编译文件、asv备份、mat数据及txt说明覆盖预测、更新、关联、剪枝等核心模块。目前已有2037人学习下载。代码围绕概率数据关联框架展开包含卡尔曼滤波预测、匈牙利与Murty分配、假设生成与剪枝等实现读者可据此理解MHT从初始化、数据关联到分支合并、后处理的完整流程并对照运动模型与门限参数进行调试将其迁移到自己的多目标跟踪任务中。1. 从一次深夜调试说起MHT 算法的 Matlab 程序到底能干什么如果你做过多目标跟踪大概率经历过这种场景雷达或摄像头里同时出现三四个机动目标航迹交叉、短暂丢失、虚警混入卡尔曼滤波单路跟踪直接跟丢航迹管理逻辑越写越乱。这时候多假设跟踪MHTMultiple Hypothesis Tracking就是绕不开的方案。它不急着在每一帧做硬判决而是把多种关联可能性都保留下来延迟几帧再剪枝用后续观测来验证前面的假设。代价是计算量和内存占用上去了但换来的是复杂场景下航迹连续性明显更稳。这份 MHT 算法的 Matlab 程序核心就是把假设树生成、假设评分、剪枝合并、航迹管理这一整套流程用 Matlab 脚本和函数实现出来配套有仿真数据生成和结果可视化。它适合两类人一类是刚接触多目标跟踪、想跑通一个完整 MHT 闭环的学生或转行者另一类是做工程验证、需要一个可改参数、可换量测模型的算法骨架的从业者。Matlab 的好处是矩阵运算写起来短调参和画图快适合把算法逻辑先验证清楚再考虑往 C 或嵌入式平台迁移。2. MHT 的核心机制假设树、评分与剪枝在 Matlab 里怎么落地2.1 假设树的数据结构选择MHT 的本质是在时间轴上维护一棵假设树。每个节点代表一个时刻的关联决策从根到叶的一条路径就是一个全局假设。Matlab 里没有指针常见做法是用结构体数组加索引来模拟树。每个节点存父节点编号、当前帧的关联结果、累计对数似然得分、以及该节点对应的航迹状态。% 假设树节点结构定义 % 字段说明 % parent : 父节点索引根节点为 0 % frame : 该节点对应的帧号 % assign : 本帧的关联向量assign(k)j 表示第 k 个量测关联到第 j 条航迹0 表示虚警 % score : 从根到该节点的累计对数似然 % tracks : 该假设下所有航迹的状态结构体数组 node struct(parent, 0, frame, 0, assign, [], score, 0, tracks, []); tree node; % tree(1) 为根节点用数组模拟树的好处是内存连续、遍历方便缺点是删除节点后索引会空洞。我一般会在剪枝后做一次紧凑化把保留的节点重新编号避免索引越界。这里要注意 assign 向量的长度等于当前帧量测数而不是航迹数因为一个量测只能属于一条航迹或虚警但一条航迹可能没有量测更新。2.2 假设评分对数似然比怎么算评分是 MHT 的命门。每个关联决策都要给出一个对数似然增量通常由三部分组成量测与预测位置的马氏距离项、检测概率与虚警概率的比值项、以及航迹起始和终结的惩罚项。Matlab 里用马氏距离很方便因为mahal函数或者手动算d * inv(S) * d都行。% 计算单个量测与某条航迹预测的关联得分 % z: 量测向量 (m x 1) % z_pred: 预测量测 (m x 1) % S: 新息协方差 (m x m) % Pd: 检测概率, lambda: 虚警密度 function score association_score(z, z_pred, S, Pd, lambda) nu z - z_pred; % 新息 d2 nu * (S \ nu); % 马氏距离平方 score log(Pd) - 0.5*d2 - log(lambda); % 若关联为虚警则得分单独计算为 log(1-Pd) end参数说明Pd 一般取 0.9 左右lambda 是单位体积虚警数根据传感器特性设。马氏距离平方超过卡方门限的关联直接丢弃不进入假设树这一步能大幅减少分支。常见做法是门限取 9.494 自由度0.05 显著性但实际要根据量测维数查卡方表。2.3 剪枝与合并K-best 和 N-scan 的取舍假设树不剪枝会指数爆炸。Matlab 程序里通常实现两种剪枝一是 N-scan 回溯剪枝保留最近 N 帧的模糊决策更早的节点只保留得分最高的分支二是 K-best 剪枝每层只保留得分前 K 个节点。两者可以叠加。% N-scan 剪枝对 N 帧前的节点只保留其子节点中得分最高的一支 function tree n_scan_prune(tree, current_frame, N) cutoff current_frame - N; for i 1:length(tree) if tree(i).frame cutoff children find([tree.parent] i); if length(children) 1 [~, best] max([tree(children).score]); remove children; remove(best) []; tree(remove) []; % 简化写法实际需处理索引重排 end end end end这段代码是示意真实实现里删除节点后要递归删除其子树并更新父节点索引。我踩过的坑是直接tree(remove)[]会导致后续find拿到错误索引正确做法是先标记待删除再统一重建数组。K-best 剪枝更简单每帧结束后按得分排序保留前 K 个叶节点对应的路径即可但 K 取太小会丢正确假设取太大又失去剪枝意义一般 K 在 5 到 20 之间试。2.4 航迹管理起始、确认与终结MHT 的航迹管理比单路跟踪复杂因为每条航迹在不同假设下状态可能不同。Matlab 程序里通常把航迹状态存在节点内每条航迹有生命周期标志自由、确认、终结。起始逻辑用连续 M/N 帧检测到目标来确认终结用连续 L 帧无关联来判定。% 航迹确认逻辑连续 3 帧有量测关联则确认 if track.hit_count 3 strcmp(track.status, tentative) track.status confirmed; end % 航迹终结连续 5 帧无关联 if track.miss_count 5 track.status deleted; end这里要注意不同假设下同一条航迹的 hit_count 可能不同所以这些计数必须存在节点内不能全局共享。否则剪枝合并后航迹状态就乱了。常见做法是每个节点存一份航迹结构体数组的副本内存换正确性。3. 跑通仿真从生成数据到出航迹图的操作流程3.1 仿真场景配置与量测生成先造一个可复现的仿真场景。我一般设 3 个目标做匀速加一次转弯的机动采样 50 帧量测噪声协方差根据传感器精度设。虚警按泊松分布撒点检测概率设 0.9。% 仿真参数 T 1; % 采样周期 N_frame 50; % 帧数 N_target 3; % 目标数 sigma_r 10; % 距离噪声标准差 Pd 0.9; % 检测概率 lambda 1e-4; % 虚警密度 % 生成真实航迹匀速转弯 truth cell(1, N_target); for t 1:N_target x [100*t; 0; 50*t; 10]; % 初始状态 [x vx y vy] truth{t} zeros(4, N_frame); for k 1:N_frame if k 25 % 第 25 帧后转弯 x(3:4) [0.9 0; 0 0.9] * x(3:4); end truth{t}(:,k) x; x(1:2) x(1:2) x(3:4)*T; end end参数怎么改sigma_r 调大关联门限要跟着放宽否则真实量测被门限滤掉lambda 调大虚警增多假设树分支变多需要加大剪枝力度。转弯那一段是检验 MHT 延迟判决能力的关键单路卡尔曼在这里容易跟丢MHT 因为保留多假设通常能在转弯后几帧纠正回来。3.2 主循环预测、关联、假设扩展、剪枝主循环是程序骨架。每一帧先对所有航迹做预测然后用量测做关联生成新假设节点再评分、剪枝、合并。for k 1:N_frame % 1. 预测对当前所有叶节点中的航迹做卡尔曼预测 leaf_idx find_leaf_nodes(tree); for i leaf_idx tree(i).tracks predict_tracks(tree(i).tracks, T); end % 2. 关联对每个叶节点用量测生成关联假设 new_nodes []; for i leaf_idx Z get_measurements(k); % 当前帧量测 assoc_list generate_associations(tree(i).tracks, Z, gate_threshold); for a 1:length(assoc_list) node make_node(tree, i, k, assoc_list{a}); new_nodes [new_nodes, node]; end end tree [tree, new_nodes]; % 3. 剪枝 tree n_scan_prune(tree, k, 3); tree k_best_prune(tree, 10); end逻辑说明find_leaf_nodes找当前没有子节点的节点只有叶节点才需要扩展。generate_associations是核心它要枚举所有满足门限的关联组合组合数随量测数指数增长所以门限和最大分支数要设上限。我一般限制每个叶节点最多扩展 50 个分支超了就按得分取前 50。剪枝放在扩展之后先 N-scan 再 K-best顺序不能反否则 K-best 会把该被 N-scan 砍掉的分支留下来。3.3 结果可视化与航迹回溯跑完后要把确认航迹画出来和真实航迹对比。MHT 的输出不是单一航迹而是得分最高的全局假设对应的航迹集合。% 找得分最高的叶节点 leaf_idx find_leaf_nodes(tree); [~, best] max([tree(leaf_idx).score]); best_node leaf_idx(best); % 回溯该假设下的航迹 tracks tree(best_node).tracks; figure; hold on; for t 1:length(tracks) if strcmp(tracks(t).status, confirmed) plot(tracks(t).history(1,:), tracks(t).history(3,:), -o); end end % 叠加真实航迹 for t 1:N_target plot(truth{t}(1,:), truth{t}(3,:), k--); end legend(估计航迹, 真实航迹);画图时重点看三个地方转弯段航迹有没有断、虚警有没有形成假航迹、两条航迹交叉时有没有互换标签。交叉互换是 MHT 的经典难点如果发现互换说明评分函数里运动模型权重不够或者 N-scan 窗口太短延迟判决没起作用。4. 避坑与排查MHT 在 Matlab 里最容易翻车的五个地方4.1 假设树索引错乱导致航迹凭空消失现象剪枝后某些确认航迹突然不见了或者画图时报索引越界。原因Matlab 里用数组存树删除节点后没有重建索引父节点指向了已删除的位置。解决剪枝函数里先标记待删除节点收集所有要保留的节点重新分配索引并更新 parent 字段。我习惯写一个compact_tree函数每次剪枝后强制走一遍。4.2 关联组合爆炸导致内存溢出现象跑十几帧后 Matlab 卡死内存飙升。原因generate_associations枚举了所有可能关联量测数一多组合数爆炸。解决设两个上限一是单帧最大量测数参与关联比如超过 10 个就只取离预测最近的 10 个二是单叶节点最大分支数超了按得分截断。另外门限别设太宽马氏距离门限 9.49 是常用值别随手改成 20。4.3 评分函数量纲不一致导致剪枝剪错现象正确航迹被剪掉假航迹反而保留。原因对数似然里各项量纲没统一比如马氏距离项和虚警项数量级差太多得分被某一项主导。解决检查 Pd 和 lambda 的取值是否在合理范围马氏距离平方通常是个位数到几十log(Pd) 约 -0.1log(lambda) 若 lambda1e-4 则约 -9.2三项要在一个量级附近。如果差太多给各项加权重系数权重用仿真调。4.4 航迹状态在假设间共享导致计数污染现象某条航迹的 hit_count 异常大确认逻辑误判。原因多个假设节点引用了同一个航迹结构体Matlab 句柄类或全局变量一个分支的更新影响了其他分支。解决航迹状态必须值拷贝每个节点存独立副本。Matlab 结构体默认是值传递但如果你用了 handle class 就要小心。我一般用 struct 数组不用 handle。4.5 N-scan 窗口取太小导致机动目标跟丢现象目标转弯时航迹中断几帧后才重新起始。原因N-scan 的 N 太小模糊决策还没等到后续观测验证就被剪掉了。解决N 至少取 3机动强的话取 5。但 N 大了内存和计算量上去需要和 K-best 配合。我一般先设 N3、K10 跑一遍看转弯段表现不行再加大 N。5. 进阶技巧用门限自适应和得分归一化把 MHT 调稳跑通基础版之后真正决定 MHT 好不好用的是两个细节关联门限怎么随场景变以及得分怎么跨帧比较。固定门限在目标密集时要么漏关联要么引入太多虚警我一般用自适应门限根据当前帧量测密度调整卡方门限量测多就收紧量测少就放宽。% 自适应门限基于当前帧量测数调整 function gate adaptive_gate(base_gate, n_meas, n_tracks) density n_meas / max(n_tracks, 1); if density 3 gate base_gate * 0.8; % 量测密集收紧 elseif density 1 gate base_gate * 1.2; % 量测稀疏放宽 else gate base_gate; end end得分归一化是另一个关键。不同帧的量测数不同累计对数似然会随帧数增长直接比较不同深度的节点不公平。常见做法是每帧结束后对所有叶节点得分做一次减均值或者用每帧平均得分来排序。我习惯在 K-best 剪枝前做减最大值归一化这样数值不会溢出排序也稳定。验证 MHT 是否调好我一般看三个指标航迹确认延迟真实目标出现到确认的帧数、航迹中断次数、标签互换次数。前两个从日志里数第三个靠画图肉眼查。如果确认延迟超过 5 帧检查起始逻辑的 M/N 是不是太严如果中断多检查 N-scan 和门限如果标签互换多检查运动模型和评分权重。还有一个实用技巧把每次剪枝前后的叶节点数和最高得分打出来画成曲线。如果叶节点数一直贴着上限跑说明剪枝力度不够或者门限太宽如果最高得分频繁跳变说明评分函数不稳定。这个日志我每次调参都开着比看最终航迹图更能定位问题。从那以后我每次改 MHT 参数都强制先跑一遍剪枝日志和门限自适应开关的对比确认叶节点数和得分曲线正常了再看航迹图。希望帮到你。本文还有配套的精品资源点击获取