简介:本资源为基于YOLOv5与DeepSORT的跟踪及卡尔曼滤波预测Python项目源码包,面向计算机、人工智能、通信工程、自动化等专业的在校学生、教师及企业员工,可用于毕业设计、课程设计、作业或项目初期立项演示。项目在BDD100K自动驾驶数据集上完成训练与测试,涵盖目标检测、多目标跟踪与轨迹预测的完整流程,代码均经运行验证,答辩评审平均分达96分。压缩包共180个文件,约44.47MB,以78个py源码、47个yaml配置、16个txt说明、11个yml及10个md文档为主,另含Dockerfile、权重文件与Jupyter教程,结构清晰便于按模块学习。已有195人学习下载。读者可获得可运行的检测跟踪代码、数据集训练配置、文档说明与使用指引,并支持在此基础上修改扩展,适合进阶学习与二次开发。
1. 从一段路口监控说起:yolov5+deepsort+卡尔曼滤波到底在解决什么
假设你手头有一段 30 秒的路口监控,画面里人来人往、电动车穿插。你想知道:每一帧里有哪些目标、它们分别是谁、下一秒会出现在哪。这三个问题对应三件事——检测、跟踪、预测。yolov5 负责“这一帧有什么”,deepsort 负责“这一帧的张三和上一帧的张三是不是同一个人”,卡尔曼滤波负责“张三下一帧大概在哪”。三者串起来,才是一条能落地的多目标跟踪(MOT)流水线。
这套组合之所以被大量课程设计和工程原型采用,是因为它把“检测强、关联稳、预测轻”三件事拆得足够干净:yolov5 出框,deepsort 用外观特征加运动信息做匹配,卡尔曼滤波在匹配之前先给出预测位置,让 IoU 匹配有参照。标题里说的“python 源码+文档说明+使用说明”,本质就是把这套流水线封装成能跑起来的工程。适合谁?适合刚学完 python、想找一个能写进简历、能改参数、能换自己数据集的实战项目的人;也适合已经会调 yolov5、但一遇到 ID 跳变就头疼的熟手。下面按“先跑通、再调参、最后避坑”的顺序讲清楚。
2. 把环境搭起来:yolov5 与 deepsort 的依赖边界
2.1 为什么这套组合对 python 版本和 torch 版本敏感
yolov5 的推理依赖 torch 和 torchvision,deepsort 的 ReID 特征提取又依赖 torchreid 或它自带的一个轻量特征网络。两者对 torch 版本的要求经常打架:yolov5 较新版本要求 torch>=1.7,而某些 deepsort 实现里的torchreid在 torch 2.x 上会因为torchvision.transforms.functional的接口变动报错。我一般会先锁定一个中间版本,比如 torch 1.13.1 + torchvision 0.14.1 + python 3.8 或 3.9,这个组合在 yolov5 和 deepsort 两边都验证过,翻车概率最低。
如果你用 conda,建议单独建环境,不要和系统 python 混用。命令如下:
conda create -n yolo_deepsort python=3.9 -y conda activate yolo_deepsort pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install opencv-python numpy scipy pyyaml tqdm逻辑说明:先建独立环境,避免和已有项目冲突;torch 和 torchvision 必须成对安装,版本对应关系在 pytorch 官网有表;opencv 用于读视频和画框,scipy 用于卡尔曼滤波里的矩阵运算,pyyaml 用于读 yolov5 的配置文件。参数上,cu117对应 CUDA 11.7,如果你没有 NVIDIA 显卡,把--index-url那行换成 CPU 版本即可,但推理速度会从 30 FPS 掉到 3 FPS 左右,跟踪效果本身不受影响。
2.2 目录结构怎么摆,才能让源码和文档对得上
拿到一份“python 源码+文档说明+使用说明”的压缩包,先别急着跑main.py。我习惯先看目录,确认三件事:权重文件在哪、配置文件在哪、入口脚本是哪个。典型结构如下:
project/ ├── yolov5/ # 检测模型目录 │ ├── weights/yolov5s.pt # 预训练权重 │ └── models/ # 网络结构定义 ├── deepsort/ # 跟踪器目录 │ ├── deep_sort.py # 跟踪主逻辑 │ └── checkpoint/ # ReID 特征权重 ├── configs/ │ └── deepsort.yaml # 跟踪参数配置 ├── main.py # 入口:读视频、检测、跟踪、画框 └── requirements.txt如果压缩包里没有yolov5s.pt,需要自己去 yolov5 官方仓库下载,注意版本要和代码里的models/yolo.py对得上。deepsort 的 ReID 权重通常是ckpt.t7,大小约 40MB,如果缺失,跟踪会退化成只靠运动信息匹配,ID 跳变会明显增多。这一步的检查很关键,很多“跑不起来”其实是权重路径写死在代码里,而文件没放对位置。
2.3 最小可跑命令:用一段视频验证整条链路
环境好了、目录对了,先用一段短视频跑通,不要一上来就接摄像头。命令通常长这样:
python main.py --source test.mp4 --yolo-weights yolov5/weights/yolov5s.pt --deepsort-ckpt deepsort/checkpoint/ckpt.t7 --conf-thres 0.4 --output output.mp4逻辑说明:--source指定输入视频,--yolo-weights指定检测权重,--deepsort-ckpt指定 ReID 权重,--conf-thres是检测置信度阈值,--output是输出视频路径。参数上,conf-thres默认 0.25 时框多但误检多,0.4 到 0.5 之间比较平衡;如果画面里小目标多,可以降到 0.3,但 deepsort 的匹配压力会变大。跑完后打开output.mp4,重点看三件事:框有没有漏、ID 有没有频繁跳、轨迹线有没有突然折返。这三件事分别对应检测、关联、预测的问题,后面章节会逐个拆。
3. deepsort 的关联逻辑:卡尔曼滤波在匹配前做了什么
3.1 卡尔曼滤波的预测步:为什么它能让 IoU 匹配更稳
deepsort 的核心不是“检测完直接画框”,而是维护一个轨迹池。每条轨迹有一个状态向量,通常包含中心点坐标、宽高比、高度以及它们在时间上的变化率,共 8 维。卡尔曼滤波分两步:预测和更新。预测步根据上一帧的状态和运动模型,推算出当前帧的预计位置;更新步用当前帧的检测框去修正这个预测。匹配时,deepsort 会同时看运动信息(预测框和检测框的 IoU)和外观信息(ReID 特征余弦距离),两者加权得到代价矩阵,再用匈牙利算法做分配。
为什么预测步重要?因为当目标被短暂遮挡时,检测框会消失,如果没有预测,轨迹直接断掉;有了预测,轨迹还能靠运动惯性撑几帧,等目标重新出现时再匹配上。这就是卡尔曼滤波在多目标跟踪里的价值——它不是用来“检测”的,而是用来“续命”的。
3.2 代价矩阵的两个权重:lambda 和 max_dist 怎么调
deepsort 的匹配代价由两部分组成:外观代价和运动代价。代码里通常有一个lambda参数控制外观权重,一个max_dist控制运动匹配的最大距离。我一般会这样改:
# 在 deepsort 配置或跟踪器初始化处 tracker = DeepSort( max_dist=0.2, # 运动匹配阈值,越小越严格 max_iou_distance=0.7, # IoU 距离阈值 max_age=30, # 轨迹最大存活帧数 n_init=3, # 连续匹配多少帧才确认轨迹 nn_budget=100, # 外观特征库大小 lambda_=0.98 # 外观代价权重 )逻辑说明:max_dist是运动信息在代价矩阵里的门限,值越小,要求预测框和检测框越近才匹配;max_iou_distance是 IoU 匹配的门限;max_age决定一条轨迹丢失后还能活多久,30 帧在 30 FPS 视频里约 1 秒,适合大多数场景;n_init是轨迹确认前的连续命中次数,设 3 可以过滤掉一闪而过的误检;lambda_越接近 1,越依赖外观特征,适合行人穿着差异大的场景,但 ReID 模型不强时会引入错误匹配。参数没有万能值,我的习惯是先在测试视频上跑一遍默认值,看 ID 跳变发生在什么情况——如果是遮挡后跳变,调大max_age;如果是相似外观目标互换,调小lambda_或提高 ReID 特征质量。
3.3 从检测框到轨迹 ID:一次完整匹配的代码走读
下面这段伪代码展示了 deepsort 一帧内的主要流程,你可以对照源码看:
# 1. 检测 dets = yolo_model(frame) # 返回 xyxy, conf, cls # 2. 预测:对每条已有轨迹做卡尔曼预测 for track in tracker.tracks: track.predict() # 3. 匹配:外观 + 运动 cost_matrix = [] for track in tracker.tracks: appearance_cost = cosine_distance(track.features, det_features) motion_cost = iou_distance(track.predicted_box, det_boxes) cost = lambda_ * appearance_cost + (1 - lambda_) * motion_cost cost_matrix.append(cost) matches, unmatched_tracks, unmatched_dets = linear_assignment(cost_matrix, max_dist) # 4. 更新:匹配上的轨迹用检测框更新卡尔曼状态 for track_idx, det_idx in matches: tracker.tracks[track_idx].update(det_boxes[det_idx]) # 5. 未匹配的轨迹 age+1,超过 max_age 删除;未匹配的检测初始化新轨迹逻辑说明:第 2 步的预测是卡尔曼滤波的核心,它让每条轨迹在匹配前有一个“预计位置”;第 3 步的代价矩阵把外观和运动融合,lambda_就是融合比例;第 4 步的更新用检测框修正预测,避免误差累积。参数上,linear_assignment里的max_dist会直接丢弃代价过大的匹配对,防止强行关联。如果你发现轨迹 ID 在目标交叉后互换,大概率是外观特征区分度不够,而不是卡尔曼滤波本身的问题。
4. 换自己的数据集:yolov5 训练与 deepsort 适配的衔接点
4.1 用 yolov5 训练自定义检测器的最小流程
标题里提到“yolov5 训练自己的数据集”,这是很多人拿到源码后第一个想改的地方。流程不复杂,但坑在数据格式和配置文件。假设你有 500 张标注好的图片,类别是 person 和 car,步骤如下:
# 1. 准备数据目录 dataset/ ├── images/train/ images/val/ ├── labels/train/ labels/val/ └── data.yaml # 2. data.yaml 内容 train: dataset/images/train val: dataset/images/val nc: 2 names: ['person', 'car'] # 3. 开始训练 python yolov5/train.py --img 640 --batch 16 --epochs 100 --data dataset/data.yaml --weights yolov5s.pt --project runs/train逻辑说明:--img 640是输入分辨率,和推理时保持一致;--batch 16在 8GB 显存上比较稳,显存小就降到 8;--epochs 100对 500 张图通常够用,但要看验证集 mAP 是否还在涨;--weights yolov5s.pt表示从预训练权重微调,比从头训练收敛快得多。训练完后,最佳权重在runs/train/exp/weights/best.pt,把它替换到跟踪脚本的--yolo-weights参数里即可。
4.2 检测类别变了,deepsort 需要改哪里
deepsort 本身不关心类别,它只关心框和特征。但有两个地方要注意:一是 ReID 特征提取网络是在行人数据集上训练的,如果你跟踪的是车辆,外观特征区分度会下降,ID 跳变可能增多;二是如果检测类别里包含大量小目标,max_dist和max_iou_distance需要适当放宽。我的做法是:换类别后先跑一段视频,统计 ID 跳变次数,如果明显高于行人场景,就把lambda_从 0.98 降到 0.9,让运动信息多承担一点匹配责任。另外,如果检测器只输出一类目标,可以在代码里过滤掉其他类,减少无效匹配。
4.3 用置信度和 NMS 参数控制送入跟踪器的框质量
yolov5 的后处理有两个关键参数:conf-thres和iou-thres。前者控制置信度阈值,后者控制 NMS 的 IoU 阈值。送入 deepsort 的框太多或太少都会影响跟踪:太多会增加匹配歧义,太少会导致轨迹断裂。我一般会这样设:
| 参数 | 推荐值 | 作用 |
|---|---|---|
| conf-thres | 0.4 | 过滤低置信度框,减少误检 |
| iou-thres | 0.5 | NMS 合并重叠框,避免同一目标多个框 |
| max-det | 100 | 单帧最大检测数,防止极端场景卡顿 |
| classes | 0 或 None | 只保留需要跟踪的类别 |
逻辑说明:conf-thres太低会把背景误检送入跟踪器,导致虚假轨迹;太高会漏掉遮挡目标,导致轨迹提前结束。iou-thres在拥挤场景可以降到 0.4,让 NMS 更激进地合并框。max-det在人群密集时有用,但设太小会丢目标。这些参数在detect.py或main.py里都能找到,改完重新跑视频对比即可。
5. 避坑与排查:ID 跳变、漏检、速度慢的常见原因
5.1 现象:目标被遮挡几帧后,ID 变了
原因:max_age太小,轨迹在遮挡期间被删除,目标重新出现时只能新建轨迹。解决:把max_age从默认 30 调到 50 或 70,给轨迹更长的存活时间。但注意,调太大会让消失的目标轨迹一直存在,增加匹配负担,一般不超过 90 帧。
5.2 现象:两个外观相似的目标交叉后,ID 互换
原因:ReID 特征区分度不够,lambda_太高导致外观代价主导匹配。解决:降低lambda_到 0.9 或 0.85,让运动信息参与更多;或者换一个更强的 ReID 模型,但要注意推理速度会下降。如果场景里目标运动规律明显,运动信息往往比外观更可靠。
5.3 现象:检测框明明有,但跟踪器没输出轨迹
原因:n_init设得太大,新轨迹需要连续匹配多帧才确认,目标快速移动时可能还没确认就离开画面。解决:把n_init从 3 降到 2,或者对高置信度检测直接初始化轨迹。但降太低会增加虚假轨迹,需要权衡。
5.4 现象:推理速度只有几 FPS,视频卡顿
原因:yolov5 输入分辨率太高,或者 ReID 特征提取在每帧对每个检测都跑一次。解决:把--img从 640 降到 416 或 320,速度能提升一倍以上;ReID 特征可以每 N 帧提取一次,或者只对确认轨迹提取,减少计算量。如果用的是 CPU,建议换 GPU,或者用 yolov5n 这种轻量模型。
5.5 现象:卡尔曼滤波预测的框和检测框偏差越来越大
原因:过程噪声和观测噪声参数不适合当前场景,导致预测过度依赖运动模型或过度依赖检测。解决:在 deepsort 的卡尔曼滤波实现里找到std_weight_position和std_weight_velocity,适当调大位置噪声,让预测更信任检测;调大速度噪声,让运动模型更灵活。这两个参数没有统一值,需要根据目标运动剧烈程度试。
6. 进阶技巧:用轨迹平滑和预测可视化验证卡尔曼滤波的效果
最后一章不讲大道理,讲一个我常用的验证方法:把卡尔曼滤波的预测框和检测框同时画出来,肉眼对比。具体做法是在跟踪代码里加几行:
# 在 track.update() 之前,拿到预测框 pred_box = track.predict() # 返回 [x, y, w, h] # 画检测框(绿色)和预测框(红色) cv2.rectangle(frame, (det[0], det[1]), (det[2], det[3]), (0, 255, 0), 2) cv2.rectangle(frame, (pred_box[0], pred_box[1]), (pred_box[2], pred_box[3]), (0, 0, 255), 2)逻辑说明:绿色是当前帧检测到的真实位置,红色是卡尔曼滤波在匹配前预测的位置。如果红色框总是滞后于绿色框,说明预测过度依赖上一帧状态,可以调大过程噪声;如果红色框乱跳,说明观测噪声太小,预测被检测带偏。这个方法比看 ID 跳变更直观,因为你能直接看到预测质量。
另外,轨迹平滑可以用卡尔曼滤波的输出代替原始检测框来画轨迹线,这样即使检测框有抖动,轨迹线也会更顺滑。我一般会在main.py里把画框和画轨迹分开,画框用检测结果,画轨迹用卡尔曼更新后的状态。这样既保留了检测的准确性,又让可视化更稳定。
还有一个习惯:每次换场景,先跑 100 帧,把预测框和检测框的 IoU 平均值打出来。如果平均 IoU 低于 0.5,说明卡尔曼参数需要调;如果高于 0.8,说明预测很准,可以适当降低检测频率来省算力。这个指标比 FPS 更能反映跟踪器的健康度。
希望帮到你。
本文还有配套的精品资源,点击获取