news 2026/9/15 18:29:58

YOLOv10 多目标追踪注册机制源码解析:`trackers/track.py` 中的回调管线与追踪器初始化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv10 多目标追踪注册机制源码解析:`trackers/track.py` 中的回调管线与追踪器初始化

YOLOv10 多目标追踪注册机制源码解析:trackers/track.py中的回调管线与追踪器初始化

【免费下载链接】yolov10YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/yo/yolov10

本篇文章聚焦 YOLOv10 仓库中多目标追踪的核心接线模块 ultralytics/trackers/track.py,深入拆解on_predict_starton_predict_postprocess_endregister_tracker三个函数如何把 ByteTrack / BoT-SORT 追踪器无缝接入预测流水线。读完本文,你将掌握追踪器何时被创建、检测结果如何被追踪结果替换、追踪 ID 如何跨帧保持,以及如何通过配置文件自定义追踪行为。

一、模块定位:追踪功能在预测流水线中的接线层

在 YOLOv10 中,多目标追踪(MOT)并非独立于推理之外的另一套流程,而是以回调(callback)方式挂载在标准预测管线之上track.py就是这一接线层的实现:它不自己实现卡尔曼滤波或数据关联,而是负责在合适的时机创建 ByteTrack / BoT-SORT 实例、把每帧检测结果喂给追踪器、再把带 ID 的追踪结果写回Results对象。

从源码结构看,追踪链路涉及以下文件:

  • ultralytics/trackers/track.py:回调定义与追踪器注册(本文主角)
  • ultralytics/trackers/byte_tracker.py:BYTETrackerSTrack实现
  • ultralytics/trackers/bot_sort.py:BOTSORTBOTrack实现
  • ultralytics/trackers/init.py:对外导出register_tracker
  • ultralytics/cfg/trackers/botsort.yaml 与 ultralytics/cfg/trackers/bytetrack.yaml:追踪器参数配置

track.py只暴露 3 个公开函数,模块结构非常精简:

函数职责
on_predict_start(predictor, persist)预测开始前初始化追踪器实例
on_predict_postprocess_end(predictor, persist)每帧后处理结束后执行追踪更新
register_tracker(model, persist)把前两个函数注册为模型回调

二、TRACKER_MAP:追踪器类型注册表

track.py顶部定义了一张将配置名映射到追踪器类的字典:

from .bot_sort import BOTSORT from .byte_tracker import BYTETracker # A mapping of tracker types to corresponding tracker classes TRACKER_MAP = {"bytetrack": BYTETracker, "botsort": BOTSORT}

这张表决定了tracker_type配置项可用的取值范围。从 ultralytics/trackers/byte_tracker.py 与 ultralytics/trackers/bot_sort.py 的类定义可以看到两者差异:

  • BYTETracker:基于卡尔曼滤波(KalmanFilterXYAH,即中心点 x/y + 宽高比 + 高度)的两阶段关联算法,通过track_high_thresh/track_low_thresh区分高、低置信度检测框,低置信度框用于二次关联找回遮挡目标;
  • BOTSORT:继承自BYTETracker的扩展版本,改用KalmanFilterXYWH,额外引入 GMC(Global Motion Compensation,全局运动补偿,默认sparseOptFlow)与可选的 ReID 外观特征(with_reid),在相机运动场景下更鲁棒。

__init__.py中还对外导出了BOTSORTBYTETracker,允许开发者直接 import 这两个追踪器类做更底层的调用。

三、on_predict_start:预测开始时初始化追踪器

def on_predict_start(predictor: object, persist: bool = False) -> None: if hasattr(predictor, "trackers") and persist: return tracker = check_yaml(predictor.args.tracker) cfg = IterableSimpleNamespace(**yaml_load(tracker)) if cfg.tracker_type not in ["bytetrack", "botsort"]: raise AssertionError(f"Only 'bytetrack' and 'botsort' are supported for now, but got '{cfg.tracker_type}'") trackers = [] for _ in range(predictor.dataset.bs): tracker = TRACKER_MAPcfg.tracker_type trackers.append(tracker) if predictor.dataset.mode != "stream": # only need one tracker for other modes. break predictor.trackers = trackers predictor.vid_path = [None] * predictor.dataset.bs # for determining when to reset tracker on new video

该函数执行三个关键动作:

  1. 幂等检查:若persist=True且 predictor 已有trackers属性,直接返回,避免重复创建追踪器;
  2. 配置装载:通过check_yaml解析predictor.args.tracker(默认botsort.yaml,见 ultralytics/cfg/default.yaml),再经yaml_load载入并用IterableSimpleNamespace包装,使cfg.tracker_type等字段可以点号访问。如果tracker_type不是bytetrackbotsort,会抛出AssertionError
  3. 按 batch 大小创建追踪器for _ in range(predictor.dataset.bs)循环按批次大小实例化追踪器,frame_rate固定传 30。stream 模式的特殊处理是这里的重点——只有在流式输入(dataset.mode == "stream")下才为 batch 中每个视频各建一个追踪器,其余模式只创建 1 个。

初始化完成后,函数还在 predictor 上挂载vid_path = [None] * bs,用于后续判断是否切换到了新视频文件,从而决定何时重置追踪器状态。

四、on_predict_postprocess_end:每帧后处理结束时的追踪更新

def on_predict_postprocess_end(predictor: object, persist: bool = False) -> None: path, im0s = predictor.batch[:2] is_obb = predictor.args.task == "obb" is_stream = predictor.dataset.mode == "stream" for i in range(len(im0s)): tracker = predictor.trackers[i if is_stream else 0] vid_path = predictor.save_dir / Path(path[i]).name if not persist and predictor.vid_path[i if is_stream else 0] != vid_path: tracker.reset() predictor.vid_path[i if is_stream else 0] = vid_path det = (predictor.results[i].obb if is_obb else predictor.results[i].boxes).cpu().numpy() if len(det) == 0: continue tracks = tracker.update(det, im0s[i]) if len(tracks) == 0: continue idx = tracks[:, -1].astype(int) predictor.results[i] = predictor.results[i][idx] update_args = dict() update_args["obb" if is_obb else "boxes"] = torch.as_tensor(tracks[:, :-1]) predictor.results[i].update(**update_args)

这是追踪管线的核心数据通路,逻辑可拆解为五步:

  1. 视频切换重置vid_path用于检测当前帧是否属于新视频。当persist=False且视频路径发生变化时,调用tracker.reset()清空上一视频的追踪状态(reset()方法在 ultralytics/trackers/byte_tracker.py 中定义,会重置tracked_strackslost_stracksframe_id与卡尔曼滤波器);
  2. 提取检测结果:根据任务类型(obb或普通检测)取出results[i].obbresults[i].boxes转为 NumPy 数组。这意味着追踪不仅支持 detect 任务,也支持旋转框 OBB 任务;
  3. 调用追踪器tracker.update(det, im0s[i])把原始图像与检测框交给 ByteTrack / BoT-SORT,内部完成卡尔曼预测、IoU 关联、轨迹状态迁移,返回形如[x1, y1, x2, y2, id, conf, cls, idx](OBB 场景为[x, y, w, h, angle, id, conf, cls, idx])的轨迹数组;
  4. 按追踪索引重排结果tracks[:, -1]是检测索引,predictor.results[i][idx]把原始检测结果按被追踪保留的检测框重新索引,保证后续可视化时框与轨迹一一对应;
  5. 写回追踪坐标:去掉索引列后,把带 ID 的轨迹通过results.update(...)(定义于 ultralytics/engine/results.py)替换原有 boxes/OBB 数据。之后用户即可通过results[i].boxes.id读取每个目标的追踪 ID。

五、register_tracker:回调注册的入口

def register_tracker(model: object, persist: bool) -> None: model.add_callback("on_predict_start", partial(on_predict_start, persist=persist)) model.add_callback("on_predict_postprocess_end", partial(on_predict_postprocess_end, persist=persist))

函数本身极简,但意义重大:它用functools.partialpersist参数固化进两个回调函数,再注册到模型的两个预测阶段钩子上。实际触发点在 ultralytics/engine/predictor.py:

  • 第 237 行:self.run_callbacks("on_predict_start")—— 数据遍历开始前执行;
  • 第 256 行:self.run_callbacks("on_predict_postprocess_end")—— 每帧postprocess完成之后执行。

因此整个追踪生命周期可以概括为:

model.track(source, persist=...) └─> model.track() 首次调用时检测到 predictor 无 trackers └─> register_tracker(self, persist) 注册两个回调 └─> predictor 进入预测循环 ├─> on_predict_start → 解析 tracker yaml,实例化追踪器 └─> 每帧: inference → postprocess └─> on_predict_postprocess_end → tracker.update() → 写回结果

六、persist参数与model.track()的关联

persist是理解追踪行为的关键开关,它有两个作用点:

  • on_predict_start中避免重复初始化(hasattr(predictor, "trackers") and persist);
  • on_predict_postprocess_end中决定是否在新视频上重置追踪器。

入口位于 ultralytics/engine/model.py 的track()方法:

if not hasattr(self.predictor, "trackers"): from ultralytics.trackers import register_tracker register_tracker(self, persist) kwargs["conf"] = kwargs.get("conf") or 0.1 # ByteTrack-based method needs low confidence predictions as input kwargs["batch"] = kwargs.get("batch") or 1 # batch-size 1 for tracking in videos kwargs["mode"] = "track" return self.predict(source=source, stream=stream, **kwargs)

值得注意的是track()还强制了两条追踪前置条件:置信度阈值默认降为 0.1(ByteTrack 系算法依赖低置信度框做二次关联,太高会丢失遮挡目标的轨迹),以及batch 强制为 1(视频追踪按帧顺序处理,且on_predict_start中按dataset.bs创建追踪器)。

典型用法:

from ultralytics import YOLO model = YOLO("yolov8n.pt") # 视频文件:非 persist 场景,切换视频自动重置追踪器 results = model.track(source="video.mp4", show=True) # 逐帧循环:persist=True 告诉追踪器当前帧是上一帧的延续 cap = cv2.VideoCapture("video.mp4") while cap.isOpened(): success, frame = cap.read() if success: results = model.track(frame, persist=True) annotated = results[0].plot()

CLI 等价命令为:

yolo track model=yolov8n.pt source=video.mp4 yolo track model=yolov8n.pt source=video.mp4 tracker=bytetrack.yaml conf=0.3

七、追踪器配置参数详解

tracker_type决定实例化哪个类,其余参数在on_predict_start中被装载进cfg并传给追踪器构造函数。仓库内置两份配置文件:

ultralytics/cfg/trackers/bytetrack.yaml:

tracker_type: bytetrack # tracker type, ['botsort', 'bytetrack'] track_high_thresh: 0.5 # threshold for the first association track_low_thresh: 0.1 # threshold for the second association new_track_thresh: 0.6 # threshold for init new track if the detection does not match any tracks track_buffer: 30 # buffer to calculate the time when to remove tracks match_thresh: 0.8 # threshold for matching tracks

ultralytics/cfg/trackers/botsort.yaml 在此基础上追加 BoT-SORT 专有项:

tracker_type: botsort track_high_thresh: 0.5 track_low_thresh: 0.1 new_track_thresh: 0.6 track_buffer: 30 match_thresh: 0.8 # BoT-SORT settings gmc_method: sparseOptFlow # method of global motion compensation proximity_thresh: 0.5 appearance_thresh: 0.25 with_reid: False

各参数在源码中的实际作用:

参数默认值源码消费位置含义
track_high_thresh0.5byte_tracker.update()第一轮关联的高分检测框阈值
track_low_thresh0.1byte_tracker.update()第二轮关联的低分检测框阈值
new_track_thresh0.6byte_tracker.update()未匹配检测框新建轨迹的最低分数
track_buffer30BYTETracker.__init__int(frame_rate/30*args.track_buffer)换算为max_time_lost,超过该帧数未匹配的 lost 轨迹将被移除
match_thresh0.8两轮linear_assignment关联距离阈值
gmc_methodsparseOptFlowBOTSORT.__init__GMC(method=...)全局运动补偿方法(BoT-SORT)
proximity_thresh0.5BOTSORT.get_dists空间邻近度阈值(IoU)
appearance_thresh0.25BOTSORT.get_distsReID 外观相似度阈值
with_reidFalseBOTSORT.__init__/get_dists是否启用 ReID 外观特征(当前encoder仍为 None,即尚未实际启用)

自定义追踪器只需复制上述任一 yaml 并修改参数(tracker_type除外),通过tracker=custom_tracker.yaml传入即可。

八、底层原理:BYTETracker 的一轮更新

tracker.update(det, im0s[i])内部(ultralytics/trackers/byte_tracker.py 的BYTETracker.update)执行的正是 ByteTrack 论文中的经典五步流程:

  1. track_high_thresh划分高置信度检测框,按[low, high)区间收集低置信度检测框;
  2. 第一轮关联:联合tracked_strackslost_stracks,用共享卡尔曼滤波器multi_predict预测新位置,以 IoU + 分数融合距离(fuse_score)做线性分配;
  3. 第二轮关联:对未匹配轨迹用低置信度检测框(iou_distance+ 阈值 0.5)二次匹配,找回被遮挡的物体;
  4. 新轨迹激活:仍未匹配且分数达new_track_thresh的检测框,通过track.activate(kalman_filter, frame_id)建立新轨迹(STrack.activate会分配新的全局track_id);
  5. 状态清理:超过max_time_lost的 lost 轨迹被mark_removed,重复轨迹按 IoU 距离与存活时长去重,removed_stracks列表裁剪至 1000 以内。

BoT-SORT 的差异在于:卡尔曼状态量是xywh而非xyahupdate前先用 GMC 估计单应矩阵并通过STrack.multi_gmc校正全局运动,get_dists中在 IoU 基础上叠加(尚未启用的)ReID 外观距离。

九、验证与测试

仓库测试 tests/test_engine.py 覆盖了回调机制本身:通过pred.add_callback("on_predict_start", test_func)注册自定义回调,再断言test_func in pred.callbacks["on_predict_start"],验证了run_callbacks/add_callback框架的正确性——register_tracker使用的正是同一套回调 API。这从侧面印证了track.py三个函数的定位:它们不是特殊逻辑,而是标准预测回调机制的两个挂载点加上一个注册入口。

十、小结

ultralytics/trackers/track.py是 YOLOv10 多目标追踪的"神经中枢":

  • register_tracker用两行代码把追踪能力注入模型预测管线;
  • on_predict_start按 batch 与 stream 模式合理实例化追踪器,并强制校验tracker_type
  • on_predict_postprocess_end完成"检测 → 关联 → 写回"的每帧闭环,支持 detect 与 OBB 两类任务;
  • persistvid_path共同管理跨视频、跨帧的追踪状态生命周期。

理解这个模块后,无论是更换追踪算法、自定义阈值参数,还是基于回调机制扩展新的追踪策略,你都能在源码层面找到清晰的落点。

【免费下载链接】yolov10YOLOv10: Real-Time End-to-End Object Detection [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/yo/yolov10

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 18:26:27

如何用 PDFPatcher 无损提取 PDF 图片并自定义输出文件命名掩码?

如何用 PDFPatcher 无损提取 PDF 图片并自定义输出文件命名掩码? 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱,可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档,探查文档结构,提取图片、转成图片等等 项目地址: …

作者头像 李华
网站建设 2026/9/15 18:25:46

Matlab频谱与Bode图绘制:从FFT到频响验证的完整指南

简介:面向信号处理、通信工程与控制系统领域的初学者及工程师,提供一套实用的MATLAB频谱分析与Bode图绘制解决方案,专门解决两大高频需求:一是利用pwelch函数完成功率谱密度估计,涵盖数据读取、滤波去噪、窗函数选择与…

作者头像 李华