
简介这份资源面向计算机视觉、信号处理方向的学习者与研究人员聚焦在MATLAB环境下实现多目标跟踪MTT的完整流程。内容围绕目标检测、状态估计、数据关联等核心环节展开涵盖卡尔曼滤波、扩展与无迹卡尔曼滤波、粒子滤波、匈牙利算法、联合检测与数据关联等经典方法并延伸至DeepSORT、FairMOT等深度学习跟踪思路以及MOTA、MOTP、IDF1等评估指标帮助读者建立从理论到代码的认知链路。压缩包共3个文件包含1个m脚本、1个avi视频和1个txt说明整体约584KB脚本可直接运行演示跟踪流程视频提供直观的测试素材说明文件辅助理解算法背景。目前已有434人学习下载适合希望快速上手MATLAB多目标跟踪原型开发、对照算法实现与效果验证的读者参考。1. 多目标跟踪 MATLAB从“能跑起来”到“敢用在项目里”的分水岭很多人第一次接触多目标跟踪 MATLAB都是被一段演示视频骗进来的几辆车在路口跑框跟着走ID 稳稳当当看起来比 Python 那套还省事。真到自己上手把一段监控丢进去十分钟不到就翻车——ID 频繁跳变、遮挡后目标直接消失、两个人交叉走过之后身份互换。问题不在 MATLAB而在于多数教程只教了“调函数”没讲清楚跟踪链路里每一环的输入输出契约。这篇笔记面向两类人一类是刚拿到一段视频、想用 MATLAB 快速搭出可复现跟踪结果的工程师另一类是做算法验证、需要一套不依赖复杂环境就能跑通 baseline 的研究者。我会按“检测从哪来 → 跟踪器怎么选 → 参数怎么调 → 坑在哪 → 怎么验证”的顺序把一条能落地的链路讲透。核心结论先放这MATLAB 做多目标跟踪难点从来不是tracker本身而是检测质量、代价矩阵和生命周期管理这三件事。2. 检测先行多目标跟踪 MATLAB 的输入到底从哪来2.1 为什么跟踪效果差八成是检测的锅多目标跟踪MOT本质上是“检测 关联”两段式。跟踪器不产生目标它只负责把当前帧的检测框和历史轨迹对上号。所以当你发现 ID 乱跳第一反应不该是去调跟踪器参数而是回头看检测框的质量。常见做法有两种一是用 MATLAB 自带或第三方检测器如 YOLO 系列导出的 ONNX 模型逐帧推理得到[x y w h score]二是直接读公开数据集的检测结果文件如 MOTChallenge 格式的det.txt。前者灵活但慢后者快但受限于数据集。我一般会先把检测结果存成统一格式再做跟踪这样检测和跟踪解耦调参时不用反复跑推理。检测框质量看三个指标召回率漏检多不多、定位精度框抖不抖、置信度分布阈值卡在哪。漏检会让轨迹断框抖会让代价矩阵失真置信度阈值设太高会丢弱目标设太低会引入大量误检。这三件事没处理好后面跟踪器再高级也白搭。2.2 用 MATLAB 跑通检测到跟踪的最小链路下面这段代码演示从视频读入、检测这里用预训练模型占位实际替换成你的检测器、到多目标跟踪器初始化的完整流程。关键是把检测结果整理成objectDetection对象数组这是 MATLAB 跟踪器要求的输入格式。% 读取视频并初始化检测器与跟踪器 videoReader VideoReader(test_video.mp4); detector yolov4ObjectDetector(csp-darknet53-coco); % 替换为你的检测器 tracker multiObjectTracker(FilterInitializationFcn, initcvkf, ... AssignmentThreshold, 30, ... ConfirmationThreshold, [2 3], ... DeletionThreshold, [5 5]); while hasFrame(videoReader) frame readFrame(videoReader); % 检测返回 [bboxes, scores, labels] [bboxes, scores, labels] detect(detector, frame, Threshold, 0.5); % 只保留目标类别如 person / car过滤无关类别 keep ismember(labels, {person,car}); bboxes bboxes(keep,:); scores scores(keep); % 构造 objectDetection 数组 detections objectDetection.empty(0,1); for i 1:size(bboxes,1) detections(i) objectDetection(frameCount, bboxes(i,:), ... Measurement, bboxes(i,:), ObjectClassID, 1, ... MeasurementNoise, eye(4)*10); end % 跟踪一步 tracks tracker(detections, frameCount); frameCount frameCount 1; end逻辑说明multiObjectTracker是 MATLAB 里最常用的多目标跟踪入口内部用卡尔曼滤波做状态估计、用匈牙利算法做关联。FilterInitializationFcn指定滤波初始化函数initcvkf是匀速模型适合行人、车辆这类运动目标。AssignmentThreshold控制关联代价门限值越小越严格容易断轨值越大越宽松容易串 ID。ConfirmationThreshold和DeletionThreshold是轨迹生命周期参数前者决定“连续几帧命中才确认”后者决定“连续几帧丢失才删除”。参数说明MeasurementNoise设的是检测框噪声协方差框越抖这个值该越大否则滤波器会过度信任检测。ObjectClassID用于区分不同类别如果你只跟踪一类目标统一设 1 即可。frameCount必须从 1 开始递增跟踪器靠它做时间戳对齐。2.3 检测结果格式转换的四个边界坑如果你用的是公开数据集检测文件格式转换是最容易出错的一步。MOTChallenge 的det.txt是frame, id, x, y, w, h, conf, -1, -1, -1其中id在检测阶段恒为 -1。转成 MATLAB 的objectDetection时要注意坐标是左上角 宽高不是中心点置信度列要单独取帧号必须连续跳帧会导致跟踪器时间戳错乱。我一般会写一个独立函数做转换并在转换后做一次校验统计每帧检测数量、检查是否有负宽高、检查帧号是否单调递增。这三个检查能拦掉八成低级错误。3. 跟踪器选型multiObjectTracker、trackerGridRFS 还是自己写关联3.1 三种主流方案的能力边界MATLAB 里做多目标跟踪常见三条路multiObjectTracker基于卡尔曼滤波 匈牙利关联、trackerGridRFS基于随机有限集的网格跟踪、以及自己写代价矩阵 assignDetectionsToTracks。三者不是替代关系而是适用场景不同。multiObjectTracker适合目标数量中等、运动模型近似匀速、检测框质量尚可的场景比如路口车辆、室内行人。它的优势是参数直观、调试方便、文档全。缺点是遮挡后恢复能力弱因为卡尔曼滤波只维护单峰分布目标丢失后只能靠预测硬撑。trackerGridRFS适合目标密集、检测噪声大、需要处理目标出生死亡的场景比如人群计数、细胞跟踪。它用概率假设密度做状态估计天然支持变数量目标但计算量大参数不直观调参靠经验。自己写关联适合研究性质的工作比如你要换代价函数、加外观特征、做级联匹配。MATLAB 提供了assignDetectionsToTracks这个底层函数输入代价矩阵和门限输出分配结果剩下的逻辑自己控制。3.2 用 assignDetectionsToTracks 手写一版可调关联下面这段代码展示如何自己构造代价矩阵并做关联。核心是用 IoU 或中心点距离做代价再用assignDetectionsToTracks求解。这样你可以自由替换代价函数比如加入外观余弦距离。% 假设 tracks 是上一帧轨迹结构体数组detections 是当前帧检测 numTracks numel(tracks); numDets size(detections,1); costMatrix zeros(numTracks, numDets); for i 1:numTracks for j 1:numDets % 用中心点欧氏距离做代价也可换成 1-IoU predCenter tracks(i).predictedCenter; detCenter [detections(j,1)detections(j,3)/2, ... detections(j,2)detections(j,4)/2]; costMatrix(i,j) norm(predCenter - detCenter); end end % 设置门限超过门限的分配视为无效 costOfNonAssignment 50; [assignments, unassignedTracks, unassignedDets] ... assignDetectionsToTracks(costMatrix, costOfNonAssignment); % assignments 每行是 [trackIdx, detIdx] for k 1:size(assignments,1) tracks(assignments(k,1)).update(detections(assignments(k,2),:)); end逻辑说明costMatrix的行是轨迹、列是检测值越小表示越可能匹配。costOfNonAssignment是关键参数它相当于“不匹配的代价”值越大越倾向于强行匹配值越小越倾向于放弃匹配。实际调参时这个值应该和你的代价量纲匹配比如用像素距离做代价门限就设在目标可能位移的上限附近。参数说明assignDetectionsToTracks内部用匈牙利算法求全局最优分配返回的unassignedTracks和unassignedDets分别用于轨迹预测更新和新轨迹初始化。注意这个函数不处理轨迹生命周期确认、删除、遮挡计数都要自己写。3.3 外观特征怎么加才不拖垮速度纯运动关联在目标交叉时容易串 ID加外观特征是常见解法。MATLAB 的multiObjectTracker不直接支持外观但你可以用trackerTOMHT或自己写关联时加入。外观特征一般用轻量 CNN 提一个 128 维向量算余弦距离和运动代价加权求和。代价融合公式cost alpha * motionCost (1-alpha) * appearanceCost。alpha一般取 0.6~0.8运动为主、外观为辅。外观特征提取是瓶颈我一般会隔帧提或只在轨迹模糊时提避免每帧都跑 CNN。实测在 1080p 视频上隔帧提外观能把速度从 5 FPS 拉到 12 FPS 左右。4. 参数调优让 ID 不跳变的那几个关键旋钮4.1 卡尔曼滤波噪声参数怎么设multiObjectTracker默认用匀速模型状态是[x vx y vy w vw h vh]。过程噪声ProcessNoise和测量噪声MeasurementNoise是两个核心旋钮。过程噪声大滤波器更信任检测过程噪声小滤波器更信任预测。经验值行人场景过程噪声设diag([1 10 1 10 1 10 1 10])量级车辆场景速度变化慢过程噪声可以再小一个量级。测量噪声和检测框抖动正相关如果检测框在 2~3 像素内抖测量噪声设diag([4 4 4 4])左右。这两个参数没有理论最优只能按场景试。提示调这两个参数时先固定一个改另一个观察 ID switch 次数和轨迹断裂次数。两个一起改你分不清是谁的功劳。4.2 确认与删除阈值轨迹生命周期的两个开关ConfirmationThreshold是[M N]表示最近 N 帧里有 M 帧命中才确认轨迹。默认[2 3]偏保守适合检测稳定的场景。如果检测漏检多可以放宽到[2 5]或[3 5]让轨迹更容易活下来。DeletionThreshold是[P Q]表示最近 Q 帧里有 P 帧没命中就删除。默认[5 5]意味着连续 5 帧丢失才删。遮挡频繁的场景这个值要加大比如[8 8]给轨迹更多恢复时间。但加太大也有代价误检产生的假轨迹会活很久污染结果。4.3 关联门限与代价矩阵的量纲匹配AssignmentThreshold是关联代价门限它的量纲取决于你的代价函数。如果用multiObjectTracker默认的代价位置 速度 尺寸加权门限一般在 20~50 之间。如果自己写 IoU 代价门限要改成 0.2~0.5 这种量级。常见错误是直接抄别人的门限值但代价函数不一样门限根本不可比。我一般会先跑一段把每帧的代价矩阵最小值打印出来看分布再定门限。门限应该设在“正确匹配代价”和“错误匹配代价”之间的谷底。5. 避坑与排查多目标跟踪 MATLAB 最常见的五类翻车5.1 ID 频繁跳变但检测框看起来没问题现象检测框稳定目标运动也平滑但 ID 每隔几帧就换一次。原因多数是AssignmentThreshold设得太小导致正确匹配被拒绝轨迹断掉后重新初始化。也可能是MeasurementNoise设得太大滤波器过度信任检测预测位置和检测位置偏差大代价矩阵失真。解决先把AssignmentThreshold调大 20% 试观察 ID switch 是否下降。如果没改善检查MeasurementNoise是否和检测抖动匹配。可以用tracker的Assignment输出看每帧匹配代价定位是匹配失败还是匹配错误。5.2 遮挡后目标消失再出现时变成新 ID现象目标被遮挡几帧后重新出现跟踪器没有接上而是新建了轨迹。原因DeletionThreshold太小轨迹在遮挡期间被删了。或者遮挡期间预测位置漂移太远重新出现时代价超过门限。解决加大DeletionThreshold比如从[5 5]改成[10 10]。同时检查过程噪声是否太大导致预测漂移。如果遮挡时间长可以考虑加外观特征做重识别但那是另一个量级的工程。5.3 检测框坐标格式搞错跟踪器输出全乱现象跟踪框位置完全不对或者轨迹数量异常多。原因把[x y w h]当成[x1 y1 x2 y2]传进去了。MATLAB 的objectDetection要求[x y w h]左上角 宽高。如果传的是角点格式框会越算越大或越小。解决在构造objectDetection之前加一行断言检查宽高是否为正。如果是角点格式先转成[x y x2-x y2-y]。这个坑我踩过不止一次血泪经验是格式转换函数一定要写单元测试。5.4 帧号不连续导致时间戳错乱现象跟踪器报错或行为异常轨迹预测完全离谱。原因objectDetection的时间戳参数必须单调递增。如果你跳帧处理或从中间帧开始时间戳不连续卡尔曼滤波的dt计算就错了。解决确保时间戳从 1 开始连续递增。如果跳帧时间戳要按实际帧号走不能按处理序号走。multiObjectTracker内部用时间戳算dt这个值直接影响预测。5.5 速度慢到无法接受但不知道瓶颈在哪现象跑一段 1 分钟视频要十几分钟完全没法调参。原因检测推理占大头跟踪本身其实很快。或者外观特征每帧都提CNN 拖垮速度。解决先用tic/toc分段计时确认是检测慢还是跟踪慢。检测慢就换轻量模型或降分辨率跟踪慢就检查是不是每帧都在做外观提取。我一般会把检测结果缓存成 mat 文件调跟踪参数时直接读缓存速度能快一个数量级。6. 验证与进阶怎么判断你的跟踪结果真的能用6.1 用 MOT 指标做定量评估光看视频不够得有数字。MOT 常用指标有 MOTA、IDF1、ID Switch。MOTA 综合了漏检、误检、ID switch但会被检测质量主导。IDF1 更关注 ID 保持能力适合评估关联算法。ID Switch 直接数 ID 跳变次数最直观。MATLAB 没有内置 MOT 评估但可以自己写。核心是先把跟踪结果转成 MOTChallenge 格式frame, id, x, y, w, h, conf, -1, -1, -1。然后和真值文件做匹配匹配用 IoU 0.5。匹配上之后统计 TP、FP、FN、IDSW。% 简化版 ID Switch 统计 % tracksHistory: 每帧的 [id, x, y, w, h] % gtHistory: 每帧的真值 [gtId, x, y, w, h] idSwitchCount 0; lastMatch containers.Map(KeyType,double,ValueType,double); for f 1:numFrames trackBoxes tracksHistory{f}; gtBoxes gtHistory{f}; for g 1:size(gtBoxes,1) iouMax 0; bestTrackId -1; for t 1:size(trackBoxes,1) iou computeIoU(gtBoxes(g,2:5), trackBoxes(t,2:5)); if iou iouMax iouMax iou; bestTrackId trackBoxes(t,1); end end if iouMax 0.5 gtId gtBoxes(g,1); if isKey(lastMatch, gtId) lastMatch(gtId) ~ bestTrackId idSwitchCount idSwitchCount 1; end lastMatch(gtId) bestTrackId; end end end逻辑说明对每个真值目标找当前帧 IoU 最大的跟踪框如果 IoU 超过 0.5 就算匹配。如果同一个真值目标上一帧匹配的 ID 和这一帧不同ID Switch 加一。这个统计简单但有效能快速判断关联稳定性。6.2 可视化调试把代价矩阵画出来调参时最有用的一招是把代价矩阵热力图打出来。正确匹配应该在对角线附近形成低代价带如果矩阵一片混乱说明预测或检测有问题。MATLAB 的imagesc可以直接画。我一般会在跟踪循环里加一个开关每隔 50 帧画一次代价矩阵和当前帧的框。这样能直观看到是预测漂了还是检测抖了。这个习惯帮我省了大量盲调参数的时间。6.3 从跟踪到业务什么时候该换方案如果你的场景是固定摄像头、目标匀速、检测稳定multiObjectTracker足够。如果目标频繁交叉、遮挡严重、需要长时跟踪考虑加外观特征或换trackerTOMHT。如果目标数量动态变化大、检测噪声高trackerGridRFS更合适。但换方案之前先确认检测和参数已经调到极限。我见过太多人一上来就换跟踪器结果发现是检测框格式错了。先把简单方案跑通、跑稳再考虑复杂方案这个顺序不能反。最后说个习惯每次调参前先把当前参数和对应的 MOTA、ID Switch 记在表格里。调完对比别靠记忆。多目标跟踪的参数空间不大但组合多没有记录就是玄学调参。希望帮到你。本文还有配套的精品资源点击获取