简介:YOLOv5与DeepSORT结合的车辆检测与多目标跟踪项目资源,面向具备一定深度学习基础的开发者、研究人员及智能交通方向学习者,有助于解决车辆识别、跟踪及数据集准备等实际问题。压缩包共2000个文件,大小约29.76MB,核心为1588个XML标注文件,配合409个TXT标签/数据列表、MD说明文档和PY数据划分脚本,覆盖从标注数据到训练验证集划分的完整环节,已有150人学习下载。借助清晰的目录结构、README说明及划分脚本,使用者可快速理解数据组织方式,在YOLOv5框架中直接开展车辆检测训练,并结合DeepSORT算法实践多目标跟踪;基于现成数据集还可进行迁移学习、算法对比或面向智能交通监控、停车场管理等场景的二次开发,显著节省数据整理与预处理时间,适合在真实项目或课程设计中快速复用。
1. 车辆检测项目:YOLOv5 与 DeepSORT 的组合,适合直接拿去二次开发的完整资源
先说结论:这份资源不是那种只有权重文件的半成品,而是把目标检测、多目标跟踪、数据集三件事一次性凑齐了。最核心的价值在于它自带一套已经拆分好的车辆标注数据(TXT 格式的 YOLO 标签),外加一个 split_train_val.py 划分脚本,拿到手之后不用再从零标注数据,能把精力直接放到模型训练和跟踪调参上。项目主体是基于 YOLOv5 做单帧检测,DeepSORT 负责跨帧关联同一辆车,实测能稳定输出车辆的 ID 轨迹,适用于车流统计、卡口监控、停车场分析这类场景。适合正在做车辆识别相关课程设计、毕设,或者在智能交通方向做原型验证的开发者。
2. 检测与跟踪的咬合方式:为什么 YOLOv5 和 DeepSORT 必须分开跑
2.1 单帧检测与跨帧关联的分工逻辑
YOLOv5 的本质是单帧目标检测器:输入一张图片,输出这一帧里所有车辆的边界框、类别和置信度。它没有任何记忆能力,上一帧检测到一辆车,这一帧它不认识。也就是说,如果直接用 YOLOv5 对视频逐帧检测,你会得到一个不断跳动的框,但不知道哪个框是上一帧的哪辆车,更数不出「有几辆车先后通过了这条车道」这种问题。
DeepSORT 解决的就是「跨帧关联」这件事。它会把每一帧检测器输出的框当作观测值,通过卡尔曼滤波预测每个目标在当前帧的位置,再结合外观特征(一个 ReID 网络提取的特征向量)和 IOU 距离,把新检测框和历史轨迹关联起来。合并之后,每一辆车就有了唯一的 ID,从出现到消失全程保持同一个编号。
这两个模型一个管「看到什么东西」,一个管「记住谁是谁」,串联起来的结构如下:
- YOLOv5 对视频帧做检测,输出
x1, y1, x2, y2, class_id, conf的列表 - 把检测结果从原图坐标系转换成 DeepSORT 需要的格式(通常是
ltrb或cxcywh) - DeepSORT 使用这些检测框更新轨迹,决定哪些框属于已有目标、哪些是新目标、哪些轨迹已失效
- 返回带 ID 的追踪框,画到帧上输出
这样分工的好处是,检测和跟踪可以独立调参。检测不准就调 YOLOv5 的置信度阈值、训练数据;跟踪不稳定就调 DeepSORT 的级联匹配参数,互不干扰。坏处是两端都要维护,但这也是目前工程上最稳的组合。
2.2 DeepSORT 的四步状态循环:级联匹配、IOU、特征距离与轨迹生命周期
DeepSORT 对每个目标维护一条轨迹,内部用卡尔曼滤波器做状态预测,再用匈牙利算法做匹配。实际跑起来一共有四个步骤,每帧都会走一遍:
- 预测:用每个轨迹的历史运动状态,预测它在当前帧的位置
- 级联匹配:优先给最近更新过的轨迹分配检测框,解决遮挡后 ID 频繁跳变的问题
- IOU 匹配:对未匹配上的轨迹和检测框,用边界框重叠程度做最后一轮关联
- 更新:匹配上的轨迹用检测框修正预测值,未匹配的轨迹进入待删除队列
这个流程里最影响效果的是级联匹配的前置条件。代码里通常用max_cosine_distance(默认为 0.2)控制外观特征的容忍度,特征距离超过这个值就拒绝匹配。取值越大,匹配越宽松,误关联的概率上升;取值越小,匹配越严格,容易出现漏匹配导致 ID 频繁重新分配。
还有一个max_age参数,表示轨迹在失去匹配后最多保留多少帧。取 30 表示一辆车被遮挡 30 帧以内不会丢 ID,超过就被判定轨迹终止。做车辆跟踪时这个值建议调大一些,因为车流里大型车辆相互遮挡非常常见,把max_age从默认 30 调到 50~80,车辆被遮挡后重新出现的 ID 保持率会有明显改善。
2.3 YOLOv5 与 DeepSORT 的工程连接:从模型输出到追踪框
工程上连接两个模型的方式有很多,常见做法是改 YOLOv5 的detect.py,在推理循环里把检测结果送入 DeepSORT 的 tracker 对象。伪代码如下:
# tracker 初始化部分 from deep_sort_pytorch.deep_sort import DeepSort # 传入检测模型权重路径和 ReID 权重路径 # max_cosine_distance 控制外观匹配阈值 # max_age 控制轨迹在丢失匹配后的存活帧数 deepsort = DeepSort( "deepsort_pytorch/checkpoints/osnet_x0_25_msmt17.pt", max_cosine_distance=0.3, max_age=60, nn_budget=100, use_cuda=True ) # 每帧推理的主循环 for frame in video_frames: # YOLOv5 检测,得到 xyxy(左上右下)坐标的检测框 results = model(frame, size=640) detections = results.xyxy[0] # [x1, y1, x2, y2, conf, class_id] # 过滤低置信度目标,保留车辆类别 conf_mask = detections[:, 4] >= 0.4 class_mask = detections[:, 5] == 2 # 类别 2 按实际类别编号调整 vehicles = detections[conf_mask & class_mask] # 取前四列作为 bbox,第五列滤波后的置信度作为检测器得分 bbox_xyxy = vehicles[:, :4] confidences = vehicles[:, 4] # 送入 DeepSORT 更新轨迹 outputs = deepsort.update(bbox_xyxy, confidences, frame) # outputs 的结构为 [x1, y1, x2, y2, id],逐框绘制即可 for x1, y1, x2, y2, obj_id in outputs: # 绘制边界框和 ID 号 draw_box_with_id(frame, x1, y1, x2, y2, obj_id)代码里需要重点说明的是DeepSort的初始化参数。max_cosine_distance我一般设 0.3,比默认的 0.2 稍宽松,因为车辆这类目标的颜色差异相对小,特征过于严格会导致前后帧匹配不上的概率增加。nn_budget控制外观特征库的大小,设 100 即可,太大增加计算量,太小导致特征unavailable。
2.4 检测与跟踪的性能平衡
YOLOv5 和 DeepSORT 串行跑,整体速度由慢的一端决定。实际测下来,在 1080P 视频上用 YOLOv5s 加 DeepSORT 跑 CPU,大约 3~8 FPS;用 GPU(如某入门级显卡)能到 25~40 FPS。如果要把速度拉上去,有两个思路:
一是把输入分辨率从 640 降到 416 甚至 320,检测精度略有回落但跟踪不易中断。二是只对检测结果中置信度最高的前 N 个框做特征提取,减少 DeepSORT 内部 ReID 网络的推理次数。很多实际部署项目就是走这条路,效果好且改动小。
3. 数据集与脚本:TXT 标注怎么用、怎么划分训练集和验证集
3.1 数据集目录的组织方式
这份资源里的数据集部分,不是那种一张图配一个 XML 的传统 VOC 结构,而是 YOLO 系原生的 TXT 标注格式。每个标注文件对应一张图片,文件名和图片名一致,后缀是.txt。文件里每一行代表一个目标:
class_id x_center y_center width height注意这里坐标全部是归一化坐标,除以了图片宽度和高度,值域在 0 到 1 之间。以car_net_141.txt为例,内容大致如下:
0 0.482981 0.614741 0.216466 0.135039 0 0.244710 0.602345 0.147392 0.117419 0 0.374651 0.667713 0.186699 0.093452第一列的0是类别编号,如果只有汽车这一类,classes.txt里就只写一行car。后面四个数字分别是中心点 X、中心点 Y、宽度、高度,全部是归一化后的比例值。
在实际使用时,注意不要把这些坐标直接当成像素坐标传给 OpenCV 画框。需要乘回图片宽高才得到像素级的xyxy或cxcywh。这是最容易被新手踩的坑。
3.2 split_train_val.py 的数据划分逻辑
split_train_val.py的作用是把图片和标注按比例划分为训练集和验证集,并生成两个文本文件——train.txt和val.txt(项目里已经有一个val.txt存在)。这个脚本内部逻辑通常包含几个步骤:
- 扫描指定目录下所有
.txt标注文件 - 按设定比例(如 0.85 训练、0.15 验证)随机划分
- 把图片的绝对路径写入
train.txt和val.txt - 要求图片目录和标注目录结构匹配,否则报错
import os import random # 读取某个目录下所有标注文件(不含非 txt 文件) label_dir = "labels/train" labels = [f for f in os.listdir(label_dir) if f.endswith(".txt")] # 可复现划分:固定随机种子,保证每次训练的划分一致 random.seed(42) random.shuffle(labels) val_ratio = 0.15 val_count = int(len(labels) * val_ratio) train_list = labels[val_count:] val_list = labels[:val_count] # 注意:写入的是图片路径,不是标注路径 # YOLOv5 训练时通过 txt 里的路径定位图片,再自动查找对应标注文件 with open("train.txt", "w") as f: for item in train_list: img_path = os.path.join("images/train", item.replace(".txt", ".jpg")) f.write(img_path + "\n") with open("val.txt", "w") as f: for item in val_list: img_path = os.path.join("images/train", item.replace(".txt", ".jpg")) f.write(img_path + "\n")这段脚本最关键的是它把「划分依据」和「读取路径」分开了:随机打乱的是标注文件的文件名,但写入文本的是图片路径。如果不理解这一点,很容易在改脚本时把后缀名写错,导致训练时 YOLOv5 报AssertionError: train: No labels in ...。
3.3 手动核对 val.txt 的有效性
拿到项目自带的val.txt之后,推荐先检查一下里面路径是否正确。常见做法是打开文件任意取三行,逐个ls或test -f确认图片真实存在:
while read p; do if [ ! -f "$p" ]; then echo "缺失文件: $p" fi done < val.txt为什么要强调整这个?我接过不少开源数据集,有一种情况是标注、划分脚本都齐全,唯独图片被省略掉,或者图片目录名和脚本里不一致,导致val.txt里全是死路径。先跑一遍这个检查,可以帮你避开最浪费时间的数据对齐问题。
另外,TXT 标注里比较容易混入空文件,也就是 0 字节的.txt,代表这张图没有车辆。YOLOv5 训练时会把这种样本当作背景图处理,正常情况下没问题。但如果你拿到数据后自己批量处理过标签,必须确保空文件确实存在而不是缺失,缺失的话 YOLOv5 会直接跳过这张图,导致采样数量不对。
4. 训练与推理全流程:从数据到带 ID 的追踪输出
4.1 三种使用方式:直接推理、微调训练、全量重训
拿到这套资源后,按目标不同有三级玩法:
- 直接推理:把项目自带的 YOLOv5 权重和 DeepSORT 串起来,对视频做实时检测加追踪。适合快速验证效果、做 Demo
- 微调训练:基于 COCO 预训练的 YOLOv5s 权重,用项目提供的数据集训练 50~100 个 epoch,适应具体场景。适合有一批自己的同类车辆图片
- 全量重训:改模型结构或类别数从零训练。需要的数据量大,如果不是搞科研不太建议
多数人适合第二种。微调训练还有一个隐形的收益:COCO 预训练权重对交通车辆的类别比较熟,几十个 epoch 就能收敛到不错的精度,比从零训省好几个小时。
4.2 YOLOv5 训练命令与关键参数
训练阶段建议用 YOLOv5 官方仓库的train.py。命令大致如下:
python train.py \ --data vehicle.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --data-dir ./dataset \ --project ./runs/trainvehicle.yaml的内容决定了数据从哪里加载,必须和你的目录结构完全对应:
train: ./dataset/train.txt val: ./dataset/val.txt nc: 1 names: ["car"]这里的nc是类别数,因为有且只有车辆这一类,所以为 1。names列表里的名称按class_id的顺序排列,只影响可视化标签,不影响训练。切不可把nc写成你标注文件里的目标总数,那是另一个概念。
参数方面,--img 640是训练分辨率,--batch 16是批大小,--epochs 100是训练轮数。如果你的显存只有 4GB 左右,batch 调成 8,--img降到 512,不然很容易 OOM。训练时盯着val/box_loss和mAP@0.5这两项看,前者只降不升说明模型在收敛,后者达到 0.85 以上就够用在大多数车流量场景了。
4.3 推理阶段:如何输出带追踪 ID 的视频
推理时用到的是前面 2.3 节那套结构。核心命令部分可以直接用脚本跑,也可以用 YOLOv5 自带的detect.py降低门槛:
python detect.py \ --source traffic_video.mp4 \ --weights runs/train/exp/weights/best.pt \ --conf-thres 0.4 \ --iou-thres 0.45 \ --img 640 \ --save-vid--conf-thres 0.4把置信度阈值提到 0.4,比默认 0.25 高一些,可以在车辆密集区减少误检。代价是远处的弱小目标可能被过滤掉。我的习惯是先跑一遍默认参数,统计输出的漏检和误检情况,再决定要不要动阈值。
如果你想在检测完的输出上继续做跟踪,推荐把 detect 的输出接一个视频处理管道。工程上最简洁的做法是先检测存成带置信度的检测结果文件,再单独跑跟踪脚本,这样如果跟踪效果不对,不用重新跑检测。
4.4 输出的可视化数据怎么解读
追踪输出的每一帧,目标框上方会显示 ID 号,例如ID 17,表示当前帧里这辆车是第 17 个被系统识别并持续跟踪的目标。检查跟踪效果时,重点关注三件事:
- 同一辆车在整个过场过程中 ID 是否始终一致
- 两辆车交汇后 ID 有没有互换
- 车辆完全遮挡再出现时,ID 是保持原编号还是新分配
这三个现象分别对应检测框稳定性、外观特征区分度、遮挡处理能力。出现哪一个问题,优先排查的模块不同,下面避坑章节会细说。
5. 避坑:标注、训练、跟踪三层的血泪经验
5.1 标注坐标归一化翻车:框画偏了半个图
现象:用 OpenCV 或标注工具可视化时,框的位置和车辆实际位置偏差很大,有的甚至画到图外。
原因:TXT 标注用的是归一化坐标,不是像素坐标。直接用x_center * img_width还原时,如果图片尺寸读取方式和标注工具不一致(比如标注时用 1920x1080,读取时 OpenCV 给错了尺寸),整个框就错位。
解决:检查可视化脚本里的宽高读取顺序。OpenCV 读图后h, w = image.shape[:2],宽是w,高是h。归一化还原公式是x_pixel = x_center * w,y_pixel = y_center * h,不要写反。
5.2 DeepSORT 的 ReID 权重缺失,外观匹配完全失效
现象:代码能跑,但跟踪 ID 疯狂乱跳,一辆车开过去出现五六个新 ID,却没有任何报错。
原因:不少开源整合包里把 ReID 权重的下载地址放在 README 里,没有跟随代码一起打包。DeepSort()初始化时找不到权重文件不会崩溃,但此时外观特征全为空,只靠 IOU 匹配,稍微遮挡几下 ID 就断裂。
解决:先确认deepsort/checkpoints/下存在有效的.pt权重,再检查max_cosine_distance是否在 0.2~0.5 之间。另外用torch.load手动验证一下:加载权重后看输出的特征向量是否因torch.empty初始化,如果是,说明权重没真正加载成功。
5.3 车辆大面积遮挡导致 ID 断裂
现象:公交车或卡车前后交叠,车辆从大型障碍物后重新出现时被分配了新 ID,车流统计数字明显虚高。
原因:DeepSORT 的max_age太小,轨迹在遮挡期内被判为死亡,车辆重新出现时自然开新轨迹。
解决:把max_age从默认 30 提高到 60~90。同时把max_cosine_distance从 0.2 放宽到 0.3,让外观相似度匹配有更多回旋余地。这两个参数一改,ID 稳定率通常能提升 20% 以上。
5.4 训练时路径和结构的坑:No labels 报错
现象:训练启动后立刻报AssertionError: train: No labels in ...。
原因:train.txt或val.txt里的图片路径和标注目录结构不匹配。最常见的是脚本里写死了images/train前缀,但你的图片实际放在images/raw。
解决:把train.txt中的某个路径拿出去ls验证。路径必须写完整绝对路径,或用相对于yolov5目录的相对路径。用脚本批量生成的路径,一定要先抽查三行再跑训练,不要盲信脚本一次成功。
5.5 batch 参数和显存不匹配导致训练中断
现象:训练跑到一半,GPU 显存占满,进程被杀,损失几千步进度。
原因:batch 设得过大,又没有开启梯度累积。4GB 显存硬上 batch 32 大概率爆显存。
解决:先按batch 16 / img 640起跑,OOM 就减半 batch。这样损失一半训练吞吐,但至少不会废掉整个训练。条件允许时用--batch 8 --img 640加--accumulate 2,等效 batch 16 但显存占用减半。
6. 进阶技巧:置信度阈值与跟踪参数联调,让 ID 保持率再上一个台阶
很多人把「检测参数」和「跟踪参数」分得清清楚楚,实际工程里它们必须联动。YOLOv5 的conf-thres降低时检测框数量变多,DeepSORT 的匹配压力变大,误关联概率随之上升;conf-thres提高时框变少,漏检增多,轨迹得不到观测更新,ID 又容易断裂。所以调试的核心是找到两者配合的最优点。
我最常用的一条验证路径是:
- 固定
conf-thres为 0.3,max_cosine_distance为 0.3,max_age为 60 - 统计连续 500 帧内 ID 的切换次数和轨迹断裂点
- 调低
conf-thres到 0.2,观察 ID 切换次数是否上升 - 回归一步:调高
max_age到 90,再统计 ID 断裂率
实际操作中记录一张表:conf-thres、max_age、跟踪中断次数、ID 总数。这样 4 组实验下来,基本能锁定适合你视频场景的参数组合。不要凭感觉去改,参数之间是联动的,每次只动一个变量是基本原则。
另外,如果你的场景是高速卡口(车速快、遮挡极少),可以把max_age缩小到 20 左右,此时轨迹生命期短,ID 释放快,系统反应敏捷,反而适合。停车场那种多遮挡慢速场景,max_age就得 80 起步。越快的车速,越短的遮挡时间,max_age不需要太大;越慢的拥堵路段,遮挡时间长,max_age小了必丢 ID。理解了这一点,换场景调参就不会无据可依。
我自己跑车流统计时,翻车最狠的一次是全程默认参数,结果一条直路上三辆车互相超车,最后 ID 总数到了 37 个,实际只有 3 辆车。从那以后,我每次拿到新的视频数据都强制走一遍上面这套参数联调流程,先固定检测阈值然后逐个动跟踪参数,再记录 ID 稳定率。调一组参数花不了几分钟,但能省掉事后数错车的整个麻烦。这套资源正好可以拿来做这件事,希望帮到你。
本文还有配套的精品资源,点击获取