ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MATLAB目标跟踪算法实战:从卡尔曼滤波到YOLO多目标跟踪

MATLAB目标跟踪算法实战:从卡尔曼滤波到YOLO多目标跟踪 简介压缩包中集成了目标跟踪核心算法的MATLAB实现包括卡尔曼滤波与交互式多模型IMM机动目标跟踪面向计算机视觉、信号处理方向的学生与开发者可用于安全监控、自动驾驶等典型应用场景。资源共三十七个文件以m源码为主辅以jpg结果图、mat数据和eps矢量图整体仅三百六十五KB轻量易用。已有七百三十八人学习源码中包含多种运动模型实现并配有对应图片与数据文件便于对照分析不同模型在匀速、变速、转弯等场景下的滤波效果。通过研读这些代码可以快速掌握卡尔曼滤波的预测更新流程、IMM的模型概率融合机制以及多目标跟踪中数据关联的基本思路同时了解匈牙利算法等关联技术的应用背景适合作为课程设计或算法入门的参考。1. 目标跟踪算法在 MATLAB 里到底怎么选型先别急着写代码目标跟踪算法在 MATLAB 里落地卡住的往往不是“工具箱里没有”而是“卡尔曼滤波、数据关联、检测和跟踪怎么串成一条能跑的链路”。拿视频数字图像处理做得多的工程师都有这种体会检测模型把目标框出来只是第一步目标一遮挡就丢两个目标一靠近就互换身份跑完仿真换真实视频又翻车。这篇直接回答三个问题单目标的最小闭环怎么搭多目标的数据关联怎么做以及“用 YOLO 做多目标跟踪”这句话实际上指的是什么。适合想在 MATLAB 2023b 或更新版本里跑通从检测到跟踪全套流程的从业者不涉及训练自己的网络也不用懂太多数学推导。2. 用 MATLAB 跑通单目标跟踪最小系统卡尔曼滤波的预测与校正2.1 为什么先从卡尔曼滤波开始状态方程、观测方程和协方差在做什么单目标跟踪的最小形态就是卡尔曼滤波。它假设目标在相邻帧之间服从某个运动模型再把模型预测和每一帧的实际检测做加权融合输出一个修正后的位置。对大多数视觉目标匀速模型ConstantVelocity已经够用状态向量取[x; y; vx; vy]位移等于速度乘以帧间隔。MATLAB 的vision.KalmanFilter把这一过程封装成两步操作没有传入观测时是预测传入观测时是校正。ProcessNoise和MeasurementNoise是两个直接影响效果的旋钮前者表示你对运动模型的信任度后者表示你对检测结果的信噪比。协方差矩阵 P 则是卡尔曼转发散的黑匣子入口它代表当前估计到底可不可信。P 初始值设得太大滤波前几十帧会出现明显滞后设得太小跟踪框会被检测噪声牵着走。工程上不建议一上来就啃矩阵推导先把两个数字定下来P 初值取 110ProcessNoise 取 0.010.1MeasurementNoise 取 15跑一遍看轨迹平滑度再微调。2.2 最小可复现代码视频帧循环 卡尔曼滤波% 单目标跟踪最小系统帧循环 卡尔曼滤波 % 前提Computer Vision ToolboxR2020b 及以上版本均可运行 vidReader VideoReader(pedestrian.mp4); frame readFrame(vidReader); imshow(frame); [x, y] ginput(1); % 点一下目标中心完成第一帧初始化 % 创建卡尔曼滤波器匀速模型 kf configureKalmanFilter(ConstantVelocity, ... [x, y], ... % 初始位置来自第一帧点选 [1 1], ... % 初始估计误差协方差范围 [10 10], ... % 过程噪声运动模型不确定度 1.0); % 测量噪声检测结果方差 traj []; while hasFrame(vidReader) frame readFrame(vidReader); % 假设 detectTarget() 返回当前帧目标中心坐标 [cx, cy] detectTarget(frame); if cx 0 % 先预测后校正卡尔曼滤波标准两步 predicted kf(); corrected kf([cx, cy]); % ConstantVelocity 模型下状态为 [x; y; vx; vy] traj(end1, :) corrected(1:2); %#okSAGROW else % 没有检测结果时仅用预测值维持轨迹 corrected kf(); end imshow(frame); hold on; plot(corrected(1), corrected(2), ro); plot(traj(:,1), traj(:,2), g-); hold off; end逻辑说明configureKalmanFilter一步完成滤波器创建和状态初始化之后调用kf()是预测调用kf([cx, cy])是预测加校正合并执行返回的是校正后的状态向量。把状态向量的前两维画出来就是平滑后的目标轨迹。detectTarget在真实项目里可以是一个背景差分函数也可以替换成任何检测器接口。参数说明initialEstimateError传[1 1]含义是初始位置误差方差约 1 像素motionNoise传[10 10]表示运动模型每个采样周期可能有 10 像素级的位置不确定度measurementNoise传 1.0对应观测噪声方差 1 像素。检测质量高就往小调检测框抖得厉害就适当放大到 510。2.3 三个必调参数过程噪声、测量噪声和初始协方差的工程取值参数MATLAB 入口偏小偏大推荐起点过程噪声 QmotionNoise轨迹僵硬跟不上目标转弯轨迹抖动滤波形同虚设1020测量噪声 RmeasurementNoise过度相信检测框沿检测噪声跳轨迹过平滑真实位置滞后15初始协方差 PinitialEstimateError前几帧滤波结果远离首个观测前 20 帧收敛缓慢15如果你用的是手写卡尔曼而不是configureKalmanFilterQ 和 R 矩阵通常设对角阵。Q 的对角元素对应加速度方差R 的对角元素取最大检测像素偏差的平方。一个我常用的调试顺序先把 R 固定在 1从 0.01 开始逐步加 Q直到轨迹不再在直行段出现扇形抖动然后反过来调 R观察目标在弯道处的跟踪滞后。两个方向各调一次大多数场景够用。3. 从单目标到多目标数据关联、匈牙利算法与跟踪生命周期3.1 多目标跟踪为什么难数据关联的本质是决策不是匹配多目标跟踪的难点不在滤波而在“每个检测框对应哪条轨迹”。如果视频里只有一个人检测框直接拿去更新唯一轨迹就行一旦有三个行人三个检测框对三条轨迹排列组合有 9 种可能其中还有遮挡造成的漏检和误检。这个问题叫数据关联工程上最常用匈牙利算法的变种。匈牙利算法解决的是“最优指派”把检测和轨迹看作两个集合计算两两匹配的代价找总体代价最小的分配方案。但真实现还得回答三个问题代价用什么算匹配不上怎么办轨迹什么时候新建、什么时候删除。每个问题各有各的坑比如代价矩阵不设上界算法会把相距几十米的目标强行匹配上名义上“关联成功”实际跟踪全乱。所以对初学者我的说法一直是数据关联不是匹配是决策。3.2 用 assignDetectionsToTracks 做检测-轨迹关联IoU 代价矩阵% 多目标跟踪核心检测-轨迹关联 % 输入 % trackPreds : N×4 矩阵每条轨迹的预测框 [x, y, w, h] % detections : M×4 矩阵当前帧检测框 [x, y, w, h] % 输出 % assignments : K×2 矩阵第 1 列轨迹索引第 2 列检测索引 N size(trackPreds, 1); M size(detections, 1); % 1. 计算 IoU 并转换成代价矩阵 iouMatrix bboxOverlapRatio(trackPreds, detections, Union); costMatrix 1 - iouMatrix; % 2. 代价上界IoU 太低的配对直接设成 Inf禁止匹配 maxCost 0.3; % 相当于要求 IoU 0.7 costMatrix(costMatrix maxCost) Inf; % 3. 未匹配代价低于这个值宁可多留一条待确认轨迹 costOfNonAssignment 0.5; % 4. 调用匈牙利算法变种做最优指派 [assignments, unassignedTracks, unassignedDetections] ... assignDetectionsToTracks(costMatrix, costOfNonAssignment);逻辑说明bboxOverlapRatio是 MATLAB 内置的边界框重叠率函数返回 N×M 的 IoU 矩阵。代价取1 - IoU重叠率越高代价越低。第 2 步把代价大于 0.3 的格子设为 Inf等于告诉匈牙利算法“这一对不允许匹配”。assignDetectionsToTracks返回的unassignedTracks是没找到对象的轨迹unassignedDetections是需要开启新轨迹的检测框。参数说明maxCost是门控阈值决定“多近算同一个目标”。对行人视频 0.30.5 都可接受对无人机俯拍的小目标要放到 0.7因为目标小帧间位移相对像素占比大IoU 天然偏低。costOfNonAssignment控制轨迹“顶着不匹配继续等”的耐心设 0.5意味着一条轨迹宁可等待也不接受代价高于 0.5 的低质量匹配设大了轨迹容易被一次假检测带偏。3.3 跟踪生命周期与三个关键参数最大轨迹数、关联门限、遮挡容忍帧数有了关联结果多目标跟踪器还要管理轨迹的创建、确认和删除。MATLAB 统一封装是trackerGNN它把卡尔曼预测、数据关联、滤波更新和生命周期管理放进一个对象里参数集中在构造时设置。三个参数对应三类现象参数作用设错会看到什么MaxNumTracks最多同时维护多少条轨迹设小了新目标出现后轨迹被挤出ID 错乱AssignmentThreshold关联门控阈值相当于替换手写 maxCost设大了误检也能匹配轨迹出现虚假轨迹NumCoastingSteps目标跟丢后允许“空跑”多少帧设 0丢失一帧轨迹就断遮挡后必然新 ID关于NumCoastingSteps先给结论遮挡场景下至少设 35 帧靠预测值撑过遮挡窗口。这是多目标跟踪与单目标卡尔曼应用上最大的差别——单目标可以靠一次预测顶住多目标必须防止轨迹被过早删除否则同一目标露头后会被当成新目标产生一次莫名其妙的 ID 切换。4. 检测器 跟踪器把 YOLO 输出接进 MATLAB 多目标跟踪链路4.1 检测与跟踪的分工检测器负责“看见”跟踪器负责“记住”实际项目里你不会自己造卡尔曼和匈牙利算法而是组合深度网络检测器和跟踪器。检测器在单帧图像上输出候选框、类别和置信度跟踪器负责把这些候选框串成时间上有连续身份的轨迹。一个常被忽略的点是检测器输出的检测框只是“观测”跟踪器要做的是把观测关联到已有轨迹再输出平滑后的结果。这个分工带来的收益很直接检测器某帧漏检了跟踪器能靠匀速模型预测把轨迹续上检测器误报了跟踪器会在关联门限里把它拦一下。反过来如果你直接把检测结果逐帧画框不做任何滤波视频里会出现肉眼可见的框抖动——这是“有检测无跟踪”的典型症状。4.2 最小可用代码yolov4ObjectDetector trackerGNN 的检测驱动跟踪% 检测驱动的多目标跟踪YOLO 全局最近邻跟踪器 % 前提Computer Vision Toolbox Sensor Fusion and Tracking Toolbox detector yolov4ObjectDetector(csp-darknet53-coco); tracker trackerGNN(MaxNumTracks, 50, ... AssignmentThreshold, 30, ... CostOfNonAssignment, 0.8); while hasFrame(vidReader) frame readFrame(vidReader); % 检测阈值 0.45 是召回率与误检的折中点 [bboxes, scores, ~] detect(detector, frame, Threshold, 0.45); if isempty(bboxes) detections {}; else % 把检测框转成标准观测格式供 tracker 消费 detections cell(size(bboxes, 1), 1); for i 1:size(bboxes, 1) detections{i} objectDetection(0, ... bboxes(i, :), ... MeasurementNoise, diag([4 4 1 1]), ... ObjectAttributes, {scores(i)}); end end % 推进跟踪器第二个参数传当前视频时间戳 tracks tracker(detections, vidReader.CurrentTime); confirmed tracks([tracks.IsConfirmed]); % 只保留确认轨迹 imshow(frame); hold on; for i 1:numel(confirmed) bbox confirmed(i).State(1:4); % 状态向量中的位置分量 rectangle(Position, bbox, EdgeColor, [0 1 0], LineWidth, 2); text(bbox(1), bbox(2), ... sprintf(ID %d, confirmed(i).TrackID), ... Color, y, FontSize, 12); end hold off; end逻辑说明detect给出当前帧的框和置信度objectDetection把这些框包成跟踪器需要的观测对象MeasurementNoise指定观测噪声协方差tracker(...)内部完成预测、关联、滤波和轨迹生命周期管理返回objectTrack对象数组。IsConfirmed属性表示轨迹已经连续多帧关联成功不画未确认轨迹可以避免把单帧误检显示成目标。参数说明AssignmentThreshold设 30 是给 GNN 的默认距离单位对应位置加尺寸的加权距离不是 IoU。值越大允许越远的框关联在一起低帧率视频要适当放大。diag([4 4 1 1])对应框中心 x、y 各 4 像素跟踪不确定度宽高各 1 像素检测质量差时把对角线对称放大即可。4.3 别只盯 mAP帧率、漏检率和 ID Switch 才是跟踪要看的指标很多人把目标检测的 mAP 直接当成跟踪效果的晴雨表这是常见的落地点误区。mAP 衡量的是单帧检测质量跟踪看的是跨帧稳定性。一个 mAP 很高的检测器如果在小目标上频繁闪烁漏检跟踪器的 ID 切换率依然会惨不忍睹。真正值得盯的是三个东西处理帧率 FPS、漏检率目标在视野内但检测器没出框的帧比例、ID Switch 次数。FPS 低于 5 基本告别实时漏检率高于 20%再好的跟踪器也补不回来ID Switch 高说明关联代价设得离谱或检测输出不稳定。检测器漏检的代价比误检高一个量级调参时应先保召回收益远超调节跟踪器参数。5. 避坑与常见问题排查目标跟踪算法在 MATLAB 里最容易翻车的六个地方5.1 现象跟踪框剧烈抖动目标明明是匀速移动原因测量噪声 R 设得太小相当于告诉滤波器“检测框绝对可信”于是跟踪输出被检测噪声牵着走。另一个高频原因是视频本身有隔行扫描或压缩噪声检测中心在相邻帧之间跳 23 个像素。解决把measurementNoise从 1 加到 5再把过程噪声 Q 从 10 减到 5。如果还抖对检测坐标做一个时间上的中值滤波再喂给跟踪器。逐帧抖就调 R周期性甩尾就调 Q两个方向不要同时大改。5.2 现象目标被遮挡 2 秒后回来轨迹断了ID 从 5 变 12原因跟踪器默认把连续未匹配的轨迹判为“假轨迹”删除。遮挡期间没有检测框匹配轨迹生命周期到了头目标再出现时只能开新轨迹。解决把NumCoastingSteps设为 5 到 8让轨迹在丢失检测时靠预测继续存活同时确认CostOfNonAssignment不要让轨迹在遮挡期间被强行匹配到远处的误检。多目标场景里遮挡比误检更致命这条血泪经验我每次调试都会跟对方强调一遍。5.3 现象MATLAB 2023b 打开项目中文注释和变量名全部乱码原因项目文件以 UTF-8 编码保存但 MATLAB 2023 在中文 Windows 上的默认字符编码是 GBK读取时按 GBK 解释 UTF-8 字节流自然全乱。解决在 MATLAB 主页进入“预设”找到“常规”里的字符编码设置把默认编码从系统 GBK 改成 UTF-8重启后生效也可以用命令feature(DefaultCharacterSet,UTF-8)临时切换验证。改完一次性解决旧项目中文注释乱码问题。5.4 现象仿真数据跑得完美换真实视频就跟踪失败原因仿真场景没有复杂背景、目标尺寸固定、没有遮挡跟踪器参数是为理想环境调的。真实视频里光照变化、目标形变和相机抖动引入的测量噪声远比仿真模型大。解决把真实视频统一缩放到与仿真一致的目标像素尺寸先跑一遍纯检测看召回再开跟踪。真实环境的参数不能沿用仿真值Q 和 R 要重新标定。5.5 现象卡尔曼滤波器发散协方差矩阵非正定MATLAB 报警告原因多数是手写卡尔曼实现时初始协方差的矩阵元素取了 0或者测量矩阵维数与状态不匹配导致协方差预测一步后失去正定性。用vision.KalmanFilter时反复跳过校正也会让误差积攒到发散。解决检查初始协方差 P0对角元素至少取 1观测缺一帧就不要调用校正只用预测顶住确保MeasurementNoise维数对应观测向量别把速度噪声混进位置观测。5.6 现象检测框与跟踪框总是差半个身位多目标关联大面积失配原因坐标系和框表示不一致。YOLO 返回的是左上角加宽高[x, y, w, h]而跟踪器内部常用中心点[cx, cy]加宽高个别函数又用四角点。两边混着用代价矩阵从一开始就是错的关联自然对不上。解决进跟踪器之前统一转成中心点加宽高退出后再转回绘制的框格式。转换放在一个集中函数里做不要散落在循环各处否则排查时很难发现谁把坐标换错了。6. 验证跟踪算法效果的三个手段从可视化到 MOT 指标6.1 用 MATLAB 自带工具生成 Ground Truth避免手标视频的血压飙升可视化只能让你“看着像”提交报告或做方案对比时必须给数值指标而数值指标依赖标注真值。常见做法是用 Ground Truth Labeler 框出每一帧目标导出成groundTruth对象。不必手动逐帧点先用findTracks让跟踪结果生成一批候选框再回放标注器批量修正能省下七成标注时间。标注时框略微比目标大一圈没关系但要和检测器平时给出的框保持同样松紧度否则 IoU 指标会系统性偏低。6.2 计算 MOTA、MOTP 和 ID Switch一份可抄的脚本模板% 评估脚本给定真值轨迹与跟踪轨迹计算 MOTA / MOTP / ID Switch % gtList, estList 均为 struct 数组字段 frameId, bbox [x y w h] % trackId 用于识别轨迹身份 % 1. 按帧匹配真值与估计框IoU 大于 0.5 算正确命中 matches matchBboxes(gtList, estList); % 自定义匈牙利匹配 tp 0; fp 0; fn 0; idsw 0; prevIds containers.Map(KeyType, int32, ValueType, int32); for k 1:numel(matches) gtId matches(k).gtTrackId; estId matches(k).estTrackId; if gtId 0 estId 0 tp tp 1; % ID Switch同一真值轨迹关联到了不同估计轨迹 if prevIds.isKey(gtId) prevIds(gtId) ~ estId idsw idsw 1; end prevIds(gtId) estId; elseif gtId 0 fn fn 1; else fp fp 1; end end % 2. 三个核心指标 MOTA 1 - (fn fp idsw) / numel(unique([gtList.trackId])); MOTP 1 - mean(IoUOfMatchedPairs); % 匹配对平均 IoU需自行计算 fprintf(MOTA%.2f MOTP%.2f ID Switch%d\n, MOTA, MOTP, idsw);逻辑说明matchBboxes需要按帧做匈牙利指派思路和前面assignDetectionsToTracks一致只是输入变成了真值框和估计框。MOTA 把漏检、误检和 ID 切换统一折算成 01 得分MOTP 反映定位精度ID Switch 单独报告身份稳定性。6.3 一个调参习惯先调检测置信度阈值再调跟踪器我的习惯永远是先调检测置信度阈值。把Threshold从 0.5 降到 0.3如果误检数量上升但跟踪稳定性变好说明检测阈值是当前瓶颈如果误检框反而把轨迹带偏说明要先收紧AssignmentThreshold再把阈值降回去。动手调跟踪器之前先确定检测输出能不能信是我吃了不少亏之后固下来的流程。最后分享一个经验保存调参结果时把视频分辨率、帧率、检测阈值和 Q、R 一起记录下来否则一个月后回来看到的只是一组来历不明的参数从头再开始就又成了玄学调参。这个习惯帮我少走了很多冤枉路。希望帮到你。本文还有配套的精品资源点击获取
返回列表