简介本资源面向计算机视觉方向的学习者与开发者提供一套基于YOLOv9与DeepSort融合的目标跟踪算法Python实现可用于行人、车辆等目标的实时检测与多目标轨迹跟踪场景适合具备一定深度学习基础、希望快速上手跟踪项目的中高级读者。压缩包共8个文件约16.85MB包含Python主程序、Jupyter Notebook实验脚本、YAML与conda环境配置、coco.names类别文件、README说明文档及演示动图等覆盖从环境搭建到推理运行的完整环节。目前已有595人学习下载。读者可借助其中的检测与跟踪代码、配置文件及示例数据理解YOLOv9检测结果如何输入DeepSort完成ID关联与轨迹维护并参考Notebook快速复现实验、调整参数或迁移到自有数据集为二次开发与算法对比提供可运行的基础框架。1. 拆开这份 YOLOv9DeepSort 跟踪源码它到底能跑出什么结果如果你手头有一段固定机位的监控视频想让它自动标出每个行人并分配一个稳定 ID那这份 YOLOv9DeepSort 的 Python 源码包就是冲这个场景来的。检测端用 YOLOv9 出框跟踪端用 DeepSort 做 ID 关联两者串起来就是一条完整的多目标跟踪流水线。它适合两类人一类是刚学完 Python 基础语法、想找一个能跑通的目标跟踪算法项目练手的新手另一类是做安防、零售客流、交通统计的从业者需要一个能直接改、能换模型、能接自己视频的基线工程。源码包通常包含检测推理脚本、DeepSort 跟踪器实现、权重加载逻辑和一段示例视频入口拿到手不用从零搭架构改几个路径就能出带 ID 的标注视频。这一章先把它的能力边界说清楚后面再拆怎么装环境、怎么调参、怎么避坑。2. 环境搭建与依赖安装从 python 安装到 vscode python 环境配置2.1 为什么这套源码对 Python 版本和 CUDA 这么挑YOLOv9 的推理依赖 PyTorch而 PyTorch 的 GPU 版本又和 CUDA 驱动强绑定这是整条链路里最容易翻车的一环。源码里如果用的是.pt权重那推理时走的是 PyTorch 原生加载不需要额外编译但如果权重是.engine格式就必须经过 TensorRT 转换这时候 CUDA、cuDNN、TensorRT 三个版本必须对齐错一个就报serialization或invalid device function。我一般建议新手先用 CPU 或单卡 GPU 把流程跑通确认检测框和 ID 都正常再去折腾 TensorRT 加速。Python 版本选 3.8 到 3.10 之间最稳3.11 以上有些旧版torchvision轮子还没跟上装的时候会卡在编译阶段。DeepSort 部分依赖numpy、scipy、opencv-python和filterpy其中filterpy负责卡尔曼滤波scipy负责匈牙利算法的线性分配。这几个包版本冲突不多但opencv-python和opencv-contrib-python不能同时装否则cv2会指向错误的那一个导致cv2.dnn模块缺失。常见做法是建一个干净的虚拟环境把依赖一次性装齐不要混用系统 Python。2.2 从零建环境命令与参数说明下面这套流程是我在 Linux 和 Windows 上都跑过的顺序不要乱。先建虚拟环境再装 PyTorch最后装跟踪相关依赖。# 创建虚拟环境Python 版本建议 3.9 python -m venv yolov9_deepsort_env # 激活环境Linux/macOS source yolov9_deepsort_env/bin/activate # 激活环境Windows yolov9_deepsort_env\Scripts\activate # 先装 PyTorch以 CUDA 11.8 为例具体版本按自己驱动改 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 装跟踪和图像处理依赖 pip install numpy scipy opencv-python filterpy matplotlib pyyaml tqdm # 如果源码里有 requirements.txt优先用它 pip install -r requirements.txtpython -m venv建出来的环境是隔离的不会污染系统 Python这一点在同时维护多个项目时特别重要。--index-url指定 PyTorch 官方轮子源比默认源快很多也避免装到 CPU 版本。filterpy和scipy是 DeepSort 的硬依赖缺一个都会在tracker.py导入阶段直接报ModuleNotFoundError。装完之后用python -c import torch; print(torch.cuda.is_available())验证 GPU 是否可用返回True再往下走。2.3 vscode 配置 python 环境与调试入口在 vscode 里按CtrlShiftP输入Python: Select Interpreter选中刚才建的yolov9_deepsort_env里的python可执行文件。这一步不做的话终端里跑得通、调试器里却报找不到模块是新手最常见的困惑。然后在.vscode/launch.json里加一个调试配置把入口脚本和参数写进去后面改参数不用每次敲命令行。{ version: 0.2.0, configurations: [ { name: YOLOv9DeepSort Debug, type: python, request: launch, program: ${workspaceFolder}/track.py, console: integratedTerminal, args: [ --source, test_video.mp4, --weights, yolov9-c.pt, --conf, 0.4, --show ] } ] }program指向源码里的主入口不同包可能叫track.py、demo.py或main.py以实际文件名为准。args里--source可以是视频路径也可以是0表示摄像头--conf是检测置信度阈值后面调参章节会细说。console设为integratedTerminal是为了让--show弹出的窗口能正常显示用内部调试控制台有时会卡住画面。3. 检测与跟踪主流程YOLOv9 出框、DeepSort 关联 ID 的完整链路3.1 一帧画面在源码里到底走了哪几步理解主流程比死记参数重要。每一帧进来先经过 YOLOv9 做前向推理输出一批边界框每个框带坐标、置信度和类别。源码里通常会用一个置信度阈值先过滤掉低分框再用 NMS 去掉重叠框。剩下的框送进 DeepSortDeepSort 内部做两件事一是用卡尔曼滤波根据上一帧轨迹预测当前帧每个目标可能出现的位置二是用匈牙利算法把预测框和检测框做匹配。匹配成功的轨迹更新状态并保留原 ID没匹配上的检测框新建轨迹连续多帧没匹配上的轨迹则删除。这个「预测—匹配—更新」的循环就是 DeepSort 的核心。卡尔曼滤波负责运动预测外观特征负责在遮挡时保持 ID 不乱跳。源码里外观特征提取通常用一个小的 ReID 网络如果包里带了ckpt.t7之类的权重文件那就是它。没有这个权重DeepSort 会退化成纯运动匹配遮挡一多 ID 就频繁切换。3.2 主循环代码结构与关键参数下面这段是主流程的骨架不同源码包变量名可能不同但逻辑一致。import cv2 import torch from models.experimental import attempt_load from deep_sort.utils.parser import get_config from deep_sort.deep_sort import DeepSort # 加载 YOLOv9 检测模型 model attempt_load(yolov9-c.pt, map_locationcuda:0) model.eval() # 初始化 DeepSort传入 ReID 权重和配置文件 cfg get_config() cfg.merge_from_file(deep_sort/configs/deep_sort.yaml) deepsort DeepSort( cfg.DEEPSORT.REID_CKPT, max_distcfg.DEEPSORT.MAX_DIST, # 外观匹配最大距离 min_confidencecfg.DEEPSORT.MIN_CONFIDENCE, nms_max_overlapcfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distancecfg.DEEPSORT.MAX_IOU_DISTANCE, max_agecfg.DEEPSORT.MAX_AGE, # 轨迹丢失后保留帧数 n_initcfg.DEEPSORT.N_INIT, # 连续命中多少次才确认轨迹 nn_budgetcfg.DEEPSORT.NN_BUDGET, use_cudaTrue ) cap cv2.VideoCapture(test_video.mp4) while True: ret, frame cap.read() if not ret: break # YOLOv9 推理输出框和置信度 pred model(frame)[0] # 过滤低置信度并做 NMS得到 bbox_xywh、confidences、cls bbox_xywh, confidences, cls post_process(pred, conf_thres0.4, iou_thres0.5) # 送入 DeepSort 更新返回带 track_id 的框 outputs deepsort.update(bbox_xywh, confidences, cls, frame) for x1, y1, x2, y2, track_id in outputs: cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID {track_id}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(track, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()max_dist控制外观特征匹配的宽松程度调大更容易匹配上但可能串 ID调小则容易断轨。max_age是轨迹丢失后还能保留多少帧行人被遮挡两三秒时这个值要适当调大否则遮挡结束会分配新 ID。n_init是轨迹确认门槛设为 3 表示连续三帧匹配上才输出 ID能过滤掉一闪而过的误检。nn_budget限制每个轨迹保存的外观特征数量太大占显存太小外观描述不稳定。这几个参数在deep_sort.yaml里都有默认值先按默认跑出问题再针对性调。3.3 换自己的视频和模型路径与类别过滤把test_video.mp4换成自己的文件注意 OpenCV 对中文路径支持不好路径里不要带中文和空格。如果只想跟踪人可以在后处理里按类别过滤COCO 预训练模型里人的类别 ID 是 0。# 只保留行人过滤其他类别 person_mask cls 0 bbox_xywh bbox_xywh[person_mask] confidences confidences[person_mask] cls cls[person_mask]换模型时YOLOv9 有yolov9-c.pt、yolov9-e.pt等不同规模字母越靠后精度越高、速度越慢。源码里attempt_load的路径改成新权重文件名即可不用改其他代码。如果换成自己训练的权重类别数和类别名要对上否则画出来的标签会错位。4. 避坑与排查ID 跳变、显存溢出、依赖冲突的现场记录4.1 现象同一个人 ID 频繁切换遮挡后变成新 ID原因通常有三个。一是 ReID 权重没加载成功DeepSort 退化成纯 IoU 匹配遮挡一出现就断。二是max_age设得太小轨迹还没等到目标重新出现就被删了。三是检测框抖动太大卡尔曼滤波预测位置和实际检测偏差过大匹配失败。解决办法先确认ckpt.t7路径正确且文件完整再把max_age从默认 30 调到 50 到 70 之间试最后检查检测置信度阈值是不是太低导致框在目标边缘跳来跳去适当提高到 0.5 左右。4.2 现象跑几帧后报 CUDA out of memory原因是显存被检测模型、ReID 模型和视频帧缓存同时占用。YOLOv9 的e版本比c版本吃显存多不少如果显卡只有 6G 或 8G换小模型是最直接的办法。另外nn_budget设太大也会让每个轨迹缓存过多特征向量把它从默认 100 降到 50 能省不少显存。还有一个容易忽略的点torch.no_grad()没加推理时还在建计算图显存会持续增长。在主循环外面套上with torch.no_grad():就能解决。4.3 现象ImportError 或版本不兼容卡在 filterpy 或 scipy原因是虚拟环境没激活或者 pip 装到了系统 Python 里。先which python确认当前解释器路径在虚拟环境目录下再pip list看filterpy和scipy是否都在。如果scipy版本过高导致linear_sum_assignment接口变化降到 1.7 到 1.9 之间通常能解决。opencv-python和opencv-contrib-python同时存在时先pip uninstall掉其中一个只留opencv-python。4.4 现象视频输出正常但窗口不显示或按 q 没反应原因是cv2.imshow和cv2.waitKey的配合问题。waitKey(1)里的数字是毫秒太小会导致窗口无响应改成waitKey(30)给 GUI 留出刷新时间。另外在无显示器的服务器上跑imshow会直接报错这时候把--show去掉改成用cv2.VideoWriter写文件输出。# 无显示器环境下改为写视频文件 fourcc cv2.VideoWriter_fourcc(*mp4v) out cv2.VideoWriter(output.mp4, fourcc, 30, (w, h)) out.write(frame)4.5 现象跟踪结果整体偏移框和人对不上原因是 YOLOv9 输出的坐标格式和 DeepSort 期望的格式不一致。YOLO 系列常见输出是xywh中心点加宽高而 DeepSort 的update有的版本期望xywh有的期望xyxy。看源码里update函数签名和内部第一行怎么处理如果是xyxy就手动转换。这个坑不报错只是结果错位排查时优先核对坐标格式。5. 进阶调优与验证把跟踪效果从「能跑」推到「能用」5.1 用 MOT 指标量化跟踪质量而不是靠肉眼看肉眼看视频只能判断「大概没跳」要真正验证得用 MOT 指标。常见做法是把跟踪结果按 MOTChallenge 格式写成 txt每行是帧号,ID,x,y,w,h,置信度,-1,-1,-1然后用py-motmetrics算 MOTA、IDF1、ID Switch 三个核心指标。MOTA 反映整体准确度IDF1 反映 ID 保持能力ID Switch 直接数 ID 跳变次数。这三个数比看视频靠谱得多调参前后各跑一次才知道改动是正收益还是负收益。import motmetrics as mm acc mm.MOTAccumulator(auto_idTrue) # gt_ids 和 pred_ids 是当前帧的真实 ID 和预测 ID # gt_boxes 和 pred_boxes 是对应框格式为 (x, y, w, h) dist mm.distances.iou_matrix(gt_boxes, pred_boxes, max_iou0.5) acc.update(gt_ids, pred_ids, dist) mh mm.metrics.create() print(mh.compute(acc, metrics[mota, idf1, num_switches], nameeval))max_iou0.5是匹配阈值低于这个 IoU 视为不匹配。num_switches就是 ID Switch 次数调max_age和max_dist时盯着这个数变化比盲调高效。5.2 检测端和跟踪端分开调别一起动血泪经验同时改 YOLO 置信度和 DeepSort 参数出了问题根本不知道是哪边的锅。我一般固定检测端先把conf定在 0.4 到 0.5 之间确认检测框稳定不抖再去调跟踪端。跟踪端优先调max_age和max_dist这两个对 ID Switch 影响最大。n_init影响的是轨迹确认速度调大能减少误检但会让新目标出现时 ID 延迟几帧才显示按场景取舍。参数作用调大后果调小后果conf检测置信度阈值漏检增多误检增多max_age轨迹丢失保留帧数遮挡后 ID 更稳但可能保留错误轨迹遮挡后易分配新 IDmax_dist外观匹配最大距离匹配更宽松可能串 ID匹配更严格易断轨n_init轨迹确认帧数新目标 ID 出现延迟误检更容易被确认5.3 一个具体技巧用检测框面积过滤远景小目标固定机位视频里远处行人框只有十几个像素YOLOv9 检出来置信度低DeepSort 匹配时外观特征也几乎不可用这些框是 ID 跳变的主要来源。在送入 DeepSort 之前加一道面积过滤把宽高小于某个阈值的框直接丢掉能明显降低 ID Switch。# 过滤掉宽或高小于 20 像素的框 areas bbox_xywh[:, 2] * bbox_xywh[:, 3] keep areas 400 # 20x20 bbox_xywh bbox_xywh[keep] confidences confidences[keep] cls cls[keep]400这个阈值按自己视频分辨率调1080p 下 20x20 是个保守起点。这个操作会牺牲远景目标的召回但换来的是近景目标 ID 稳定做客流统计时通常划算。从那以后我每次拿到新的跟踪源码包都强制先跑一遍默认参数、记录 MOTA 和 ID Switch 基线再动任何一个参数。没有基线调参就是玄学。希望帮到你。本文还有配套的精品资源点击获取