简介:面向计算机视觉研究与开发者的目标检测与多目标跟踪算法对比平台,基于YOLOv7检测器,集成SORT、DeepSORT、ByteTrack、Bot-SORT四种跟踪算法,并在VisDrone2019无人机视角数据集上提供离线测试环境。资源共393个文件,压缩包229.98MB,以Python源码与pyc编译文件为主(134个py与204个pyc),另有模型权重(pth/t7)、yaml配置、测试视频、界面ui、说明文档及Docker部署文件,覆盖数据集加载、算法调用、结果输出与可视化全流程。目前已有107人学习下载。借助这一平台,可快速横向比较四种跟踪算法在相同数据与指标下的性能差异,定位各自在遮挡、低分辨率等场景的优缺点;随附的Markdown笔记、操作说明及镜像配置,也为二次开发、实验复现和算法选型提供了可落地的工程参考,适合研究对比、毕业设计与实际项目选型使用。压缩包内还含有演示视频与运行截图,可直观核验不同跟踪器的运行效果。
1. 让YOLOv7和四种跟踪器走进同一场离线考试:这套比较系统到底在比什么
做目标检测的工程师都有过这种体验:单帧上YOLOv7画框画得漂亮,一接视频流就露馅,同一个行人的ID在帧与帧之间乱跳,甚至车和人互换轨迹。要把SORT、DeepSORT、ByteTrack、Bot-SORT这些多目标跟踪算法放在一起做对比选型,最大的坑不是算法本身,而是各自用了一份不同的检测结果——有人在YOLOv7上测,有人在老旧的YOLOv5上测,最后比的根本不是跟踪器。这套“基于YOLOv7目标检测与多目标跟踪算法比较系统”的离线测试平台,本质是把检测器锁死在一次离线推理的输出上,再用统一的接口驱动四种跟踪器在VisDrone2019数据集上跑分。
这个平台适合两类人:一类是做无人机视角监控项目的落地工程师,需要在车流、人群这类密集场景里选一个能在遮挡和尺度突变下稳住的跟踪器;另一类是需要为论文实验提供可复现对比的研究生,最怕审稿人问一句“你这些指标是不是自己调过检测器”。它解决的核心问题不是“哪种跟踪器一定最好”,而是让对比结果在同一个检测缓存、同一份数据、同一套评估口径下成立——结论可以复现,指标可以追溯,参数可以换,但比较的前提始终不变。
2. 为什么是VisDrone2019:先把数据准备成比较平台能用的格式
2.1 无人机视角与高空小目标:评测跟踪器绕不开这个数据集
先讲数据集选型。目标检测数据集有很多,COCO、VOC都能测YOLOv7的单帧精度,但要评测多目标跟踪算法,必须有连续帧、有跨帧的同一目标ID标注、有密集遮挡和尺度突变。VisDrone2019是无人机视角采集的公开数据集,关注的就是“高空看地面”这个场景:行人、车辆在几十米到上百米高度下只有十几个像素到几十个像素,目标之间互相遮挡严重,摄像头自身还在移动。
这类场景恰恰是SORT这类纯运动模型的短板,也是DeepSORT、Bot-SORT这类带外观模型的算法最值得较量的地方。有人说“遥感目标检测”才需要关注高空小目标,但遥感是单张静态图,跟踪评测更看重时序一致性——同一个目标在相邻帧的检测框不能凭空消失,ID不能被别的目标顶掉。我在社区里看到有人问鸟类目标检测的数据集,其实鸟类和无人机视角下的车辆一样,真正的问题不是“类别”而是“尺寸”:目标在画面里只有几个像素时,无论检测器还是跟踪器的行为都会发生质变。VisDrone2019把这个压力集中暴露出来,所以用来做跟踪算法比较比MOT17更苛刻,也更接近真实无人机落地方案的负载环境。
2.2 认识VisDrone2019的目录结构与标注字段
拿到VisDrone2019数据后,第一件事不是跑代码,而是把它的标注格式摸清楚。检测任务(VisDrone-DET)和跟踪任务(VisDrone-MOT)的标注放在不同的目录下,MOT任务一般是一个视频对应一个文件夹,里面是jpg图片序列和gt/gt.txt标注文件。gt.txt的每行代表一个有效目标框,字段顺序很有讲究,我习惯先转成带表头的解析,避免后面写着写着忘了列含义。
VisDrone-MOT的gt.txt每一行是逗号分隔的10个字段,按顺序分别是:
| 字段序号 | 字段名 | 说明 |
|---|---|---|
| 1 | frame_id | 帧序号,一般从1开始 |
| 2 | target_id | 目标ID,跨帧保持一致,-1表示忽略目标 |
| 3 | bbox_left | 目标框左上角x坐标,像素单位 |
| 4 | bbox_top | 目标框左上角y坐标,像素单位 |
| 5 | bbox_width | 目标框宽度 |
| 6 | bbox_height | 目标框高度 |
| 7 | score | 置信度,人工标注时通常接近1 |
| 8 | category | 类别ID |
| 9 | truncation | 截断程度,0表示完整可见,越大截断越严重 |
| 10 | occlusion | 遮挡程度,数值越大遮挡越严重 |
类别方面常见的是以下这些:行人和各类车辆是主体,另外还有自行车的子类别和三轮车等特定车型。需要注意“忽略区域”不是训练目标,但跟踪评测时如果目标与忽略区域重合,要根据官方指导意见决定是否过滤;大多数离线测试平台会把它过滤掉,避免污染IDF1的计算。类别ID在不同下载渠道的标注文件里偶尔有编号差异,所以解析脚本一定要写配置化的映射表,不能直接写死数字。
2.3 离线评测前先归一化:把VisDrone标注转成统一JSON
不同的跟踪器对输入格式要求不同,但比较平台的思路是先做一层归一化:把VisDrone的gt.txt解析成统一的检测结果JSON,坐标系统一为像素坐标,frame_id统一从0开始连续编号。这样后面无论接SORT还是接ByteTrack,都只需要一个适配器,不需要每个跟踪器单独写一套数据加载逻辑。
import json from pathlib import Path # 以官方常见定义为准,实际以你下载的gt.txt为准 CAT_ID = { "pedestrian": 1, "people": 2, "bicycle": 3, "car": 4, "van": 5, "truck": 6, "tricycle": 7, "awning_tricycle": 8, "bus": 9, "motor": 10, } def parse_visdrone_mot(gt_path: Path, include_categories=None, min_score: float = 0.0, start_frame: int = 0): """ 解析 VisDrone-MOT 的 gt.txt,输出统一格式: [{frame_id, target_id, bbox, score, category}] bbox: [x1, y1, w, h],左上角坐标 + 宽高,像素单位 """ records = [] with open(gt_path, "r", encoding="utf-8") as fp: for line in fp: parts = line.strip().split(",") if len(parts) < 8: continue frame_id, target_id = int(parts[0]), int(parts[1]) x, y, w, h = [float(v) for v in parts[2:6]] score = float(parts[6]) category = int(parts[7]) # 过滤无效框与忽略区域 if target_id < 0: continue if w <= 1 or h <= 1 or x < 0 or y < 0: continue if include_categories is not None and category not in include_categories: continue if score < min_score: continue records.append({ "frame_id": frame_id - start_frame, "target_id": target_id, "bbox": [x, y, w, h], "score": score, "category": category, }) return records # 使用:常见做法是保留行人和主要车辆类别 include_cats = { CAT_ID["pedestrian"], CAT_ID["people"], CAT_ID["car"], CAT_ID["van"], CAT_ID["bus"], CAT_ID["motor"], } det_gt = parse_visdrone_mot( gt_path=Path("VisDrone2019-MOT-train/seq1/gt/gt.txt"), include_categories=include_cats, start_frame=1, ) with open("seq1_det.json", "w", encoding="utf-8") as fp: json.dump(det_gt, fp, indent=2, ensure_ascii=False)这段代码主要做了三件事:读取gt.txt并解析字段、过滤掉无效框与忽略区域、按类别白名单过滤。target_id小于0的行表示被标注为忽略的目标,直接跳过;宽度或高度小于1像素的框大多是对齐误差,留着会让后面的IoU计算失真。
include_categories传的是VisDrone的类别ID集合,我按常见的做法把行人、普通车、货车、公交车、摩托车保留下来,三轮车这类类别边界模糊的对象是否保留取决于项目需求,但要在实验记录里注明,否则换个人复现时对不上口径。start_frame设为1是因为gt.txt的frame_id从1开始,而后面YOLOv7推理输出建议从0开始,两边统一后才能保证检测缓存和真值在评估阶段逐帧对齐。
3. 锁定检测变量:用YOLOv7离线推理缓存全部帧结果
3.1 为什么必须先跑YOLOv7而不是边测边跑
要理解这一步,先看多目标跟踪的输入是什么:几乎所有跟踪器不直接吃RGB图,吃的是检测器输出的边界框和置信度,唯一例外是DeepSORT、Bot-SORT会额外吃目标的外观特征——而外观特征也是以检测框为中心裁剪出来的。所以检测器一旦换,整个跟踪对比就失去意义。这个平台把YOLOv7当作唯一上游,对VisDrone2019所有视频序列做一次离线推理,把每一帧的检测结果写成文件缓存;后面每个跟踪器的每次实验都读同一份缓存,跑再快也不怕“检测器变了”。
理解YOLOv7原理的人应该也认可这个结论:YOLOv7的输出分布对输入分辨率极其敏感,同一个权重,用640推理和用原始尺寸推理,小目标检测数量可能差一个量级。部署时我们可以为了速度降到640,甚至量化到TensorRT,但离线评测的目的是看算法上限,我一般会把推理分辨率设到1280,甚至直接按原图尺寸,取决于显存容量。热词里常有人讨论yolov7部署,强调的是推理速度和设备适配;而离线测试平台要的是确定性和可复现,这两件事的目标不同,参数必须分开,不要拿部署配置去跑评测,也不要把评测配置直接搬到线上。
3.2 一次推理,把检测结果按帧落盘
核心代码思路:用YOLOv7的模型做底层推理,不直接用官方脚本自带的--save-txt,而是自定义一个输出回调,把每帧的结果聚合成统一的文件。我习惯输出成JSON Lines而不是一个大JSON,因为一个视频有几万帧时,大JSON会吃掉大量内存,而JSON Lines可以用一行一帧的方式边写边读,检查进度也方便。
import torch import json import cv2 from pathlib import Path def run_offline_detection(video_dir: Path, weights: str, output_jsonl: Path, conf_thres: float = 0.25, iou_thres: float = 0.45, img_size: int = 1280, device: str = "cuda:0", category_filter: set = None): # 加载 YOLOv7 权重,这里以官方仓库的加载方式为基础 model = torch.load(weights, map_location=device)["model"].float().eval() # VisDrone 帧名通常是连续数字,必须按 int 排序,不能用字符串排序 frame_paths = sorted(video_dir.glob("*.jpg"), key=lambda p: int(p.stem)) with open(output_jsonl, "w", encoding="utf-8") as fp: for fid, frame_path in enumerate(frame_paths): img = cv2.imread(str(frame_path)) # 按 YOLOv7 的推理流程跑,返回 xyxy 像素坐标框 dets = model_predict(model, img, img_size, conf_thres, iou_thres) results = [] for x1, y1, x2, y2, score, cls in dets: if category_filter is not None and int(cls) not in category_filter: continue results.append({ "bbox": [float(x1), float(y1), float(x2 - x1), float(y2 - y1)], # 统一转成 xywh "score": float(score), "category": int(cls), }) fp.write(json.dumps({"frame_id": fid, "dets": results}) + "\n") return output_jsonl这段代码把检测框统一成与前面gt.txt一致的xywh格式,这样评估模块可以共用同一套IoU计算逻辑。关键参数有三个:conf_thres在离线评测里通常比部署低,因为跟踪器自己会用卡尔曼滤波和级联匹配去纠错,检测端宁可多出候选也别漏检;iou_thres控制NMS的重叠容忍度,无人机视角下目标密集,设太大容易把紧挨着的行人和电动车合并成一个框,我一般从0.45起调;img_size按前面说的设1280。device建议固定为cuda:0,多卡训练跑的模型就选相应设备。
另一个经验:全程关闭half精度。FP16在桌面显卡上有时会丢掉小目标响应,这种“玄学差异”在对比实验里非常难解释,离线平台里直接不用,统一跑FP32。如果视频量大,可以按视频目录拆成多个进程并行推理,每个视频单独落盘,互不依赖,完成后合并索引即可。
3.3 检测缓存自检与权重微调:重跑之前先验货
检测缓存是整个比较系统的基础设施,建议在正式评测前写一个自检脚本:统计每个视频序列的帧数、总检测框数、每帧平均框数、分数分布,并和gt.txt按帧做一次粗略IoU比对。如果平均每帧检测框数量远低于真值框数量,先不要急着跑跟踪器,回去查推理分辨率或置信度阈值。这一步能在10分钟内发现八成问题,省下后面几小时的无效实验。
如果手上有VisDrone2019检测标注或你自己标注的数据,先用YOLOv7微调一轮再离线推理,结果会更贴近真实项目上限。微调时注意保持类别映射与gt.txt一致——用YOLOv7的data配置时要逐行检查yaml里的类别名和索引,字符串和整型ID不能错位。微调后的权重文件单独存路径,推理脚本的weights参数改成微调权重即可,缓存结构完全不用变。这个设计把检测器更换限制在一个参数开关里,整条流水线的其余代码都不受影响。
4. 统一裁判接口:在同一份检测结果上驱动SORT、DeepSORT、ByteTrack与Bot-SORT
4.1 从卡尔曼滤波到相机运动补偿:四种算法的本质差异
先把四种算法的定位说清楚,否则后面适配器代码会看不懂。
SORT是纯运动模型,用卡尔曼滤波预测目标位置,然后用匈牙利算法把检测框与预测框按IoU做匹配。它不管目标长什么样,只看位置运动是否连续。在VisDrone这种目标密集且频繁遮挡的场景里,SORT的问题很明显——目标一被遮挡就断轨,重新出现时大概率开新ID。它是整个平台的基线,也是理解其他算法的基础。
DeepSORT在SORT上加了外观特征:为每个目标保留一个特征描述子,匹配时既要位置接近,又要长得像,还引入了级联匹配优先处理最近确认的轨迹。网上大量“deepsort改进”方案本质都是在改ReID特征提取网络或匹配代价,但要提醒一句:MOT17上训练的ReID模型搬到无人机视角下,行人和车辆的外观区分度反而下降,DeepSORT不见得在VisDrone上赢SORT,这一点第5章会展开讲。
ByteTrack的做法是用“BYTE”策略:高置信度检测框做第一轮匹配,低置信度检测框做第二轮匹配,把SORT里被直接丢弃的框重新利用起来。这对遮挡场景是有效补偿,尤其适合无人机视角下大量目标被短暂遮挡后又出现的场景。Bot-SORT在ByteTrack的基础上加相机运动补偿和更强的外观特征,是目前社区里公认效果较强的方案之一。四种算法能力递增,计算量和参数数量也递增;比较平台的意义正在于用同一份检测结果看这个递增是否真的值得。
| 算法 | 关联依据 | 是否依赖ReID | 低分框策略 | 相对计算量 |
|---|---|---|---|---|
| SORT | 卡尔曼滤波 + IoU 匈牙利匹配 | 否 | 直接丢弃 | 低 |
| DeepSORT | 卡尔曼滤波 + IoU + 外观特征级联匹配 | 是 | 高分帧匹配后丢弃 | 中 |
| ByteTrack | 卡尔曼滤波 + IoU 两阶段匹配 | 否 | 低分框二次匹配 | 中低 |
| Bot-SORT | 卡尔曼滤波 + 相机运动补偿 + 外观特征 | 可切换 | 低分框二次匹配 | 高 |
4.2 用Tracker基类包装四个实现
比较平台的公共代码长这样:一个抽象基类定义update()方法,不同跟踪器作为不同实现。为什么不能直接拿SORT和DeepSORT各自的类来用?因为接口不统一:SORT接收[x1,y1,x2,y2,score]矩阵,DeepSORT接收带外观特征的列表,ByteTrack接收两个层次的检测输出,Bot-SORT还要相机运动参数。如果比较脚本里到处是if-else分支,新加一个跟踪器就要改一堆代码。
from abc import ABC, abstractmethod import numpy as np class MOTTracker(ABC): """统一多目标跟踪器接口。 dets 结构: np.ndarray,每行 [x1, y1, x2, y2, score, category] 返回值: list of dict {track_id, bbox: [x1, y1, x2, y2], category} """ @abstractmethod def update(self, frame_id: int, dets: np.ndarray, **kwargs): pass class SortTracker(MOTTracker): def __init__(self, max_age=15, min_hits=1, iou_threshold=0.3): # 内部实例化 SORT 的 Sort(max_age, min_hits, iou_threshold) pass def update(self, frame_id, dets, **kwargs): # 把 dets 转换为 SORT.update 需要的 [x1, y1, x2, y2, score] return self.tracker.update(dets) class ByteTrackTracker(MOTTracker): def __init__(self, track_thresh=0.45, match_thresh=0.8, frame_rate=30): pass def update(self, frame_id, dets, **kwargs): # 按 BYTE 策略内部再分高分/低分两段匹配 return self.tracker.update(dets, frame_id, frame_id)这里的关键在参数:max_age控制轨迹在被遗忘前保留多少帧。VisDrone里目标被遮挡2秒内出来很常见,15到30帧是两种常见取值;ByteTrack的frame_rate与视频实际帧率必须一致,否则卡尔曼滤波的“运动惯性”和时间步对不上——这是很多人在离线评测时指标忽高忽低的隐形开关。Bot-SORT如果开启相机运动补偿,则需要传入视频帧或帧间隙的特征点结果,接口里的**kwargs就是给这类参数留的口子,不影响其他跟踪器的调用。
4.3 四种跟踪器关键参数对照与推荐起点
把这四个算法放在同一份检测缓存上跑之前,参数要先初始化。我的经验是不要直接用论文默认值,因为检测器尺度、视频帧率、目标密度全变了。下表是从VisDrone2019场景出发的推荐起点,只能作为基线,不能当成最终调参结果:
| 算法 | 关键参数 | 推荐起点 | 说明 |
|---|---|---|---|
| SORT | max_age / min_hits / iou | 15 / 1 / 0.3 | 无人机遮挡频繁,min_hits=3会把刚出现的短轨迹全丢 |
| DeepSORT | max_age / 特征融合权重 | 30 / 0.3 | 权重太小退化成SORT,太大又会在同类目标间错配ID |
| ByteTrack | track_thresh / match_thresh | 0.45 / 0.8 | 低分档阈值要与YOLOv7置信度分布对齐 |
| Bot-SORT | TrackThresh / match_thresh / CMC | 0.4 / 0.8 / 启用 | CMC需要连续帧内容,离线推理缓存要保留图像 |
做对比实验时,先把所有跟踪器在训练集上各跑一遍,记录各参数对MOTA和HOTA的敏感性,再统一固定参数进入验证集。否则你比较的不是算法能力,而是“谁的默认参数恰好碰到这个数据集的好运”。这一条我把它放在每个跟踪器的README注释里,提醒后面接手的人。
5. 离线测试避坑实录:VisDrone2019上的五处常见翻车现场
下面这5条都是我在“检测缓存 + 多跟踪器对比”模式里实际摔过的跟头。每一条按“现象→原因→解决”的方式写,基本可以避开最消耗时间的排查阶段。
5.1 ID Switch高得离谱:查了半天不是跟踪器菜,是检测阈值设错了
现象:同一份YOLOv7检测缓存,SORT的IDSW在VisDrone验证集上高到每秒振荡,视频里同一个行人被分成七八个ID。
原因:一开始把conf_thres设成0.5,认为丢掉低分框能提升精度。结果是无人机视角下很多行人在画面里只有二三十个像素,YOLOv7输出的置信度天然落在0.2到0.4区间,0.5以上的框只剩大车和近处的人。检测器漏检之后,SORT的卡尔曼预测框无处匹配,只能频繁开新ID,IDSW自然爆炸。
解决:把离线缓存的conf_thres降回0.25,配合iou_thres 0.45重跑一次YOLOv7推理,SORT的max_age从15调到30,让漏检期间轨迹不立即销毁。教训是在离线测试里,检测缓存是跟踪器的上帝,检测器阈值一改,所有指标都要推倒重来。重跑前先看一眼缓存里每帧平均检测框数量,心里有个底再继续。
5.2 离线指标与在线可视化结果对不上:frame_id和列表顺序三处错位
现象:离线跑出来的MOTA不错,但把跟踪结果渲染成视频看,发现框在某个目标上停留时间与逻辑不符,甚至出现检测框在时间轴上回跳。
原因:VisDrone的图片文件名是连续数字,但偶尔有空帧或启动帧编号不从1开始。按字符串排序时,frame_id会错位;更隐蔽的是输出跟踪结果时如果直接按列表下标写入评估文件,而评估模块按frame_id去重,就会出现“同一个frame_id拼了两帧的结果”。
解决:从生成检测缓存那一刻就强制frame_id等于“帧索引”而不是“文件名数字”。推理脚本里用enumerate(frame_paths)生成frame_id,与解析gt.txt里的frame_id - start_frame保持一致;评估前跑一个断言校验:读缓存JSONL,检查frame_id单调递增且步长等于1。这个自动化检查不花一分钟,但能拦住最无语的错位。
5.3 类别文件与VisDrone标注没对齐:行人和车辆集体互换
现象:YOLOv7微调后推理,跟踪结果里行人框特别少,但所有车辆框正常;细看发现大量行人框被标成了car。
原因:VisDrone的类别从1开始编号,而训练时类别列表从0开始,序号差1。如果直接加载COCO预训练权重推理,模型对类别的理解停留在COCO,类别ID映射根本对不上——YOLOv7输出的cls索引和VisDrone类别定义是两套字典。
解决:在数据准备阶段的parse函数和检测推理阶段同时维护一张映射表,用JSON文件把“VisDrone类别ID”和“YOLOv7训练时的类别ID”显式对应起来,代码里禁止用if cls == 1这种裸数字。映射错了改一行配置重新生成缓存即可。类别映射必须随权重一起记录归档,否则别人复现你的实验时,第一个翻车点就在这里。
5.4 DeepSORT在无人机场景下被SORT反超:ReID模型域迁移失败
现象:在MOT17上DeepSORT比SORT明显好,但在VisDrone2019验证集上MOTA几乎持平,IDF1甚至更低。
原因:DeepSORT的外观特征模型是用MOT17行人数据集训练的,VisDrone的俯视视角下行人姿态和尺度分布不同,特征提取器给每个框输出的描述子区分度变差。此时匹配主要靠运动模型,而DeepSORT的级联匹配和特征提取还会增加额外开销,反而不如干净利落的纯运动模型SORT。
解决:要么换成更强的ReID主干并在VisDrone视角数据上微调,要么在对比实验时给DeepSORT关掉特征分支只看运动匹配,让它退化成“加了卡尔曼滤波的SORT”。这提醒我们:多目标跟踪算法比较的前提是算法适配场景,否则结论会失真。输出论文或项目报告时,把“ReID训练数据与评测数据不匹配”这条写在限制条件里,比只放一张指标表更有说服力。
5.5 同一条检测缓存跑两遍,指标却差两个点
现象:用固定的检测缓存、固定跟踪器参数,连续跑两次评估输出,MOTA和IDF1的个位到十位出现抖动。
原因:很多跟踪器内部带了随机初始化或未固定的随机种子,评估脚本如果对dict键迭代而不排序,在某个帧集合上关联矩阵的边角情况会被不同的哈希顺序改变计算结果。
解决:在评估脚本开头固定random.seed(42)、numpy.random.seed(42),如有torch也一并设置;评估工具接收的输入统一按frame_id排序后再送入。实验记录里写入配置快照,避免跟踪器代码在两轮实验之间悄悄更新。固定种子之后再跑两遍,指标应当完全一致;如果仍抖动,检查多线程或GPU算子的非确定性,最坏情况下把评估阶段锁到CPU单线程,牺牲一点速度换绝对可复现。
6. 用HOTA和可视化验证每一次对比结论
把四种跟踪器跑完后,最忌讳的是只看一张MOTA表格就下结论。MOTA把检测误差和关联误差混在一起,对ID切换的惩罚也偏简单;在VisDrone这么密集的场景里,我建议把HOTA作为主指标,同时看IDF1和AssA。HOTA把检测精度和关联精度分开,能回答“跟踪器到底哪里强”:ByteTrack分数高,通常是AssA贡献,因为低分框策略救回了遮挡目标;DeepSORT分数低,要看是检测精度不够还是关联精度太差,前者是检测缓存的问题,后者才是关联策略的问题。
计算HOTA前先统一匹配口径:匹配距离一般用IoU,无人机小目标建议以0.5 IoU为阈值。如果用中心点距离,几十像素的框稍微偏一点就全算错,指标会被拉得很低。我会在验证时同时算两步:第一步用py-motmetrics风格的代码算出MOTA、IDF1,第二步用TrackEval风格的代码算出HOTA,两边交叉验证。
除了数字,还要把跟踪结果渲染成视频肉眼过一遍。建议把track_id直接绘制在框上方,轨迹线画在目标中心点上,用半透明的方式画出最近20帧历史。我习惯随机抽3个视频序列,取中间600帧渲染成视频,重点看目标被遮挡后重新出现时,ID是否恢复原编号;如果恢复了说明跟踪器做了轨迹等待重连,如果新开ID说明门控参数太紧。这一步肉眼验证比任何指标都诚实。
最后,我这些年的习惯是:检测缓存文件、YOLOv7权重和跟踪器参数配置一起打tar归档。检测器升级或跟踪器版本变更后,重跑对比只需要换跟踪器,最耗时的推理阶段不再重复。这也是离线测试平台相对在线评测最大的价值——所有结论都能在几分钟内复现,任何指标异常都能从缓存里回头追查。希望帮到你。
本文还有配套的精品资源,点击获取