资讯详情

资讯详情

YOLOv7与多目标跟踪算法比较:VisDrone2019上的SORT、DeepSORT、ByteTrack、Bot-SORT实战

简介本资源是一套面向计算机视觉研究者与算法工程师的离线测试平台聚焦YOLOv7目标检测与多目标跟踪算法的横向对比适用于视频监控、自动驾驶、机器人导航等场景下的算法选型与性能评估。包内集成SORT、DeepSORT、ByteTrack、Bot-SORT等多种跟踪算法并基于VisDrone2019无人机航拍数据集进行统一测试便于在相同条件下比较各算法在遮挡、低分辨率等复杂环境中的表现。资源共393个文件以134个py源码与204个pyc编译文件为主体辅以yaml配置、png示意图、mp4演示视频、txt说明及md文档等压缩包约229.98MB目录结构清晰便于按模块检索与二次开发。目前已有107人学习下载。使用者可获得完整的算法集成框架、离线评测流程、可视化结果与操作说明文档快速复现实验并定位各跟踪算法的优势与不足为项目选型与优化提供参考。1. 无人机视角下的多目标跟踪为什么你的 YOLOv7 检测框总在跳如果你跑过 VisDrone2019 这类无人机航拍数据集大概率遇到过一种让人抓狂的现象明明 YOLOv7 的单帧检测指标看着还行mAP 能到 0.4 上下可一旦把连续帧串起来看同一个目标的框就开始疯狂抖动ID 频繁切换小目标跟丢更是家常便饭。这不是检测器一个人的锅而是检测与跟踪的耦合方式出了问题。这套「基于 YOLOv7 目标检测与多目标跟踪算法比较系统」要解决的核心问题就是在一个离线测试平台上把 SORT、DeepSORT、ByteTrack、Bot-SORT 这几种主流跟踪算法放在同一份 VisDrone2019 数据上跑用同一套检测结果做输入横向比较它们的 ID Switch、MOTA、MOTP 等指标。适合谁用做无人机视觉、安防巡检、交通流量统计的工程师尤其是那些已经训好了检测模型、却在跟踪环节反复翻车的人。VisDrone2019 的难点在于目标极小、密度极高、遮挡严重这恰好是检验跟踪算法鲁棒性的试金石。2. 检测与跟踪的接口设计YOLOv7 输出怎么喂给跟踪器2.1 为什么不能直接把 YOLOv7 的推理结果丢进跟踪器很多人第一次搭这类系统时习惯让跟踪算法自己调检测模型每帧跑一次推理。这在离线测试里是灾难SORT 和 ByteTrack 对检测框的置信度阈值敏感度完全不同DeepSORT 还需要额外的外观特征。如果每个跟踪器各自跑一遍 YOLOv7不仅耗时翻倍更致命的是检测结果无法对齐——同一帧、同一目标不同跟踪器看到的框可能因为 NMS 阈值差异而不同比较就失去了意义。正确的做法是解耦先用 YOLOv7 对 VisDrone2019 的每个视频序列做一次完整推理把每帧的检测结果类别、置信度、xyxy 坐标序列化成统一格式存盘然后所有跟踪算法都从这个缓存里读检测框。这样既保证了输入一致又能在调参时反复复用不用每次重跑检测。2.2 检测结果缓存的标准格式与生成脚本我一般用 JSON Lines 格式存检测缓存每行对应一帧结构清晰且方便流式读取。下面这个脚本把 YOLOv7 的推理输出转成缓存文件import json import cv2 import torch from pathlib import Path # 假设使用 YOLOv7 官方推理接口model 已加载 # conf_thres 和 iou_thres 是检测阶段的关键参数 def cache_detections(video_dir, model, output_path, conf_thres0.25, iou_thres0.45): 对视频目录下每个序列逐帧推理缓存检测结果 video_dir: VisDrone2019 视频序列根目录每个子目录是一个序列 output_path: 输出的 jsonl 文件路径 video_dir Path(video_dir) with open(output_path, w) as f: for seq_dir in sorted(video_dir.iterdir()): if not seq_dir.is_dir(): continue seq_name seq_dir.name img_files sorted(seq_dir.glob(*.jpg)) for frame_idx, img_path in enumerate(img_files): img cv2.imread(str(img_path)) # YOLOv7 推理返回 xyxy, conf, cls dets model(img, conf_thresconf_thres, iou_thresiou_thres) frame_record { seq: seq_name, frame: frame_idx, dets: [] } for det in dets: x1, y1, x2, y2, conf, cls det.tolist() frame_record[dets].append({ bbox: [x1, y1, x2, y2], score: conf, class: int(cls) }) f.write(json.dumps(frame_record) \n)逻辑说明这个脚本的核心是把检测和跟踪彻底分开。conf_thres0.25是 VisDrone2019 上比较稳妥的起点因为小目标本身置信度偏低阈值设太高会漏掉大量真实目标iou_thres0.45控制 NMS 的合并力度无人机视角下目标密集这个值不宜再低。缓存文件按序列和帧号索引后续跟踪器读取时直接按seq和frame查找即可。参数方面如果你发现缓存里某些序列的检测框数量异常少优先检查conf_thres是否过高如果同一目标出现多个重叠框说明iou_thres需要下调。这个缓存文件就是后续所有比较实验的基准建议生成后先统计一下每帧平均检测数VisDrone2019 的典型值在 20 到 60 之间偏离太多就要回头查检测环节。2.3 统一跟踪器输入接口的抽象层有了检测缓存还需要一个统一的接口层让 SORT、DeepSORT、ByteTrack、Bot-SORT 都能以相同方式消费数据。我通常定义一个基类把「读缓存 → 逐帧喂检测 → 收集跟踪结果」的流程固定下来from abc import ABC, abstractmethod class BaseTrackerWrapper(ABC): 所有跟踪器的统一包装基类 def __init__(self, det_cache_path, output_path): self.det_cache self._load_cache(det_cache_path) self.output_path output_path self.tracker None # 子类初始化具体跟踪器 def _load_cache(self, path): cache {} with open(path, r) as f: for line in f: rec json.loads(line) key (rec[seq], rec[frame]) cache[key] rec[dets] return cache abstractmethod def init_tracker(self): 子类实现初始化具体跟踪算法 pass abstractmethod def update(self, dets, frame): 子类实现传入当前帧检测返回跟踪结果 pass def run(self): self.init_tracker() results [] for (seq, frame), dets in sorted(self.det_cache.items()): tracks self.update(dets, frame) for t in tracks: results.append({ seq: seq, frame: frame, track_id: t.track_id, bbox: t.to_tlbr().tolist(), class: t.class_id }) # 按 MOT 挑战赛格式输出 self._save_results(results)这个抽象层的价值在于比较实验最怕的就是各跟踪器输入不一致。通过强制所有子类从同一个缓存读取、用同一个update签名你才能确信指标差异来自跟踪算法本身而不是数据管道的偏差。run方法里的排序保证了帧序正确输出格式对齐 MOT 挑战赛标准方便后续用 py-motmetrics 之类的工具算指标。3. 四种跟踪算法的落地配置与参数调优3.1 SORT 与 DeepSORT卡尔曼滤波和外观特征的取舍SORT 的本质是「卡尔曼滤波预测 匈牙利算法匹配」它只用检测框的位置和大小做关联速度极快但在 VisDrone2019 这种遮挡频繁的场景下ID Switch 会明显偏高。DeepSORT 在 SORT 基础上加了外观特征提取网络用余弦距离做级联匹配代价是每帧要多跑一次 ReID 网络。在离线测试平台上配置 SORT 时关键参数是max_age和min_hits。max_age决定一个轨迹丢失多少帧后删除VisDrone2019 里目标被遮挡后重新出现的间隔可能达到 10 到 15 帧所以max_age建议设到 30min_hits是轨迹确认所需的最小连续命中数设 3 比较平衡太低会引入大量误检轨迹太高会延迟新目标的确认。DeepSORT 的配置多了一个max_dist外观特征余弦距离阈值和nn_budget特征库容量。max_dist默认 0.2 在 VisDrone2019 上偏严因为小目标的外观特征本身就不稳定建议放宽到 0.3nn_budget设 100 足够再大只会拖慢匹配速度。3.2 ByteTrack低分检测框的二次利用策略ByteTrack 的核心创新是把检测框按置信度分成高低两组先用高分框做匹配再用低分框去补救未匹配的轨迹。这个策略在 VisDrone2019 上特别有用因为小目标的检测置信度普遍偏低如果直接按 0.5 阈值过滤会丢掉大量真实目标。配置 ByteTrack 时track_thresh是高低分框的分界线我一般设 0.5match_thresh是匹配阶段的 IoU 阈值设 0.8 比较合适track_buffer控制轨迹保留帧数和 SORT 的max_age类似设 30。需要注意的是ByteTrack 对检测框的质量比 SORT 更敏感如果缓存里的检测框本身抖动严重低分框的二次匹配反而会引入错误关联。3.3 Bot-SORT把 ReID 和运动信息融合进匹配代价Bot-SORT 可以理解为 DeepSORT 的升级版它把外观特征、运动信息卡尔曼滤波的马氏距离、IoU 距离融合成一个统一的代价矩阵并且引入了相机运动补偿。在无人机场景下相机本身在移动传统的卡尔曼滤波预测会因为全局运动而产生偏差Bot-SORT 的运动补偿能明显改善这一点。配置 Bot-SORT 时with_reid设为 True 启用外观特征proximity_thresh和appearance_thresh分别控制运动代价和外观代价的阈值。VisDrone2019 上我一般把appearance_thresh设到 0.25比 DeepSORT 的max_dist略低因为 Bot-SORT 的代价融合机制本身已经平衡了多种信息外观阈值不需要放得太宽。3.4 四种算法的参数对照与选型建议算法核心机制关键参数VisDrone2019 建议值适用场景SORT卡尔曼滤波 IoU 匹配max_age, min_hits30, 3速度优先目标稀疏DeepSORTSORT 外观特征max_dist, nn_budget0.3, 100遮挡较多外观可区分ByteTrack高低分框二次匹配track_thresh, match_thresh0.5, 0.8小目标多检测置信度低Bot-SORT多代价融合 运动补偿appearance_thresh, proximity_thresh0.25, 0.5相机运动复杂场景选型上没有绝对优劣。如果你的场景对速度要求极高SORT 仍然是最务实的选择如果遮挡严重且目标外观差异明显DeepSORT 和 Bot-SORT 更稳如果检测器本身对小目标召回不足ByteTrack 的低分框利用策略能挽回不少 ID Switch。4. 离线测试平台的避坑与排查记录4.1 检测缓存与跟踪器帧号错位导致 ID 全乱现象跑完跟踪后指标显示 ID Switch 异常高但肉眼观察视频发现跟踪框其实跟得挺准。原因检测缓存里的帧号是从 0 开始连续编号的但跟踪器内部可能按自己的计数器递增。如果缓存读取时没有严格按(seq, frame)排序或者某个序列的帧号有跳变跟踪器的卡尔曼滤波预测就会错位。解决在BaseTrackerWrapper._load_cache里强制按seq和frame排序并且在run方法里加一个断言检查每个序列的帧号是否连续。如果发现跳变回到检测缓存生成阶段排查图像文件是否缺失。4.2 VisDrone2019 小目标检测框过小导致 IoU 匹配失效现象ByteTrack 和 SORT 在密集小目标区域频繁丢失轨迹ID Switch 集中在小目标上。原因VisDrone2019 里很多目标的边界框只有十几个像素两个相邻小目标之间的 IoU 可能天然就低于匹配阈值导致匈牙利算法无法正确关联。解决适当降低匹配阶段的 IoU 阈值ByteTrack 的match_thresh从 0.8 降到 0.6SORT 的iou_threshold从 0.3 降到 0.2。同时检查检测框是否因为 NMS 过度合并而丢失了相邻目标。4.3 DeepSORT 外观特征提取网络与检测类别不匹配现象DeepSORT 的 ID Switch 反而比 SORT 还高外观匹配似乎起了反作用。原因DeepSORT 默认的 ReID 网络是在行人数据集上训练的而 VisDrone2019 包含车辆、行人等多种类别。用行人 ReID 特征去匹配车辆余弦距离自然不可靠。解决要么换一个在车辆和行人混合数据上训练的 ReID 模型要么在 DeepSORT 配置里按类别分别维护特征库。如果短期内找不到合适的 ReID 模型可以先把max_dist调大让外观匹配的权重降低退化成接近 SORT 的行为。4.4 跟踪结果输出格式与评估工具不兼容现象用 py-motmetrics 算指标时报错提示格式无法解析。原因MOT 挑战赛的标注格式要求框为[x, y, w, h]且帧号从 1 开始而很多跟踪器内部用的是[x1, y1, x2, y2]且帧号从 0 开始。解决在_save_results里统一做转换把to_tlbr()的输出转成[x, y, w, h]帧号加 1。这个转换看起来简单但漏掉任何一个细节都会让整个评估流程卡住建议在输出后先用小样本手动核对几行。4.5 多算法并行跑导致内存溢出现象同时启动四个跟踪器跑完整数据集跑到一半进程被系统杀掉。原因每个跟踪器都加载了一份完整的检测缓存四份缓存加上各自的特征库内存占用迅速攀升。VisDrone2019 的检测缓存动辄几百 MB四份就是几个 GB。解决把检测缓存改成按序列懒加载每个跟踪器只在处理当前序列时读取对应部分。或者更简单串行跑四个跟踪器反正离线测试不追求实时性稳定比快更重要。5. 用指标反推跟踪器短板一个具体的调参技巧跑完一轮比较实验后面对一堆 MOTA、MOTP、ID Switch 数字很多人会陷入「哪个高用哪个」的粗放决策。我的习惯是先把指标拆到序列级别看每个跟踪器在哪些序列上崩了再结合序列特点反推参数问题。具体做法是用 py-motmetrics 按序列分别计算指标然后画一张「序列-指标」的热力图。VisDrone2019 的序列大致可以分成几类城区密集车流、广场人群、稀疏道路。你大概率会发现SORT 在稀疏道路序列上表现不差但在密集车流序列上 ID Switch 飙升ByteTrack 在密集小目标序列上优势明显但在目标较大的序列上反而因为低分框引入噪声。这个观察直接指导调参如果你的应用场景以密集小目标为主ByteTrack 的track_thresh可以再降到 0.4让更多低分框参与二次匹配如果场景里目标尺寸变化大Bot-SORT 的proximity_thresh需要适当放宽避免运动代价主导匹配。还有一个容易被忽略的指标是「轨迹碎片化程度」也就是每个真实目标被切成了多少段轨迹。这个指标不直接出现在 MOT 挑战赛的标准输出里但你可以从跟踪结果里统计每个track_id的连续帧数分布。如果大量轨迹只有几帧就断了说明max_age或track_buffer设得太小目标被短暂遮挡后轨迹就被删除了。我自己的血泪经验是不要一上来就调跟踪算法参数先把检测缓存的质量盯住。检测框的抖动、漏检、误检跟踪器再强也救不回来。VisDrone2019 上检测环节的conf_thres每变动 0.05跟踪指标可能会有肉眼可见的变化。所以我的习惯是固定检测缓存先跑一轮基线确认检测质量稳定后再动跟踪参数。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →