尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

多目标跟踪实战指南:从SORT到ByteTrack的算法选型与调参心法

多目标跟踪实战指南:从SORT到ByteTrack的算法选型与调参心法 简介面向计算机视觉入门与进阶学习者的多目标跟踪代码资源采用VIBE前景检测与卡尔曼滤波组合方案适合理解视频监控、自动驾驶中动态目标的检测与持续跟踪流程。压缩包共16个文件以C编写包含7个头文件和7个源文件分别承担接口声明、算法实现和主程序逻辑另含pro工程配置及user用户文件便于在Qt环境中快速打开、编译和运行。目前已有289人学习下载。通过阅读VIBE背景建模源码可掌握像素级前景提取原理结合卡尔曼滤波器代码能够理解状态预测、目标关联与轨迹更新机制匈牙利算法实现也同步提供有助于完整梳理多目标跟踪任务的关键环节。压缩包整体仅18KB代码精炼无需庞大依赖即可作为教学演示或算法实验模板使用。 第一次被多目标跟踪MOT真正“教育”是我在一个地铁出入口人流量统计项目里。单帧行人检测的mAP当时已经跑到了不错的水平结果一接到视频流就傻眼了同一个乘客在画面里来回走ID一会儿切换一次计数跟着乱跳复查轨迹更是完全对不上。也就是从那个项目开始我才彻底明白——检测只是给单张图里的目标画框跟踪才是把一张张独立的图片串成一个完整的故事它得回答一个看似简单、做起来却很难的问题这一帧里的目标A跟上一帧里的目标A到底是不是同一目标多目标跟踪Multi-Object Tracking, MOT要解决的就是这个身份关联的连续性问题同时要给画面里所有目标分配并维护各自的ID最终输出一段段可用的轨迹。如果你正在做安防监控、智慧交通、商业客流统计、体育赛事分析或者自动驾驶中的多目标感知这个方向基本绕不开。这篇文章没什么玄乎的理论主要是我从算法选型到项目落地踩过坑之后的一套完整心法。1. 先弄清楚多目标跟踪在解决什么问题1.1 检测和跟踪的分工到底在哪很多人第一次接触多目标跟踪时会觉得“既然检测已经框出目标了那跟踪不就是把前后的框连起来吗”。话是没错但这个“连起来”的动作恰恰是难点所在。单帧检测器做的事是独立地对每一帧图像输出目标的位置、类别和置信度它不关心这个目标在上一帧有没有出现过。也就是说把同样的视频分别给同一检测器跑两次同一帧的结果完全一致但帧与帧之间没有任何绑定关系。你可以理解为检测是在“拍照”每张照片里有一堆人但你不知道前后两张照片里谁是同一个人。跟踪器则是在“拍视频”它要做的是给每个目标建立一条时间上的轨迹。为了做到这一点它需要三个能力第一预测目标下一帧大概会出现在哪里通常靠卡尔曼滤波这类运动模型第二把当前帧检测到的新位置和已有的轨迹做匹配通常靠IoU、马氏距离、特征相似度等第三对轨迹的整个生命周期做管理哪些轨迹是新出现的哪些已经丢失太久该删除了。所以我习惯把检测叫做“感知”把跟踪叫做“认知”。检测给的是当前时刻的事实跟踪给的是一个持续存在的身份。项目里如果只要统计“画面上有几辆车”单帧检测就够了一旦需求变成“这条路每辆车分别停留了多久”“这个顾客在店里逛了哪些区域”就必须上多目标跟踪。1.2 哪些场景离不开多目标跟踪多目标跟踪的典型应用场景我大致梳理成下面几类安防监控对公共场所的行人做轨迹回放、尾随检测、徘徊预警。这类场景最大的特点是目标密集且经常互相遮挡。智慧交通对路口车辆进行跟踪和计数识别逆行、违停、连续变道等行为。车辆运动规律性较强但速度快帧率不足时匹配很容易出问题。商业分析顾客动线分析、门店热力区统计需要跨摄像头甚至跨天维持同一个顾客的身份。体育赛事球员位置追踪、跑动距离统计、战术阵型分析需要在高动态、强对抗的情况下保持ID稳定。自动驾驶行人、车辆、非机动车的多目标感知这个场景对实时性和安全性要求都很高通常还会和传感器融合一起做。场景不同多目标跟踪的难度差异非常大。空旷马路上跟踪一辆直行的车几乎不用什么技巧但在商场高峰期跟踪密集人群ID经常在几帧内互相交换。这也直接决定了算法选型和参数设定的方向。2. 主流技术路线选型SORT、DeepSORT与ByteTrack2.1 从SORT到DeepSORT再到ByteTrack的演进这几年多目标跟踪的主流框架基本都是tracking-by-detection路线也就是先靠检测器给出目标框再由跟踪器做关联。在这个框架下最经典的几个方案值得好好聊一聊。SORTSimple Online and Realtime Tracking是绕不开的起点。它用卡尔曼滤波预测目标位置用IoU计算检测框和预测框的相似度用匈牙利算法做二分匹配。整套逻辑非常朴素但速度快、结构简单当年在行人跟踪上给整个社区提供了一个干净利落的baseline。它的问题也很明显一旦目标被遮挡几帧或者检测框抖动厉害ID就会发生切换而且没有任何补救手段。DeepSORT在SORT基础上加了外观特征也就是用一个人体ReID模型提取目标的视觉特征计算特征间的余弦相似度跟运动相似度一起加权再送进级联匹配流程。加了这一路外观信息之后ID切换明显减少尤其在被遮挡后重现时能靠“认脸”把ID接回来。但代价是额外引入一个ReID模型训练和推理都有成本而且依赖行人外观特征的稳定性。ByteTrack则是近两年实战项目里非常受欢迎的方案。它的核心洞察很直接传统方法在关联时只保留高置信度的检测框那些低置信度的框往往被直接丢弃但被遮挡的目标、远处的小目标恰恰最容易产生低置信度框。所以ByteTrack的做法是先用高置信度框做第一轮匹配再把没匹配上的轨迹和低置信度框做第二轮匹配。就这么一个看似简单的改动在很多密集场景里把跟踪效果拉高了一大截。2.2 我为什么在实际项目里选ByteTrack这套路线我在多个项目里对比过SORT、DeepSORT和ByteTrack最终默认方案基本都是ByteTrack原因有三点。一是实现成本低。ByteTrack不需要额外训练ReID模型只要有一个质量过关的检测器就能跑出稳定的结果。对项目交付来说少一个模型就少一份数据采集和训练部署的工作量。二是它对检测器的依赖非常直接。跟踪效果的上限几乎完全由检测召回率决定。我可以在项目早期先集中精力调好检测器再套上BytesTrack流程而不用面对“检测一般、ReID也一般、关联算法还很重”的三重排查困境。三是它在实时性和稳定性之间平衡得很好。ByteTrack的计算开销主要还是在检测器上关联部分非常轻量在嵌入式设备上也能跑得动。当然它也有短板。如果目标的外观特征非常相似比如统一工作服的工人、同色车辆且运动规律又不明显ByteTrack缺少外观信息兜底长时遮挡后恢复效果一般。这种情况我会考虑在ByteTrack的基础上加一个轻量级的ReID分支做辅助匹配而不是直接上全套DeepSORT。3. 手把手搭建一套可落地的多目标跟踪流程3.1 检测器配置高置信度与低置信度分开用做多目标跟踪第一步永远是先把检测器输出调整好。我常用的检测器是YOLOv8或YOLOv5系列训练完成后推理时对每一帧都会输出形如[x1, y1, x2, y2, score, class]的结果。大部分跟踪器在关联阶段只会使用score大于某个阈值比如0.5的框低于阈值的框直接当不存在。但ByteTrack思路强调的是低置信度的框里往往藏着被遮挡目标的“残影”。所以实际操作时会设两个阈值一个高置信度阈值用于正常匹配一个低置信度阈值用于处理被遮挡后轨迹的二次关联。我自己的经验是高置信度阈值设置在0.4到0.6之间低置信度阈值设置在0.1左右。如果场景里小目标多高阈值要适当调低避免大量目标在第一轮就被当成背景丢掉。3.2 轨迹状态机管理Tentative、Confirmed与Deleted跟踪器内部每条轨迹都有自己的生命周期一般用状态机来管理。我最常用的状态有三种Tentative候选轨迹。刚检测到新目标时先不急着确认因为可能只是误检。Confirmed确认轨迹。当一个候选轨迹连续若干帧都能匹配上检测框才认为它是一个稳定目标正式参与输出。Deleted删除轨迹。当一条轨迹连续多帧找不到匹配的检测框说明目标可能已经离开画面或长时间被遮挡达到阈值后将它删除。这背后的逻辑是新目标出现的第一帧你无法判断它是真实目标还是检测器的一次抖动。如果立刻确认一个误检就会生成一条垃圾轨迹后面还会污染关联过程。所以需要“观察期”也就是min_hits参数。同样的目标丢失时也不能立刻删除轨迹。遮挡会让目标从画面里暂时消失等它再出现时如果已经删除了轨迹就会被当成新目标分配新ID。所以需要max_age参数给轨迹一个“找回”的时间窗口。3.3 核心代码实现与关键参数设定下面是我在项目里常写的极简版核心逻辑去掉了大量工程细节但保留了多目标跟踪最关键的结构。from scipy.optimize import linear_sum_assignment class TrackState: TENTATIVE 0 CONFIRMED 1 DELETED 2 class Track: def __init__(self, bbox, score, track_id): self.track_id track_id self.bbox bbox self.score score self.hit_streak 0 self.time_since_update 0 self.state TrackState.TENTATIVE def predict(self): # 卡尔曼滤波更新bbox代码省略常规匀速模型 self.time_since_update 1 def update(self, bbox, score): self.bbox bbox self.score score self.hit_streak 1 self.time_since_update 0 if self.state TrackState.TENTATIVE and self.hit_streak min_hits: self.state TrackState.CONFIRMED if self.time_since_update max_age: self.state TrackState.DELETED def associate(det_boxes, track_boxes, iou_threshold): # 计算IoU代价矩阵 iou_matrix compute_iou(det_boxes, track_boxes) # 匈牙利算法求解最大权重匹配 row_idx, col_idx linear_sum_assignment(-iou_matrix) matches, unmatched_dets, unmatched_tracks [], [], [] for d in range(len(det_boxes)): if d not in row_idx: unmatched_dets.append(d) for t in range(len(track_boxes)): if t not in col_idx: unmatched_tracks.append(t) for r, c in zip(row_idx, col_idx): if iou_matrix[r, c] iou_threshold: unmatched_dets.append(r) unmatched_tracks.append(c) else: matches.append((r, c)) return matches, unmatched_dets, unmatched_tracks上面这段代码的核心就是用IoU矩阵加匈牙利匹配完成检测框和轨迹框之间的硬关联。真实项目里我还会在关联前先把轨迹框通过卡尔曼滤波往前预测一帧让匹配使用预测位置而不是上一帧的历史位置这样能明显提高目标运动时的匹配成功率。3.4 关键调参参考表每次给不同场景调试时我基本都会从下面这张表开始设定参数再按实测效果微调参数推荐初值作用调节方向高置信度阈值0.5第一轮正常匹配的检测框筛选小目标多则降低到0.4低置信度阈值0.1第二轮匹配被遮挡目标的候选框误检多则提高到0.15min_hits3候选轨迹转为确认轨迹所需连续命中次数误检多则提高到5max_age30轨迹丢失后最多保留帧数遮挡时间长则提高到50iou_threshold0.3判定匹配成功的最小IoU目标密集则降低到0.25输入帧率25fps视频帧率帧率低时需提高max_age这套参数在30fps的行人场景下通常能跑出不错的基线效果。但注意参数永远跟着场景走帧率低的视频、运动快的小目标、密集人群都需要重新评估。4. 实战中绕不开的坑遮挡、相机抖动与ID切换4.1 遮挡场景怎么让ID跨过去遮挡是多目标跟踪里最让人头疼的问题没有之一。一个行人在人群里被另一个行人挡住两秒钟检测器的置信度会迅速下降有时候框直接消失。如果跟踪器在此时把低置信度框全部丢弃那这条轨迹就断了等目标重新露出来妥妥生成一个新ID。ByteTrack处理这个问题的思路是给轨迹“留后路”目标被遮挡时检测框分数会降低它依然会进入第二轮匹配只要IoU不是完全为0轨迹就能靠预测框跟这个低分框黏上从而维持原来的ID。我实测下来在遮挡不超过1秒的场景里ID切换率能比普通SORT减少一半以上。但如果遮挡超过3秒甚至更久单靠低置信度框匹配也无济于事因为目标被完全挡住时压根没有可用的检测框。这时只能靠max_age把轨迹多保留一段时间并结合运动模型预测目标可能的位置。再久一点基本就只能接受ID切换的现实了。4.2 相机运动会毁掉整个关联结果如果你用的是固定枪机摄像头上面的流程基本够用。但一旦摄像机在转动、缩放或者安装位置有风导致轻微晃动问题就来了画面里所有目标都在往同一个方向偏离检测框的坐标整体漂移而卡尔曼滤波却以为每个目标都在按自己的速度移动。我的经验是在做数据关联之前先算一个全局运动补偿。最简单的做法是抽取前一帧和当前帧的背景特征点用稀疏光流估计全局单应矩阵然后把轨迹预测框按照单应矩阵做一个坐标变换再进行IoU匹配。这一步在很多球机跟踪项目里是刚需。还有一个小技巧尽量让检测器的输入图像稳定。如果摄像头本身有电子防抖优先开启如果画面抖动严重可以考虑在检测前做一次轻量的图像稳定预处理这能减少大量因为坐标跳动导致的ID误切换。4.3 参数调优中的实测心得几个我在实战中被反复教做人的点值得拿出来单独说。第一min_hits不适合设成1。很多新手为了快速显示轨迹把min_hits设成1结果检测器一个闪烁的误检就会变成一条永久轨迹。至少等3帧确认才不会把噪声当成目标。第二低置信度阈值不要设得太低。我见过有人把低置信度阈值调到0.05来追求召回结果大量背景纹理也被当作检测框轨迹会被垃圾框污染反而导致ID频繁切换。0.1是一个比较平衡的起点。第三帧率对跟踪效果的影响非常大。同一个视频源文件如果只有10fps那么目标在相邻帧之间的位移可能非常大IoU匹配很容易断开。此时要么使用更高帧率的视频源要么把max_age调得很大但后者会让轨迹混叠的风险增加。第四检测器的召回率永远优先于精度。跟踪场景下漏检一个框可能意味着一条轨迹断裂多检测一个误检框至少可以被跟踪器用min_hits过滤掉。所以我训练检测器时宁可让精度稍微下降也要把召回率拉上去。5. 评估多目标跟踪效果的正确姿势5.1 看懂MOTA、IDF1和MT/ML三个核心指标多目标跟踪的评估不能光靠肉眼觉得“看起来还可以”。行业里比较常用的指标是MOTA、IDF1和MT/ML它们各有侧重。MOTAMultiple Object Tracking Accuracy综合了误检、漏检和ID切换公式是1减去(FPFNIDSW)除以真实目标总数。它衡量的是整体跟踪准确度数值越高越好但要注意MOTA没有上限严格约束可能在遮挡严重时变成负数。IDF1衡量的是ID分配的F1分数也就是轨迹身份层面的精确率和召回率调和平均。如果说MOTA关心“框有没有跟住”那IDF1就是关心“身份有没有搞对”。如果一个目标从头到尾都在跟踪框里但ID从1变成了3MOTA只记一次IDSWIDF1却会受到较大影响。MT/ML则是从轨迹完整性角度统计的MT表示一个目标在大部分生命周期中都被成功跟踪ML表示大部分时间都丢失了。对业务侧来说MT和ML往往比平均数更有解释力。5.2 评估结果如何对齐真实的业务场景我的习惯是每次调完版本都会用一条固定的线下评估脚本跑同一批测试视频输出MOTA、IDF1、IDSW、MT/ML这些指标再结合具体的业务需求做判断。比如做区域人数统计时我会更关注FP和FN因为多计或少计一个人直接影响运营决策做轨迹回放时更关注IDF1和IDSW因为ID一旦切换回放就被拦腰截断。只盯着一个MOTA做优化很容易在某个指标变好的同时把其他维度搞坏。还有一点离线评估指标和线上实时表现往往有差异。离线测试视频里的相机角度、光照条件可能和实际现场不一致所以我会在项目交付前专门录制一段真实场景数据做一次现场指标复测。这也是多目标跟踪项目里最常被忽略、但最该花时间做的一步。最后顺着个人踩坑经验多分享一句多目标跟踪的优化80%的收益来自检测器的召回率而不是关联算法本身的花哨程度。如果检测器在目标重叠时把其中一个目标丢掉再复杂的ReID和匹配策略也救不回来。所以我现在的流程是先花大功夫把场景检测调到顺手再用轻量可靠的关联器去接轨迹每次改动都用同样一批视频做定量对比而不是靠肉眼感觉判断好坏。这套方法帮我少走了很多弯路也推荐你试试。本文还有配套的精品资源点击获取
返回列表