☰
基于YOLOv5+DeepSORT的车辆目标跟踪毕设项目实战与调优
2026/10/7 23:30:07 网站建设 项目流程

简介:本资源为毕业设计《基于yolov5+deepsort实现车辆目标跟踪与应用》的完整Python项目源码与文档说明,面向计算机、人工智能、通信工程、自动化等专业的在校学生、教师及企业员工,适合作为毕设、课程设计、作业或项目初期立项演示,也适合具备一定基础的学习者进阶参考。项目围绕车辆目标跟踪展开,涵盖摄像头视频播放、目标检测与多目标跟踪、车流量统计、车辆违停检测、车辆逆行检测以及信息栏控制等功能模块,代码均经过测试运行成功。压缩包共134个文件,约43.71MB,以64个py源码文件为核心,辅以30个yaml配置、9个png与2个jpg示意图、7个xml标注、6个sh脚本、3个md说明文档,以及Dockerfile、license、ipynb教程等,目录结构清晰,便于按模块检索与二次开发。目前已有108人学习下载,下载后建议先阅读README.md,仅供学习参考,切勿用于商业用途。

1. 从一份能跑通的 YOLOv5+DeepSORT 毕设包说起:车辆跟踪到底难在哪

如果你正在做车辆目标跟踪相关的毕业设计,大概率已经翻过不少 YOLOv5 加 DeepSORT 的教程,但真正动手时还是会卡在几个地方:环境装不上、权重加载报错、跟踪 ID 频繁跳变、统计逻辑对不上实际车流。这份《基于 YOLOv5+DeepSORT 实现车辆目标跟踪与应用》的 Python 项目源码包,解决的就是从检测到跟踪再到业务应用这条完整链路的落地问题。它不是单纯给你一个检测脚本,而是把摄像头视频播放、多目标跟踪、车流量统计、违停检测、逆行检测、信息栏控制这几个功能串成了一个可运行的系统。适合计算机、人工智能、通信、自动化等专业的在校学生做毕设或课程设计,也适合刚接触多目标跟踪、想找一个能跑通的基线项目来改的开发者。下面我按实际拆包和复现的顺序,把这份资源的技术选型、运行步骤、参数含义和容易翻车的地方讲清楚。

2. 拆开这个包:YOLOv5 检测与 DeepSORT 跟踪是怎么串起来的

2.1 为什么是 YOLOv5 + DeepSORT 这个组合

车辆目标跟踪这个任务,拆开看就两件事:每一帧里车在哪,以及同一辆车在连续帧里怎么对应上。YOLOv5 负责前者,DeepSORT 负责后者。YOLOv5 在速度和精度之间平衡得比较好,模型体积小,用 PyTorch 加载方便,对毕设这种算力有限、又要实时出效果的场景很合适。DeepSORT 的核心是在 SORT 的基础上加了外观特征提取网络,用余弦距离做级联匹配,能在车辆被短暂遮挡后重新找回同一个 ID,这对车流量统计和违停判断很关键——ID 一跳变,统计就全乱了。

这个包里检测和跟踪是解耦的:YOLOv5 输出每帧的检测框,DeepSORT 拿这些框去做预测和匹配。这种结构的好处是你想换检测器或者换跟踪器都相对独立,不用把整个系统推倒重来。常见做法是把检测置信度阈值设在 0.4 到 0.5 之间,太低会引入大量误检,DeepSORT 的匹配压力陡增;太高又会漏掉远处的小目标车辆,导致跟踪断链。

2.2 目录结构和关键文件说明

拿到包之后先别急着跑,花两分钟把结构看清楚,后面排错会快很多。从文件清单来看,核心内容大致是这样分布的:

文件/目录作用
Yolov5-Deepsort-main.iml项目配置文件,标记项目根目录和模块类型
tutorial.ipynb交互式教程,通常包含分步运行和可视化演示
Dockerfile容器化构建脚本,用于统一环境
YOLOv5模型.jpg/train.jpg模型结构或训练过程示意图,辅助理解
LICENSE开源协议,注意商用限制
.gitignore/.gitkeep版本控制忽略规则和占位文件

tutorial.ipynb是这个包里最值得先打开的文件,它一般会把视频读取、模型加载、跟踪循环、结果绘制这几步拆成独立的单元格,你可以一格一格跑,看每一步的输出对不对。Dockerfile的存在说明作者考虑过环境一致性问题,如果你本地环境怎么都配不好,用容器跑是一个兜底方案。

2.3 从视频流到跟踪结果的核心代码逻辑

下面这段是这类项目里最典型的跟踪主循环结构,我按实际项目里常见的写法整理出来,你可以对照包里的代码看:

import cv2 import torch from models.experimental import attempt_load from utils.datasets import LoadStreams, LoadImages from deep_sort.utils.parser import get_config from deep_sort.deep_sort import DeepSort # 加载 YOLOv5 模型,weights 替换成包里的权重文件路径 model = attempt_load('weights/yolov5s.pt', map_location='cpu') model.eval() # 初始化 DeepSORT,配置文件里指定特征提取网络和匹配阈值 cfg = get_config() cfg.merge_from_file('deep_sort/configs/deep_sort.yaml') deepsort = DeepSort( cfg.DEEPSORT.REID_CKPT, max_dist=cfg.DEEPSORT.MAX_DIST, # 外观特征最大余弦距离 min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE, # 检测框最低置信度 nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, # NMS 重叠阈值 max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE, max_age=cfg.DEEPSORT.MAX_AGE, # 轨迹丢失后保留帧数 n_init=cfg.DEEPSORT.N_INIT, # 连续命中多少次才确认轨迹 nn_budget=cfg.DEEPSORT.NN_BUDGET, # 外观特征库容量 use_cuda=True ) cap = cv2.VideoCapture('test_video.mp4') while cap.isOpened(): ret, frame = cap.read() if not ret: break # YOLOv5 推理,输出检测框 img = letterbox(frame, new_shape=640)[0] img = img[:, :, ::-1].transpose(2, 0, 1) img = torch.from_numpy(img).float() / 255.0 pred = model(img.unsqueeze(0))[0] detections = non_max_suppression(pred, conf_thres=0.5, iou_thres=0.45)[0] # 把检测框送入 DeepSORT 做匹配 if detections is not None: bbox_xywh = detections[:, :4].cpu().numpy() confs = detections[:, 4].cpu().numpy() outputs = deepsort.update(bbox_xywh, confs, frame) for track in outputs: x1, y1, x2, y2, track_id = track cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f'ID:{int(track_id)}', (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow('Tracking', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这段代码里几个参数直接决定跟踪效果。max_dist控制外观匹配的宽松程度,调大能容忍更多外观变化但容易串 ID,调小则容易丢目标,一般从 0.2 开始试。max_age是轨迹丢失后保留多少帧再删除,车辆被遮挡时这个值太小会导致 ID 切换,太大又会让已经离开画面的车残留轨迹,30 帧左右是比较常见的起点。n_init是连续命中多少次才确认一条新轨迹,设成 3 可以过滤掉大部分一闪而过的误检。nn_budget是每个轨迹保留的外观特征数量,越大越准但越吃内存,毕设场景 100 够用。

2.4 车流量统计与违停、逆行的判定逻辑

跟踪跑通之后,业务功能才有意义。车流量统计的常见做法是在画面里画一条虚拟检测线,当某个 track_id 的检测框中心点从线的一侧移动到另一侧时,计数加一。这里有个坑:如果车辆在检测线附近来回抖动,中心点反复穿越,计数会重复累加。解决办法是给每个 track_id 记录一个已计数标记,或者要求连续两帧都在另一侧才确认。

违停检测的逻辑是给每个 track_id 维护一个停留计时器,如果检测框中心点在某个区域内停留超过设定时长(比如 10 秒),就标记为违停。逆行检测则需要预先定义车道的方向向量,计算车辆运动方向与车道方向的夹角,超过阈值就判定逆行。这两个功能都依赖跟踪 ID 的稳定性,ID 一跳变,计时器和方向计算全部重置,所以前面调 DeepSORT 参数时不要只盯着检测框好不好看,要盯着 ID 稳不稳。

3. 把项目跑起来:环境配置、权重加载与视频推理的完整步骤

3.1 Python 环境与依赖安装

这个项目基于 PyTorch,对 Python 版本有要求。常见做法是用 Python 3.8 或 3.9,太新的版本有些依赖包还没跟上。先确认你的 Python 版本:

python --version # 如果低于 3.8,建议用 conda 建一个独立环境 conda create -n yolov5_deepsort python=3.8 conda activate yolov5_deepsort

然后安装核心依赖。这个包大概率会带一个requirements.txt,如果有就直接装:

pip install -r requirements.txt

如果没有,按这个项目类型,手动装这几个是跑不掉的:

pip install torch torchvision opencv-python numpy scipy pip install easydict pyyaml tqdm matplotlib

torch的安装要注意和 CUDA 版本匹配,如果你有 NVIDIA 显卡并且装了 CUDA,去 PyTorch 官网选对应版本;没有显卡就用 CPU 版,推理会慢但功能不受影响。opencv-python是视频读取和绘制的核心,easydict和pyyaml是 DeepSORT 配置文件解析用的,缺一个都会在导入阶段报错。

3.2 权重文件与配置文件检查

YOLOv5 的权重文件(通常是.pt格式)和 DeepSORT 的 ReID 模型权重(.t7或.pt)需要确认路径正确。打开deep_sort/configs/deep_sort.yaml,检查REID_CKPT指向的文件是否存在。常见报错是FileNotFoundError或者RuntimeError: Error(s) in loading state_dict,前者是路径不对,后者多半是权重文件和网络结构不匹配。

YOLOv5 这边,如果你用的是包内自带的权重,确认attempt_load里的路径和实际文件名一致。如果打算用自己训练的模型,注意类别数要和代码里的nc参数对上,车辆检测一般是 1 类(car)或者多类(car, bus, truck),类别数不对会在后处理时索引越界。

3.3 用 tutorial.ipynb 分步验证

环境配好之后,先打开tutorial.ipynb。这种教程文件的价值在于它把整个流程拆成了可独立运行的单元格,你可以逐格执行,定位问题出在哪一步。一般顺序是:导入依赖 → 加载模型 → 读取视频 → 单帧检测测试 → 完整跟踪循环 → 结果保存。

如果某个单元格报错,先看错误类型。ModuleNotFoundError是依赖没装全,CUDA out of memory是显存不够,把use_cuda改成False或者换小一号的模型(比如从yolov5m换到yolov5s)。AssertionError多半是输入尺寸或通道数不对,检查letterbox的new_shape参数和模型期望的输入是否一致。

3.4 命令行推理与结果输出

如果不想用 notebook,也可以直接跑推理脚本。这类项目一般会提供一个detect.py或者track.py:

python track.py --source test_video.mp4 --weights weights/yolov5s.pt --conf 0.5 --save-txt --save-vid

--source可以是视频文件路径,也可以是0表示调用摄像头。--conf是检测置信度阈值,--save-txt会把每帧的跟踪结果存成文本,方便后续做统计。--save-vid输出带标注的视频。如果你要做车流量统计,先跑一遍保存结果,再写一个后处理脚本读文本文件做计数,比在推理循环里直接统计更容易调试。

提示:第一次跑先用一段短一点的视频,比如 10 到 20 秒,确认整个流程通了再换长视频。长视频跑一半报错,前面的时间就白费了。

4. 避坑与排查:ID 跳变、显存溢出、统计重复这些坑怎么填

4.1 跟踪 ID 频繁跳变

现象:同一辆车在连续几帧里 ID 从 1 变成 5 又变成 12,统计结果完全不可用。

原因:DeepSORT 的外观特征匹配不够稳定,常见诱因是检测框抖动太大、max_dist设得过大导致错误匹配、或者max_age太小导致轨迹被提前删除。

解决:先把max_dist从默认值往下调,试 0.15 到 0.2;把max_age提到 50 到 70,给遮挡留更多恢复时间;检查 YOLOv5 的conf_thres,如果设得太低(比如 0.2),大量低质量检测框会干扰匹配,提到 0.45 到 0.5 通常能明显改善。

4.2 显存溢出导致推理中断

现象:跑了几十帧之后报CUDA out of memory,程序崩溃。

原因:DeepSORT 的特征库nn_budget设得太大,或者视频分辨率太高导致 YOLOv5 输入尺寸过大。

解决:把nn_budget从默认的 100 降到 50;在 YOLOv5 预处理阶段把new_shape从 1280 降到 640;如果还不行,在推理循环里加torch.cuda.empty_cache(),或者直接切到 CPU 模式跑。CPU 模式慢但不会崩,适合调试阶段用。

4.3 车流量统计重复计数

现象:一辆车过去,计数器加了 2 甚至 3。

原因:车辆在检测线附近速度慢或者有轻微倒车,中心点反复穿越检测线。

解决:给每个 track_id 加一个状态标记,只有从未计数状态变为已计数状态时才加一;或者要求中心点连续 3 帧都在检测线另一侧才确认穿越。后一种更稳,但会引入几帧的延迟,对实时性要求不高的毕设场景完全可接受。

4.4 违停检测误报

现象:正常行驶的车辆被标记为违停。

原因:停留计时器没有排除车辆在路口等红灯的情况,或者检测区域画得太大,把正常车道也包进去了。

解决:把违停检测区域缩小到路边或禁停区,不要覆盖整个画面;给计时器加一个速度判断,如果车辆中心点位移超过阈值就重置计时器,只有低速或静止才累加停留时间。

4.5 视频读取失败或帧率为零

现象:cv2.VideoCapture返回False,或者读出来的帧是空的。

原因:视频编码格式不被 OpenCV 支持,或者文件路径里有中文。

解决:先用ffmpeg把视频转成 H.264 编码的 mp4,路径全部用英文;如果是在 Linux 服务器上跑,确认装了libopencv-dev和对应的视频解码库。这个坑很隐蔽,因为文件明明存在,但 OpenCV 就是读不了。

5. 进阶用法:换模型、调参和把跟踪结果接进自己的业务逻辑

5.1 替换成自己训练的 YOLOv5 模型

如果你想检测特定车型或者加一些自定义类别,可以自己标注数据训练 YOLOv5,然后把导出的.pt替换掉原来的权重。关键点是类别数和类别名称要同步改。在detect.py或track.py里找到解析检测结果的地方,把names字典改成你自己的类别映射。如果只检测车辆,nc=1,类别名写['vehicle']就行。训练自己的数据集时,注意标注格式用 YOLO 的 txt 格式,每行是class_id x_center y_center width height,坐标要归一化到 0 到 1。

5.2 DeepSORT 参数调优的实操顺序

调参不要一次改一堆,按影响从大到小来。先调conf_thres,这个决定哪些检测框进入跟踪器,影响最大。然后调max_dist和max_iou_distance,这两个控制匹配的宽松度。再调max_age和n_init,这两个影响轨迹的生命周期。最后调nn_budget,这个主要影响内存和长时跟踪的稳定性。每次只改一个参数,跑同一段测试视频,对比 ID 切换次数和统计结果。我一般会准备一段包含遮挡、并线、停车等场景的测试视频,专门用来验证参数改动有没有引入新的问题。

5.3 把跟踪结果接入业务逻辑的通用模式

不管你是做车流量统计、违停检测还是逆行判断,通用的模式是:维护一个track_id到状态字典的映射,每帧更新状态,然后根据状态做业务判断。下面是一个简化的状态管理框架:

from collections import defaultdict class TrackState: def __init__(self): self.first_seen = None # 首次出现时间 self.last_seen = None # 最后出现时间 self.counted = False # 是否已计数 self.positions = [] # 历史位置,用于方向判断 self.stationary_frames = 0 # 静止帧计数 track_states = defaultdict(TrackState) def update_business_logic(track_id, center_x, center_y, timestamp): state = track_states[track_id] if state.first_seen is None: state.first_seen = timestamp state.last_seen = timestamp state.positions.append((center_x, center_y)) # 只保留最近 30 帧的位置,避免内存无限增长 if len(state.positions) > 30: state.positions.pop(0) # 判断是否静止:最近 10 帧位移小于阈值 if len(state.positions) >= 10: recent = state.positions[-10:] dx = max(p[0] for p in recent) - min(p[0] for p in recent) dy = max(p[1] for p in recent) - min(p[1] for p in recent) if dx < 5 and dy < 5: state.stationary_frames += 1 else: state.stationary_frames = 0 # 违停判断:静止超过 300 帧(约 10 秒,按 30fps 算) if state.stationary_frames > 300: return 'illegal_parking' return None

这个框架的好处是把跟踪和业务判断解耦了,你换跟踪器或者改业务规则都不用动核心逻辑。positions列表只保留最近 30 帧,防止长时间运行内存涨上去。stationary_frames用连续帧计数而不是时间戳,是因为视频帧率可能变化,用帧数更稳定。

5.4 验证跟踪效果的一个笨办法但很管用

调完参数之后怎么知道效果变好了?我的习惯是:选一段 30 秒左右的测试视频,手动数一下画面里出现了多少辆车,然后跑跟踪,看输出的 track_id 总数和手动数的差多少。如果 track_id 总数远大于实际车辆数,说明 ID 跳变严重;如果远小于,说明漏检或者轨迹合并了。这个办法不精确,但比盯着画面看直观得多。另外,把--save-txt打开,用脚本统计每个 track_id 的持续帧数,正常跟踪的车辆持续帧数应该和它在画面里出现的时长成正比,如果大量 track_id 只持续几帧就消失,说明n_init或者max_age需要调。

从那以后我每次拿到一个新的跟踪项目,都会先跑一遍这个笨办法,把 ID 稳定性摸清楚再动业务逻辑。希望这份拆解能帮你少走点弯路,顺利把毕设跑通。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询