简介:本资源为毕业设计《基于yolov5+deepsort实现车辆目标跟踪与应用》的完整Python项目源码与文档说明,面向计算机、人工智能、通信工程、自动化等专业的在校学生、教师及企业员工,适合作为毕设、课程设计、作业或项目初期立项演示,也适合具备一定基础的学习者进阶参考。项目围绕车辆目标跟踪展开,涵盖摄像头视频播放、目标检测与多目标跟踪、车流量统计、车辆违停检测、车辆逆行检测以及信息栏控制等功能模块,代码均经过测试运行成功。压缩包共134个文件,约43.71MB,以64个py源码文件、30个yaml配置、9个png与2个jpg图像、7个xml标注、6个sh脚本及3个md文档为主,另含Dockerfile、ipynb教程与UI文件,结构完整便于按模块学习。目前已有108人学习下载。下载后可先阅读README.md,在理解检测与跟踪流程的基础上修改代码,实现自定义功能,仅供学习参考,切勿用于商业用途。
1. 从一段路口视频说起:yolov5+deepsort 车辆跟踪到底在做什么
假设你手里有一段城市路口的高清视频,画面里几十辆车来回穿梭,有的被前车挡住,有的在画面边缘进进出出。现在要你统计每辆车在画面里停留了多久、走了哪条轨迹、有没有逆行——靠人眼盯,十分钟就崩溃。基于 yolov5+deepsort 实现车辆目标跟踪要解决的正是这件事:yolov5 负责在每一帧里把车框出来,deepsort 负责把这一帧的框和上一帧的框对应起来,给每辆车分配一个稳定的 ID,让它在整个视频里“认得出是同一辆”。这套组合是车辆目标跟踪方向里最经典、最容易复现的入门方案,也是很多毕业设计选它的原因:检测有成熟权重、跟踪有开源实现、Python 生态齐全,一台带独显的笔记本就能跑通。适合谁?适合刚接触计算机视觉、需要一套能跑通、能改、能写进论文的完整链路的人。但“能跑通”和“跑得稳”之间隔着不少坑,下面按落地顺序拆开讲。
2. 检测与跟踪的分工:yolov5 出框,deepsort 认车
2.1 为什么不是“一个模型全干完”
很多人第一反应是:既然 yolov5 已经能检测车辆,为什么还要 deepsort?因为检测模型是“无记忆”的——它只看当前这一帧,输出一堆框和类别,帧与帧之间没有任何关联。第 1 帧里那辆白色轿车是框 A,第 2 帧里它移动了 20 像素变成框 B,检测模型不知道 A 和 B 是同一辆车。跟踪要补的就是这个“跨帧身份”。
deepsort 的思路是:对每个检测框提取一个外观特征向量(ReID 特征),同时用卡尔曼滤波预测目标下一帧可能出现的位置,再把“外观相似度”和“位置匹配度”结合起来做匈牙利匹配。匹配上的框继承旧 ID,没匹配上的框新建 ID,连续多帧没匹配上的 ID 就删掉。所以它的核心价值是在遮挡、短暂丢失后还能把 ID 接回来,这正是车辆跟踪里最需要的。
常见做法是:yolov5 用官方或自训练的车辆检测权重,deepsort 用开源实现里的默认 ReID 模型。两者通过一个“检测结果 → 跟踪器 update”的接口串起来,中间的数据格式是[x1, y1, x2, y2, conf, cls]。
2.2 最小可跑通的目录与依赖
先别急着改代码,把环境跑通是第一关。我一般会建一个干净的虚拟环境,避免和系统里的其他包打架。
# 创建并激活虚拟环境(Python 3.8 兼容性最好) python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate # 安装核心依赖,torch 按自己 CUDA 版本去官网选对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy scipy lap这里lap是做匈牙利匹配的线性分配库,deepsort 依赖它;scipy用于卡尔曼滤波里的矩阵运算。torch 的安装命令一定要按自己显卡的 CUDA 版本选,装错了会出现“能 import 但一跑就报 CUDA error”的玄学问题。
目录上我习惯这样分:
project/ ├── yolov5/ # 检测模型与推理代码 ├── deep_sort/ # 跟踪器 ├── weights/ # 检测权重 ├── configs/ # 跟踪参数 ├── videos/ # 输入视频 └── main.py # 串联入口2.3 把检测结果喂给跟踪器的最小代码
下面这段是串联的核心逻辑,去掉花哨封装,只保留“读帧 → 检测 → 跟踪 → 画框”四步。
import cv2 import torch from deep_sort import DeepSort # 加载 yolov5 模型,weights 换成自己的车辆检测权重 model = torch.hub.load('ultralytics/yolov5', 'custom', path='weights/vehicle.pt', device='0') model.conf = 0.4 # 置信度阈值,低于此值的框直接丢 model.iou = 0.45 # NMS 的 IoU 阈值,控制重叠框合并 # 初始化 deepsort,max_dist 控制外观匹配的宽松程度 tracker = DeepSort(model_path='deep_sort/deep/checkpoint/ckpt.t7', max_dist=0.2, max_iou_distance=0.7, max_age=70) cap = cv2.VideoCapture('videos/road.mp4') while True: ret, frame = cap.read() if not ret: break # yolov5 推理,结果里 xyxy 是框,conf 是置信度,cls 是类别 results = model(frame) dets = results.xyxy[0].cpu().numpy() # 只保留车辆类别(假设 2 是 car,5 是 bus,7 是 truck) dets = dets[[int(d[5]) in (2, 5, 7) for d in dets]] if len(dets) > 0: bbox_xywh = dets[:, :4] # deepsort 要的是 [cx, cy, w, h] 格式,这里做转换 bbox_xywh[:, 2] -= bbox_xywh[:, 0] bbox_xywh[:, 3] -= bbox_xywh[:, 1] bbox_xywh[:, 0] += bbox_xywh[:, 2] / 2 bbox_xywh[:, 1] += bbox_xywh[:, 3] / 2 confs = dets[:, 4] # 更新跟踪器,返回带 track_id 的框 outputs = tracker.update(bbox_xywh, confs, frame) for out in outputs: x1, y1, x2, y2, tid = out cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f'ID {int(tid)}', (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow('track', frame) if cv2.waitKey(1) & 0xFF == 27: break cap.release() cv2.destroyAllWindows()逻辑说明:yolov5 的results.xyxy[0]输出的是[x1, y1, x2, y2, conf, cls],而 deepsort 的update需要[cx, cy, w, h]和对应的置信度,所以中间要做一次坐标转换,这一步漏了会导致框整体偏移。参数上,model.conf调低会引入更多误检,deepsort 就会频繁新建 ID;调高会漏检,跟踪容易断。max_age是“一个 ID 最多允许丢失多少帧”,车辆被遮挡时这个值太小会直接换 ID,太大又会让已经离开画面的车“阴魂不散”,一般 30 到 70 之间试。
3. 参数调优:让 ID 不跳、不丢、不重复
3.1 三个最影响跟踪效果的参数
deepsort 里真正需要动手调的不多,但下面三个几乎决定了成败:
| 参数 | 作用 | 调大后果 | 调小后果 | 建议起点 |
|---|---|---|---|---|
| max_dist | 外观特征匹配的最大余弦距离 | 容易把不同车匹配成同一 ID | 同一辆车频繁换 ID | 0.2 |
| max_iou_distance | 位置匹配的 IoU 阈值 | 遮挡后容易错配 | 正常移动也匹配不上 | 0.7 |
| max_age | ID 允许丢失的帧数 | 离开画面的车残留 | 遮挡即换 ID | 50 |
这三个参数没有万能值,和视频帧率、车辆速度、遮挡频率都有关。我的习惯是先用默认值跑一遍,把出现问题的片段截出来,再针对性调一个参数,不要一次改三个。
3.2 检测质量才是跟踪的上限
血泪经验:deepsort 跟踪效果差,八成不是跟踪器的问题,而是检测框在抖。yolov5 如果每帧框的位置跳来跳去,卡尔曼滤波的预测就会失准,外观特征也会因为框内内容变化而漂移。所以调跟踪之前,先把检测稳住:
# 推理时开启 TTA 和更高分辨率,能明显减少框抖动 results = model(frame, size=1280, augment=True) # 对同一目标连续帧的框做简单平滑,抑制抖动size=1280比默认的 640 能检出更远的小车,但速度会下降,实时场景要权衡。augment=True是测试时增强,精度略升、速度略降。如果做的是离线分析,这两个都开;如果是实时,建议只提分辨率。
3.3 用配置文件管理参数而不是硬编码
参数一多,散在代码里就是灾难。我一般抽一个 yaml:
# configs/track.yaml detect: conf: 0.4 iou: 0.45 size: 1280 track: max_dist: 0.2 max_iou_distance: 0.7 max_age: 50 n_init: 3 # 连续命中多少帧才确认一个新 IDn_init这个参数容易被忽略:它要求新目标连续被检测到 3 帧才正式分配 ID,能有效过滤掉一闪而过的误检。代价是车辆刚进入画面时会有两三帧没有 ID,属于正常现象。
4. 避坑与排查:那些让 ID 乱跳的真实原因
4.1 现象:同一辆车 ID 频繁变化
原因通常是外观特征匹配失败。车辆颜色相近、光照突变、或者检测框抖动导致框内像素变化大,都会让 ReID 特征对不上。解决:先把max_dist从 0.2 放宽到 0.3 试试,同时检查检测框是否稳定;如果画面里同色车多,可以考虑换一个更强的 ReID 模型,或者提高检测分辨率让框更贴合车身。
4.2 现象:车辆被遮挡后 ID 直接换新
这是max_age太小或卡尔曼预测不准。遮挡期间检测不到目标,跟踪器靠预测维持,如果max_age只有 10,遮挡 15 帧后 ID 就被删了。解决:把max_age提到 50 以上,同时确认max_iou_distance不要太小,否则遮挡结束后位置偏移稍大就匹配不上。
4.3 现象:画面边缘出现“幽灵 ID”
车辆已经驶出画面,但 ID 还挂在边缘不动。原因是max_age太大,跟踪器一直在等它回来。解决:适当降低max_age,或者在代码里加一个判断——如果某个 ID 的框连续多帧贴在画面边界且没有检测支持,主动删除。
4.4 现象:一跑就报 lap 相关错误
lap库在部分 Python 版本上编译失败,报Microsoft Visual C++ 14.0 is required之类。解决:Windows 上装 Visual C++ Build Tools,或者直接用pip install lap --only-binary :all:装预编译版本;实在不行换scipy.optimize.linear_sum_assignment自己替换匹配部分。
4.5 现象:GPU 显存够但速度还是很慢
常见原因是每帧都在做 ReID 特征提取,而 ReID 模型默认跑在 CPU 上。解决:确认 deepsort 里的特征提取网络也.to('cuda'),并且把检测和跟踪的 batch 处理打开。另一个隐藏坑是 OpenCV 的cv2.imshow在高分辨率下会拖慢循环,离线分析时直接写视频文件,别开窗口。
5. 从能跑到好用:轨迹导出与效果验证的一个技巧
跑通之后,真正让这套方案“有价值”的一步是把轨迹落成结构化数据,而不是只停留在画面上画框。我一般会在跟踪循环里顺手把每个 ID 的轨迹点存下来:
import json tracks = {} # {track_id: [[frame_idx, cx, cy], ...]} # 在画框循环里追加 for out in outputs: x1, y1, x2, y2, tid = out cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 tracks.setdefault(int(tid), []).append([frame_idx, cx, cy]) # 循环结束后导出 with open('tracks.json', 'w') as f: json.dump(tracks, f)有了这份 JSON,你就能做很多“论文里能写、答辩时能演示”的事:统计每辆车的平均速度(相邻帧位移除以帧间隔)、判断是否逆行(轨迹方向与车道方向夹角)、画热力图看车流集中在哪。验证跟踪效果时,我常用的一个笨办法但很有效:挑一段有遮挡的视频,人工数出实际车辆数,再对比程序输出的 ID 总数。如果 ID 总数明显多于实际车辆数,说明 ID 切换频繁,回去调max_dist和max_age;如果明显少,说明漏检或过早合并,检查检测阈值。
还有一个容易被忽略的点:帧率。如果视频是 30fps,但你的处理速度只有 5fps,卡尔曼滤波的预测步长和实际时间就对不上,跟踪会明显发飘。离线处理时问题不大,实时场景一定要么降分辨率提速度,要么在跟踪器里按实际时间间隔更新。
我自己踩得最深的一次坑,是拿一个训练集里没见过的车型去跑,检测框时有时无,deepsort 的 ID 像抽奖一样乱跳,调了两天参数都没用,最后发现是检测权重根本不认识那类车。从那以后我养成了一个习惯:跟踪出问题,先单独把检测结果可视化一遍,确认框本身是对的,再去动跟踪参数。这个顺序能省掉大量无效调试。希望帮到你。
本文还有配套的精品资源,点击获取