news 2026/10/19 5:19:50

YOLOv5+DeepSORT车辆检测跟踪:数据集与工程避坑实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5+DeepSORT车辆检测跟踪:数据集与工程避坑实践

简介:这是一份面向计算机视觉学习者与智能交通开发者的车辆检测与多目标跟踪项目资源,以YOLOv5作为检测器、DeepSORT作为跟踪器,解决实时场景中车辆识别、遮挡与重叠情况下的稳定追踪问题,并附带了经过整理的数据集,降低了复现和二次开发门槛。压缩包共2000个文件,整体约29.76MB,主要文件类型包括1588个xml标注文件、409个txt标签或配置文件、2个md说明文档及1个Python脚本(split_train_val.py),覆盖了数据集标注、训练集/验证集划分、项目说明等核心环节,结构清晰便于查找使用。目前已有150人学习下载。资源内包含训练好的模型权重、完整数据集与划分脚本,结合README文档,读者可快速掌握YOLOv5+DeepSORT的工程实现细节,并直接用于模型微调、算法对比或智能交通场景验证;同时,car_net系列txt文件与xml标注一一对应,既能用于检查数据质量,也可作为自定义数据处理的参考范例,对入门车辆检测和进阶多目标跟踪均有实用价值。

1. 一个YOLOv5+DeepSORT的车辆检测项目:从数据集到跟踪方案的完整拼图

监控画面里数车流量,最让人头疼的不是把车框出来,而是让框跟上每一辆车。YOLOv5每帧都能检出目标,但车辆换个角度、经过遮挡之后,位置和外观都会变,于是同一辆车在统计里变成了"新车",数据彻底失真。这份资源解决的问题就在这一层:用YOLOv5做目标检测,用DeepSORT在检测结果之上做时序关联,让每辆车从进场到出场带着稳定ID走完整个画面,还附带了一套已经按YOLO格式处理好的车辆标注数据集,拿到手就能直接开训。

想快速搭一套车辆检测+追踪Demo的开发者、做智能交通方向课题的学生、以及刚接触多目标跟踪、想拆开看清数据关联细节的人,都能在这份资源里找到自己需要的部分。它不是一份只有论文思路的空壳,而是把数据集、划分脚本、说明文档都准备齐了的落地工程。接下来我从文件结构开始拆,再讲链路、讲运行、讲避坑。

2. 技术选型与数据资产:这份资源里到底有什么

2.1 为什么是YOLOv5加DeepSORT而不是别家组合

先回答一个我经常被问到的问题:检测模型这么多,为什么这里选YOLOv5?原因主要有三个。一是它在精度和速度之间平衡得比较好,一张640x640的输入图在消费级显卡上能跑到几十帧,处理视频流完全够用;二是它的工程化程度高,训练、导出、推理一条链路都现成,拿来改数据配置就能跑;三是它的输出格式规整,每个目标给出xyxy坐标、置信度和类别ID,这些信息正好是DeepSORT需要的输入。

DeepSORT这边的定位也很明确,它不是用来替代检测器的,而是解决"跨帧匹配"的问题。纯SORT算法只靠卡尔曼滤波预测位置和IOU匹配,目标一旦被遮挡或者互相靠近,ID就乱跳;DeepSORT在SORT的框架上多引入了一路外观特征——每个检测框经过一个ReID小网络提取特征向量,匹配时同时看运动位置和外观相似度,ID稳定性明显好一截。这套"检测器+ReID+级联匹配"的管线是工业界做多目标跟踪最通用的做法,项目中把它俩拼在一起,思路很标准。

至于数据集,车这个类别在交通场景里足够单一,又足够有挑战:白天逆光、夜间灯影、车与车重叠遮挡,都是真实世界里会碰到的情况。用YOLO格式而不是VOC或COCO,省掉了训练前的标注格式转换,train/val划分也已经在脚本里做了,这是为了让人把精力放在训练和联调上。

2.2 文件清单与YOLO标注格式

我拿到这份资源后,先扫了一遍文件列表。压缩包里没有复杂的目录嵌套,核心构成是这些:

文件作用
README.md(根目录)项目说明,包括训练权重路径和运行步骤
README.md(数据目录内)数据集说明,标注格式和划分方式
split_train_val.py把标注文件按比例拆成训练集和验证集的脚本
val.txt验证集图片路径清单,每行一个图片路径
car_net_141.txt 等每一张图片对应的目标标注文件,文件名与图片同名

car_net_141.txt 这组文件就是数据资产的核心。以 YOLO 格式打开它,每一行代表图片里的一个车辆目标,标准结构是五个字段:

0 0.51875 0.361111 0.523437 0.244444

第一列是类别ID,这个项目只有car一个类别,所以全部是0;后面四列分别是归一化之后的中心点x、中心点y、目标宽度、目标高度。这里的"归一化"指的是坐标都已经除以了图片本身的宽和高,所以数值范围在0到1之间。car_net这个前缀大概率是采集批次或者机位命名,不影响使用。

这个格式意味着训练时不需要写任何解析代码,YOLOv5的dataset类直接就能读。val.txt里存的是验证集图片的路径列表,一行一个路径,配合数据目录里的README可以确认路径是相对路径还是绝对路径,这个细节在后面避坑章节会专门说。

2.3 split_train_val.py在做什么

这个脚本是整个数据资产的"调度中心",它的功能是把标注文件按比例拆成训练集和验证集。这类脚本每个做检测的人都会自己写,写法五花八门,但核心逻辑一致:读所有标注文件、打乱顺序、按比例切开、生成两份路径清单。我根据项目的文件结构和YOLO惯例,还原了一份最可能的实现:

import os import random def split_train_val(img_dir, label_dir, train_ratio=0.9): # 读取所有txt标注文件 labels = [f for f in os.listdir(label_dir) if f.endswith('.txt')] random.shuffle(labels) # 打乱顺序,避免同一批车全进训练集 cut = int(len(labels) * train_ratio) # 按比例切分 train_labels = labels[:cut] val_labels = labels[cut:] with open('train.txt', 'w') as f: for name in train_labels: img_path = os.path.join(img_dir, name.replace('.txt', '.jpg')) f.write(img_path + '\n') with open('val.txt', 'w') as f: for name in val_labels: img_path = os.path.join(img_dir, name.replace('.txt', '.jpg')) f.write(img_path + '\n') if __name__ == '__main__': split_train_val('images', 'labels', 0.9)

这个脚本的逻辑很直白:先读出所有txt文件,用random.shuffle打乱;然后按train_ratio算出切割点,前90%进训练集,后10%进验证集;最后把每个txt文件对应的图片路径写进train.txt和val.txt。注意这里做了name.replace('.txt', '.jpg'),这说明脚本默认图片后缀是.jpg,如果数据集的图片实际是.png,这里就会生成一堆指向不存在文件的路径。我在实际跑的时候会先确认图片后缀再执行,这是这类脚本最容易埋雷的地方。

train_ratio这个参数决定了训练/验证的规模分配。0.9的意思是训练数据占九成,验证占一成,适合数据量中等的情况;如果数据特别多,可以改成0.95,把更多样本留给训练;如果数据量小,我一般会改成0.8,让验证集有足够样本评估模型真实水平。改完之后重新跑一次脚本,train.txt和val.txt会覆盖生成,不破坏原始标注文件,这个设计很稳。

3. 核心模块拆解:一个目标从检测框到稳定ID的完整链路

3.1 检测端:YOLOv5输出什么

YOLOv5在推理阶段输出的不是一张图,而是一组张量。用官方推理接口跑一帧,results.xyxy[0] 里每一行对应一个检测目标,内容依次是x1、y1、x2、y2、置信度、类别ID。x1y1是目标框左上角坐标,x2y2是右下角坐标,全部是像素值,不是归一化坐标。这一步的输出,就是整个跟踪系统的输入。

实际使用中我会把检测结果整理成DeepSORT能吃的格式,习惯写成这样:

results = model(frame, size=640) detections = [] for det in results.xyxy[0]: x1, y1, x2, y2, conf, cls = det.cpu().numpy() if conf > 0.4 and int(cls) == 0: # 过滤低置信度和非车辆目标 detections.append([x1, y1, x2, y2, conf])

这段代码做了两件关键的事。第一是置信度过滤,conf > 0.4,低于这个值的框直接丢弃,防止噪声送进跟踪器;第二是类别过滤,int(cls) == 0,只保留类别ID为0的车辆。这里有个容易忽略的细节:det是CUDA张量,必须先调cpu().numpy()转成numpy数组才能正常解析,否则类型上会踩坑。size=640控制输入图像缩放尺寸,越大检测越准但越慢,实时场景下640已经够用。

为什么过滤这两项很重要?DeepSORT的多目标跟踪性能不只取决于跟踪算法本身,还取决于喂进去的检测框质量。检测置信度阈值设太低,一堆误检框会变成"幽灵目标",跟踪器就得维护大量无意义的轨迹;阈值设太高又会漏掉远处的小车。项目里的配置默认建议0.4到0.5之间,这是我在车辆场景踩过很多次之后的一个中间值。

3.2 跟踪端:DeepSORT的卡尔曼滤波与级联匹配

DeepSORT的核心可以拆成三个模块:特征提取器、卡尔曼滤波器、级联匹配器。特征提取器负责把每个检测框转换成外观特征向量;卡尔曼滤波器负责预测目标在下一帧的位置,并更新状态;级联匹配负责把预测轨道和当前检测框做最优配对。这三个模块在tracker.update这个入口里被串联起来。

我通常会在自己的代码里封装一层,让调用方不用关心内部细节:

class DeepSortWrapper: def __init__(self, max_dist=0.3, max_age=70, nn_budget=100): self.extractor = build_feature_extractor() # 提取外观特征向量的ReID网络 self.tracker = Tracker(max_dist, max_age, nn_budget) def update(self, detections, frame): bbox_xywh, confs = detections # 检测框需要先转成xywh格式 features = self.extractor(frame, bbox_xywh) # 每个框提取一个特征向量 tracks = self.tracker.update(bbox_xywh, confs, features) return tracks

update方法的调用链是这样的:外部传进来一组检测框(已经转成中心点xywh格式)和当前帧图像;DeepSORT先让特征提取器对每个框做一次前向计算,得到高维特征向量;然后卡尔曼滤波对已有轨迹做状态预测,估计它们在当前帧的位置;最后级联匹配把预测轨迹和检测框做最优匹配,输出更新后的轨迹列表。

这里有几个参数决定行为差异。max_dist是max_cosine_distance,代表外观特征允许的最大余弦距离,设得越小匹配越严格,车辆外观稍有变化就匹配不上;max_age是轨迹允许丢失的最大帧数,超过这个帧数没匹配到检测框就终止轨迹,它决定跟踪器对遮挡的容忍度;nn_budget是维护特征样本库的预算,常见的deep_sort_pytorch实现里用它控制每个轨迹存储多少历史特征。这三个参数是调跟踪稳定性的核心旋钮,后面避坑章节会展开讲。

3.3 数据关联:把两类算法焊在一起的坐标系转换

检测器输出的是xyxy,DeepSORT内部要求xywh,再加上特征匹配用的代价矩阵和距离门控,这里藏着项目工程化最微妙的衔接细节。第一步是坐标系转换,xyxy转xywh只差一个减法:

def xyxy_to_xywh(bbox): x1, y1, x2, y2 = bbox w = x2 - x1 h = y2 - y1 return [x1, y1, w, h]

这个转换看起来简单,但经常翻车。YOLOv5推理时如果不加pixels参数,得到的是归一化坐标,直接喂给DeepSORT会让卡尔曼滤波的尺度全部错乱。我一般在初始化model之后马上设置conf=0.4,并明确以像素坐标系工作,从源头消除这个隐患。

第二步是构建代价矩阵。DeepSORT把每个检测框与每条预测轨迹之间的距离定义为马氏距离与外观余弦距离的加权融合:马氏距离利用卡尔曼滤波输出的协方差矩阵做门控,排除那些位置偏离太远的组合;外观余弦距离则用特征向量之间的夹角衡量相似度。两者结合后,通过匈牙利算法求出全局最优匹配。门控阈值起着"硬约束"的作用,位置差太远的检测框直接不参与匹配,这一设计在车辆密集场景里能显著降低错误关联的概率。

第三步是级联匹配的优先级。DeepSORT把轨迹按"已经连续丢失多少帧"分组,丢失帧数少的轨迹优先匹配。这种设计解决了实际场景里最常见的毛病:目标短暂遮挡后重现,如果按全局匹配来做,容易让新出现的检测框先去匹配那些已经丢失很久的旧轨迹,导致ID错乱。级联匹配保证了"新鲜"轨迹有更高的匹配优先级。

4. 环境搭建与运行:让训练好的权重在你的机器上跑起来

4.1 依赖安装与目录组织

先把环境底子打好。YOLOv5官方仓库的requirements.txt会把依赖列得很全,核心项包括torch、opencv-python、numpy、matplotlib、pyyaml。我用的是Python 3.8以上的环境,PyTorch按照自己的显卡情况选对应版本,没有GPU也能在CPU上跑推理,只是训练速度要慢很多。如果只做跟踪推理,不重新训练,那连CUDA都不一定需要,CPU跑YOLOv5s加DeepSORT也能维持在几帧每秒的可用水平。

安装完依赖,下一步是目录组织。数据这块的资源里,图片和标注文件是分开放的,我建议按YOLOv5约定整理:

dataset/ ├── images/ # 车辆图片 ├── labels/ # 对应的txt标注文件 ├── train.txt # 训练集图片路径清单 └── val.txt # 验证集图片路径清单

images和labels的对应关系靠文件名维持:images/img_001.jpg对应labels/img_001.txt。这个结构不需要额外写数据加载代码,YOLOv5的dataset实现直接按这种约定读数据。把split_train_val.py生成的两个txt文件放在dataset目录下,data.yaml里直接引用它们的路径。

4.2 训练参数与数据集配置

训练前需要写一个data.yaml配置文件,描述数据集的位置、类别数和类别名。我提供的配置长这样:

train: dataset/train.txt val: dataset/val.txt nc: 1 names: ['car']

train和val指向的是图片路径清单文件,不是图片目录;nc告诉模型有几个类别,这里只有1个;names数组按顺序给类别命名。这个yaml文件的路径会在训练命令里传进去。names的顺序非常关键,因为它决定模型预测输出的类别索引含义,训练完推理时检测结果类别ID就是按这个顺序对齐的。

配置好后启动训练:

python train.py --data car.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch-size 16

这个命令的每个参数都可以按实际情况调整。--data指定数据集配置;--weights指定预训练权重,yolov5s.pt是体积和速度折中的选择,追求精度换yolov5m或者yolov5l;--img是训练输入尺寸,640是YOLOv5的默认设置,跟推理保持一致才能保证效果;--epochs是训练轮数,100轮在小数据集上已经能看到收敛趋势,数据量大的话适当增加;--batch-size受GPU显存限制,16在消费级显卡上比较稳妥。训练完之后,权重会输出到runs/train/exp/weights/best.pt,这个路径等下推理要用。

4.3 推理与跟踪:参数怎么调

训练好检测权重之后,用DeepSORT接上做推理。整个运行环节有四个参数最常被反复调整,我列了一张自己常用的参数表:

参数作用建议值
conf_thres检测置信度阈值,过滤低质量框0.4 ~ 0.5
iou_thresNMS交并比阈值,控制重叠框取舍0.45 ~ 0.5
max_dist外观特征余弦距离上限0.2 ~ 0.3
max_age轨迹丢失后保留的最大帧数60 ~ 70

置信度阈值影响检测端送入跟踪器的目标数量,阈值太低会出现大量误检框;IOU阈值影响NMS对重叠框的处理力度,车辆密集时调高一点点能减少压制;max_dist是DeepSORT外部接口最常见的调参入口,车辆路径里有大面积相似外观时,这个值过小会导致ID反复切换;max_age决定跟踪器在目标消失后多久才放下它的轨迹,遮挡频繁的场景建议给到70以上。

启动一轮完整的推理加跟踪,我通常用这样的命令组合:

python track.py --source test.mp4 --weights runs/train/exp/weights/best.pt --conf 0.4

source可以接视频文件、图片序列或者摄像头地址,换成摄像头就是rtsp流地址。之所以强调用best.pt而不是last.pt,是因为best.pt是验证集上表现最好的那个权重文件,泛化能力通常比训练结束时的那版更强。跑完的跟踪结果在输出目录里逐帧叠加了ID框,每辆车头顶上的数字就是它的跟踪ID,通过观察这个数字的跳变情况,就能快速判断跟踪器调没调好。

5. 避坑清单:从数据训练到跟踪稳定的四个常见翻车现场

5.1 现象:训练一启动就报 class 1 has no labels

这个报错第一次见的时候会有点懵,因为它出现在训练刚启动的标签校验阶段。项目里的标注文件类别ID明明只有0,但报错说找不到类别1的标签文件。原因基本都在data.yaml:nc写成了2,而names数组可能写成了['car', 'truck'],但数据里压根没有truck的样本。解决起来很简单,把nc改成1,names改成['car'],重新启动训练。这条血泪经验提醒我,每次拿到新数据集,先打开标注文件看一眼最大类别ID,再写data.yaml,能少折腾半天。

5.2 现象:训练过程提示No labels found

训练没有报错退出,但日志里显示找不到标签,或者train.txt加载了0个样本。这种现象的原因通常是路径问题。split_train_val.py生成train.txt时写的是相对路径,而实际执行train.py的工作目录不在dataset这一层;又或者脚本里假设图片后缀是.jpg,数据集里的文件实际是.png,生成的路径全部指向了不存在的文件。解决方法是检查train.txt和val.txt里的每一行路径,在终端里执行ls逐行确认文件存在。我后来习惯在脚本里加一个校验函数,如果某一行路径对应的图片不存在,直接打印出来,宁可启动慢一点,也不能让训练默默跑在没有数据的状态下。

5.3 现象:跟踪画面里出现一堆细碎框

刚开始上手时容易为"不漏检"把置信度阈值调到0.1,结果是跟踪画面上出现大量细碎的小框,还有一些框在背景墙面、路灯杆之间飘来飘去,统计出来的车流量严重虚高。原因是置信度阈值太低,模型把一堆近似车辆的纹理当成了目标,DeepSORT不像检测器那样有自己的过滤机制,来者不拒地全维护成了轨迹。解决方法是把conf_thres调到0.4以上,同时把iou_thres适当调高。检测框质量跟不上时,调跟踪器的参数是没有意义的,得先从源头把误检压下去。

5.4 现象:同一辆车的ID在画面中反复跳变

跟踪中最容易感知到的问题就是ID Switch,一辆车明明一直开在画面里,但它头上的数字却在几个ID之间来回换。原因通常分两种。第一种是max_dist设置太小,车辆转个弯、光线变一下,外观特征向量就偏离了原轨迹的阈值范围,DeepSORT认定它是新目标;第二种是max_age设置太短,车辆被短暂遮挡后轨迹就被销毁了,等目标重新出现时,只能以一个新ID注册。解决方法是先把max_dist调到0.3到0.5之间,再把max_age放宽到70帧,同时确认送入跟踪器的图像分辨率保持一致。如果做了图像缩放预处理,前后帧不一致会让ReID提取的特征本身就不稳定,参数怎么调都救不回来。

6. 二次开发:把它改造成自己的车辆跟踪系统

6.1 扩展多类别:从car到car、truck、bus

如果用这套系统做路口统计,单一个car类别通常不够用,货车、公交车、行人都有统计价值。改造的第一步是数据侧:把其他类别的标注补进txt,第一列换成对应的类别ID。第二步是改data.yaml:

nc: 3 names: ['car', 'truck', 'bus']

第三步是检测端的类别过滤条件要跟着改,如果项目的数据集里还没有其他类别的样本,可以先用这版单类权重做迁移学习,把预训练权重在新增数据集上微调几轮,收敛速度比从零训练快得多。

6.2 接RTSP摄像头流:从离线视频到实时统计

把source从视频文件换成摄像头地址,命令一行就能搞定,但这里有个实际的工程问题:视频文件可以反复回放,摄像头画面如果线程阻塞处理不过来,视频流会越积越卡。我的做法是在读取端和推理端之间加一个队列,读帧线程只管往队列里放,推理线程只管从队列取,队列满的时候丢最旧的一帧,保证实时性。实时场景还需要把计数逻辑做成增量式的:维护上一帧所有确认轨迹的ID集合,拿当前帧的ID集合做差集,新出现的ID就是进入画面的车辆,这个差分统计法是车流统计最简单的落地方式。把ID集合和统计结果写成CSV落盘,后续做报表也就顺理成章了。

这套项目拆下来,我最深的体会是数据集质量决定训练上限,数据关联细节决定跟踪下限,算法本身反而是最不容易出错的那一环。从那以后,我每次拿到类似项目,都会先检查标注格式是否归一化、验证集路径是否写死、检测框坐标是否像素级,再谈调参和二次开发。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 5:19:21

ICMP隧道实战:突破防火墙策略的内网渗透与检测思路

Windows 出口防火墙只留了一个 ping 的放行策略,TCP 全部拒绝;DMZ 到核心生产网段之间的 ACL 也只开放了 ICMP;目标机器上连 445、3389、53 都试过了,全部不通——这是我在授权红蓝对抗中遇到过的真实卡点。如果你的第一反应是&qu…

作者头像 李华
网站建设 2026/10/19 5:19:09

基于医学图像处理的肺结节检测:从预处理到两阶段流水线实战

简介:这份资源面向医学图像处理与深度学习方向的初学者及研究者,聚焦肺部CT影像中肺结节的自动检测问题,提供了一套可复现的完整项目代码。包内共26个文件,以Python脚本、Jupyter Notebook、PNG图示和说明文档为主,压缩…

作者头像 李华
网站建设 2026/10/19 5:18:18

基于深度学习的滚动轴承故障诊断毕设实战:CWRU数据预处理与CNN/DNN模型全链路解析

简介:这份资源是面向高校学生与深度学习入门者的滚动轴承故障诊断毕业设计完整项目包,围绕振动信号采集、预处理、模型构建、训练调优、故障诊断预测及结果可视化六个环节展开,适合作为课程设计、毕业设计或故障诊断方向入门实践的参考方案。…

作者头像 李华
网站建设 2026/10/19 5:18:18

WinGet完整指南:从下载安装到批量部署与自动化更新

Windows 自带的应用商店确实解决了一部分问题,但说实话,对于每天要装十几个软件、或者是运维一堆电脑的朋友来说,一个个去官网下载安装包,再点“下一步”的旧流程还是太耗费时间了。装上 WinGet 之后,我现在的习惯变成…

作者头像 李华
网站建设 2026/10/19 5:18:09

Vibe-Coding实战:五端协同开发的关键流程与踩坑复盘

今年圈子里聊开发,几乎绕不开 Vibe-Coding 这个词。给它一个不那么玄的解释:你把需求用自然语言讲清楚,AI 负责把代码生成出来,开发者负责拆需求、验结果、修边界,整个过程像顺着节奏走一样。真正让我改观的是手头一个…

作者头像 李华
网站建设 2026/10/19 5:18:01

AI编程教程仓库被AI助手推荐:内容组织与AI友好设计实操

1. 一个教程仓库被AI助手推荐,意味着什么先交代背景。我在代码托管平台上维护了一个AI编程入门教程仓库,从零开始写起,面向的是完全没接触过AI辅助编程的开发者。写了大概大半年,Star数慢慢爬到了两万出头。这个数字在开源圈不算什…

作者头像 李华