简介:面向YOLO系列算法的车辆检测数据集,专注于公交车、卡车、摩托车、行人、自行车、小型车等常见交通目标,可服务于自动驾驶感知、智慧交通监控、车流量统计等场景中的模型训练与算法验证,特别适合初、中级目标检测开发者使用。该压缩包共包含2000个文件,核心文件类型为xml标注文件,压缩后整体大小约205.35MB。数据集已完成训练集与验证集划分,同时提供YOLO格式(txt)和VOC格式(xml)两套标签,并配有data.yaml配置文件,能够直接适配YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等多个主流算法版本,免去手动格式转换的麻烦。标签字段含义清晰,yolo格式记录类别索引与归一化的目标框中心点、宽高,xml格式则适合在VOC流程或自定义数据管线中灵活使用。整体目录结构规整,可直接用于目标检测入门练习,也可支撑车辆检测类项目快速原型验证。目前已有98人学习浏览,感兴趣的开发者可以下载体验。
1. 9767张车辆检测图,为什么值得你从零复现一遍数据管线
拿到这份YOLO算法-车辆检测数据集,大多数人第一时间想的是解压、训练、看mAP,但我在两个项目里用它踩出来的结论是:真正决定项目成败的不是模型选型,而是你花在标签校验和数据划分上的那几小时。标题里这9767张图像、六类目标——公交车、卡车、摩托车、行人、自行车、小型车——覆盖了城市道路和高速场景的绝大部分常见目标,适合做智慧交通、辅助驾驶的检测基线。新手可以直接拿它练手跑通YOLO全流程,熟手则能把它当作评估自己模型鲁棒性的「复测集」。本文按我的实际复现路径展开,从标签格式讲到训练参数,再到按类验收,全程不假设你有任何额外数据集,照着操作就能把训练跑起来。
2. 先读懂这份数据集的内部构成:六类分布与YOLO标签格式
2.1 六类目标的真实分布:为什么行人总是最难的那一档
在跑训练之前,我最先做的事是把每个类别的标签数量统计出来。标题只告诉你「9767张图像带标签」,不会告诉你六类样本是否存在严重不平衡,而这直接决定最后mAP的可信度。常见的做法是把标签文件读进脚本,按类别ID计数,再配合条形图观察分布。真实场景里,卡车和小型车通常占大头,摩托车和自行车偏少,行人则经常出现「样本少且多为远处小目标」的双重问题。如果你发现某类只有几百个框,另一类有两万个框,后续的增强策略和类别权重都要调整,而不是拿着默认参数直接开跑。
统计脚本很简单,把每张图的txt标签读一遍,累加每个类别出现的次数,顺带把空标签文件筛出来:
from pathlib import Path import collections labels_dir = Path("labels") empty_files = [] per_class = collections.Counter() for txt in sorted(labels_dir.glob("*.txt")): with open(txt, "r", encoding="utf-8") as f: lines = [line.strip() for line in f if line.strip()] if not lines: empty_files.append(txt.name) continue for line in lines: cls_id = int(line.split()[0]) per_class[cls_id] += 1 print("每个类别的目标数量:") for cls_id in sorted(per_class): print(f"class {cls_id}: {per_class[cls_id]}") print("空标签文件数量:", len(empty_files))这段代码的逻辑:labels目录下每个txt文件对应一张图,每个非空行是一个目标,第一列是类别ID。统计出来的数字如果和标题里的六类能对上,说明标签结构基本健康;如果类别ID超过5,说明标签里有越界类别号,这种文件训练时要么被忽略要么直接报错。int()强转在遇到非数字字符时会抛异常,这也是一个快速筛错手段。唯一值得改的参数是labels_dir的路径,如果你的标签放在别的目录下,改这一行即可。
2.2 YOLO坐标系的换算逻辑:从像素框到归一化txt
YOLO系列统一采用「类别ID + 归一化中心点 + 归一化宽高」的txt格式,每一行代表一个目标:第一列类别ID从0开始,第二三列是目标框中心点的x和y坐标(相对图像宽高的比值),第四五列是宽和高(同样是比值)。举例来说,一张1920×1080图像里一辆小型车的像素框左上角是(240,300)、右下角是(960,900),那么中心点x就是((240+960)/2)/1920=0.3125,中心点y就是((300+900)/2)/1080=0.5556,宽高分别是(960-240)/1920=0.375和(900-300)/1080=0.5556,最终写成一行为5 0.3125 0.5556 0.3750 0.5556。很多标注软件导出的标签是VOC或COCO的像素坐标,转成YOLO时就需要这一套换算。
我一般会写一个从归一化回到像素的校验脚本,把框画回原图,确认没有坐标越界,也确认框和肉眼看的位置基本一致:
import cv2 def yolo_to_pixel(line, img_w, img_h): cls_id, xc, yc, w, h = map(float, line.split()) x1 = int((xc - w / 2) * img_w) y1 = int((yc - h / 2) * img_h) x2 = int((xc + w / 2) * img_w) y2 = int((yc + h / 2) * img_h) return int(cls_id), (x1, y1, x2, y2) img = cv2.imread("images/000001.jpg") h, w = img.shape[:2] with open("labels/000001.txt", "r") as f: for line in f: if not line.strip(): continue cls_id, box = yolo_to_pixel(line, w, h) x1, y1, x2, y2 = box if x1 < 0 or y1 < 0 or x2 > w or y2 > h: print(f"越界: {line.strip()}") cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check.jpg", img)yolo_to_pixel的输入是归一化浮点值和图像宽高,返回像素坐标系下左上角和右下角。脚本的关键参数是img_w和img_h,从cv2.shape取的是(height, width)顺序,不要写反,否则框会整体错位。如果发现大量目标框集中在图像边缘或者明显没包住车体,大概率是标签导出时坐标被错误处理,例如没有除以宽高就写进了txt。这种错误在训练时不会报错,但会让模型学到一个偏移的分布,后面所有指标都不可信。
2.3 标签与图像的文件名对齐:一张图对应一个标签
训练前还要确认images和labels下的文件名一一对应。YOLO的默认约定是同一张图的标签文件与图像文件同名,仅扩展名不同,比如images/000001.jpg对应labels/000001.txt。如果解压后的命名规则不是这样,训练时要么缺正样本,要么数据加载阶段直接报错。检查方式用两行set运算:
from pathlib import Path img_names = {p.stem for p in Path("images").glob("*.jpg")} lbl_names = {p.stem for p in Path("labels").glob("*.txt")} print("缺标签的图像:", sorted(img_names - lbl_names)[:10]) print("缺图像的标签:", sorted(lbl_names - img_names)[:10]) print(f"图像 {len(img_names)} 张,标签 {len(lbl_names)} 个")缺标签说明这张图没有监督信号,会被YOLO静默跳过;缺图像说明标签是孤立文件,基本可以删除。常见原因是某次批量导出时部分图损坏,或者标注平台只导出了部分图片。我的习惯是把这个检查脚本放在数据集根目录,每次拿到新数据集先跑一遍,几秒钟就能把名字不配对的文件全部找出来,省得训练跑到一半才发现某个类别的样本数少得离奇。
3. 把9767张图拆成训练集、验证集和测试集:划分方案与校验脚本
3.1 随机划分还是目录划分:先看数据的组织方式
拿到9767张图,别急着全量训练,先把训练、验证、测试三份划分出来。常见比例是7:2:1或8:1.5:0.5,我默认用8:1.5:0.5,因为验证集只需要足够稳定地评估模型,不需要太多。如果解压后自带train/val目录,直接写配置就行;如果所有图像混在同一个images目录,就要自己写随机划分。需要注意的是,道路视频抽帧而来的数据集常有相邻帧连续出现的情况,如果它们被分到了train和val两侧,验证指标会虚高。标题没有提供序列信息,所以常见做法还是先按文件随机划分,再用重复检测脚本检查有没有高相似图像串集。
3.2 按固定随机种子划分的Python脚本
我习惯用路径列表的方式做划分,不复制图像,而是生成三个txt文件,每个文件一行图像路径。这样磁盘占用最小,换数据集也只要改路径。
import random from pathlib import Path image_dir = Path("images") all_images = sorted(image_dir.glob("*.jpg")) print("图像总数:", len(all_images)) random.seed(42) random.shuffle(all_images) train_ratio, val_ratio = 0.8, 0.15 n = len(all_images) n_train = int(n * train_ratio) n_val = int(n * val_ratio) train_set = all_images[:n_train] val_set = all_images[n_train:n_train + n_val] test_set = all_images[n_train + n_val:] def write_list(path, imgs): with open(path, "w", encoding="utf-8") as f: for img in imgs: f.write(str(img) + "\n") write_list("train.txt", train_set) write_list("val.txt", val_set) write_list("test.txt", test_set) print(f"train={len(train_set)}, val={len(val_set)}, test={len(test_set)}")这段脚本的关键参数是随机种子seed,我固定为42,保证每次划分结果一致,便于复现。如果你的环境里shuffle行为跨Python版本不同,可以先用sorted()固定列表顺序再shuffle,正是上面代码的做法。txt里写的是相对路径images/000001.jpg,这样数据集整个挪到别的机器也不用改配置。如果你改成绝对路径,则每次移动目录后都要重新生成txt。
3.3 划分后的三项校验:类别比例、重复帧、空标签
划分完成以后别直接开训练,先检查train和val里每个类别的目标数量占比。如果训练集里公交车占0.5%,验证集里公交车占5%,这个划分就是失败的,模型会被验证集的分布带偏。检查方式是把2.1节的统计脚本分别跑在train和val的标签目录上,输出比例差异,差异超过一倍就需要重新划分。
第二项是重复帧检测,视频抽帧数据集里常见。肉眼很难发现两张图是不是同一场景,用MD5可以快速找出完全相同的副本,感知哈希能找近似重复,但计算量较大,先用MD5过滤掉最严重的泄漏:
import hashlib from pathlib import Path def md5(path): return hashlib.md5(path.read_bytes()).hexdigest() groups = {} for img in all_images: groups.setdefault(md5(img), []).append(img) dups = {k: v for k, v in groups.items() if len(v) > 1} print("重复图像组数:", len(dups)) for k, v in list(dups.items())[:5]: print("重复组:", [p.name for p in v])MD5完全一致说明是同一张图的不同副本,如果分布在train和val里,验证集评估会被灌水。第三项是空标签,划分前的统计结果里如果空标签文件超过总数的3%,就要回到源数据确认是否应该过滤。这种背景图可以让模型学到「没有目标时不要乱输出」,但如果比例太高,验证集会因为大量空图拉低调用的均值,mAP的表现也会失真。
4. 用YOLOv8跑通车辆检测:数据yaml配置、训练命令与收敛判断
4.1 yaml的五个字段:类别顺序决定预测结果的正确性
YOLOv8的数据配置是一个yaml文件,里面写path、train、val、nc和names五个字段。最容易翻车的是names的顺序,它必须和标签文件里的类别ID一一对应。这份数据集的类别顺序从标题看是公交车、卡车、摩托车、行人、自行车、小型车,那names就要写成['bus', 'truck', 'motorcycle', 'pedestrian', 'bicycle', 'car'],ID 0对应bus,ID 5对应car。如果写反,训练时模型loss能正常下降,但预测出来的类别全是错的,这种「静默错误」最浪费时间。
path: /home/user/datasets/vehicle train: train.txt val: val.txt test: test.txt nc: 6 names: 0: bus 1: truck 2: motorcycle 3: pedestrian 4: bicycle 5: car一个验证yaml对不对的土办法:把val临时指向train.txt,启动一次只跑一个epoch的验证,看一眼输出里的类别名字是否和标签ID对应。注意path字段建议写绝对路径,避免相对路径与txt里的相对路径拼接后出现两级目录错位。如果你从事过多个数据集混用的项目,一定遇到过path写错报错No labels found的情况,先检查这里。
4.2 训练命令与batch、epochs、imgsz参数的实际含义
写好yaml后,一条命令即可启动训练:
yolo detect train data=vehicle.yaml model=yolov8s.pt epochs=100 batch=16 imgsz=640 device=0 workers=8参数的选择逻辑:yolov8s是速度和精度折中的选择,显存不够就换yolov8n,要求精度就换yolov8m或yolov8l。batch=16在8GB显存下比较稳,如果报CUDA out of memory,降到8或者把imgsz降到512。epochs=100对一万张以下的数据集够用,但也别只看数字,要结合loss曲线判断有没有收敛。device=0指定GPU,workers=8是加载图像的进程数,Windows下workers别超过4,否则容易引发缓冲区的卡死问题。
训练时终端输出的box_loss、cls_loss、dfl_loss三列,前几十个epoch是快速下降期,后几十个是平台期。如果loss从epoch 10开始反复震荡不降,先看是整体震荡还是单列震荡。整体震荡常见原因是学习率太高,单列如cls_loss震荡则可能是类别分布异常。每轮结束还会打印验证集mAP50和mAP50-95,这两个才是真正要盯的指标。mAP50是IoU取0.5时的平均精度,mAP50-95是在0.5到0.95区间内多个IoU阈值下的均值,后者更严格,也更反映框的定位质量。对这份数据,我训练的100轮结果里mAP50在0.85以上,mAP50-95在0.6到0.7之间,达不到就往第5章的坑里找原因。
4.3 加载预训练权重还是从零训练:两种起点的差异
model=yolov8s.pt表示用COCO预训练权重做迁移学习,这是推荐做法。车辆检测的六类里,卡车、摩托车、行人、自行车在COCO里都有对应或近似的类,预训练模型已经学到了通用特征,收敛速度和最终精度都比从零训练好。如果尝试model=yolov8s.yaml从零训练,同样的epoch数下mAP50一般会低5到10个百分点,而且训练前期loss下降非常慢。我见过有人为了「避免COCO偏见」故意从零训练,结果小目标检测反而更差,因为数据量不足以让backbone学到足够鲁棒的中层特征。
4.4 断点恢复与产物文件:best.pt、last.pt怎么用
训练中断是常态,尤其是多人共用GPU时。YOLOv8支持断点恢复,用resume=True加模型权重路径:
yolo detect train data=vehicle.yaml model=runs/detect/train/weights/last.pt resume=True训练结束后runs/detect/train/weights/下会有两个文件:best.pt是验证集指标最好的权重,last.pt是最后一轮权重。推理和验收入直接用best.pt,因为我发现车辆检测场景里最后几轮往往出现过拟合征兆,last.pt的泛化能力通常不如best.pt。如果磁盘空间紧张,last.pt可以在确认best.pt可用后删掉,省几百MB很划算。
5. 车辆检测数据集落地避坑:标签偏移、类间混淆、小目标漏检与类别不平衡
5.1 标签框偏移导致loss降不下去
现象:训练到第50轮左右,box_loss卡在0.06上下不再下降,val的mAP50反复横跳,怎么看都不像收敛。
原因:标签框和真实目标之间存在系统性偏移,常见于标注工具导出时坐标没对齐,或者图像resize后标签没有跟着变换。统计上看,一部分框偏左上,一部分偏右下,模型学到的框中心分布是散乱的。
解决:用2.2节的还原脚本随机抽查50张图,把标签框画出来与原图叠放。如果偏移有固定方向,比如所有框都偏右上10像素,那是导出代码里坐标加减反了,修正后重新导出;如果是零散偏移,就回到原始标注平台或工具链,重新导出一次标签。不要指望模型自己修正偏移,它学到的就是「带偏移的分布」,推理时照样错。这个坑最隐蔽的地方在于loss能降,指标也不算太难看,但框的位置始终不贴合车体边缘。
5.2 摩托车和自行车互相误检
现象:验证集里摩托车的precision很高但recall很低,混淆矩阵里摩托车那一行有很大一部分被预测成了自行车。
原因:这类数据集的常见标注定义问题。摩托车和自行车在较远距离时外形相似,标注员如果没有统一「看发动机/排气管」的判定口径,很容易标混。另一个原因是图像里骑手的姿态相似,模型提取到的区分特征不够。
解决:先统计两者标签数量,如果数量悬殊,考虑对少数类做过采样。更直接的做法是把两个类别的标签抽样打印出来,逐张人工复核,手动修正明显标错的框。我处理过一份类似数据集,人工复核了300张图,发现近10%的标签归类错误,修正后摩托车的AP从0.52涨到0.71。如果业务上实在不要求区分两者,也可以把两个类别ID合并成「两轮车」类,再重新评估,这种取舍在交通流量统计场景里经常出现。
5.3 远处小目标车辆漏检
现象:整体mAP50-95偏低,但把测试图拆开看,近处卡车能被检出,远处摩托车和行人几乎全漏。
原因:训练时imgsz=640,原图1920×1080被缩放到640宽后,一个原本只有20×20像素的小目标被压成7×7像素,特征彻底模糊。这是车辆检测里最常见的「小目标黑匣子」——总指标不告诉你它丢了多少小目标。
解决:第一个选项是提高训练分辨率,比如imgsz=1280,但这会加大显存占用,迭代变慢;第二个选项是训练时做随机裁剪,让模型多看到局部放大后的目标;第三个选项是推理阶段用SAHI这类切片辅助推理,把大图切成带重叠的patch分别检测再合并结果。对比下来,切片对mAP50-95的提升最明显,但推理时间也成倍增加,适合离线分析场景。我在一次路侧项目里把imgsz从640提到1280,行人的召回率从0.3提到0.52,代价是单卡训练时间从6小时变成11小时,值不值取决于你的业务实时性要求。
5.4 类别不平衡让mAP虚高的陷阱
现象:某次训练mAP50到了0.9,但把测试集按六个类别分别算AP后,发现小型车AP是0.97,行人只有0.3,验证集平均指标被多数类拉高了。
原因:数据集里小型车和卡车的框占绝大多数,模型把所有类别学成了「多数类优先」,少数类几乎不出现在正样本里。9767张图整体量级不小,但六类目标的框数量差距可能达到几十倍。
解决:三个办法。第一是给损失函数加类别权重,YOLOv8没有直接的类别权重参数,但可以用实例过采样,在dataloader里按类别欠采样或过采样;第二是数据增强时针对少数类做copy-paste,把行人或摩托车的框随机粘贴到道路上,这是我在之前一个模拟项目里用过最见效的方法,行人的AP从0.3涨到0.55;第三是调整置信度阈值,用更低的conf过滤,recall增加但precision可能下降,需要业务上能接受误检。最终我一般以按类别算出的AP列表为准,而不是单看一个mAP50数字。
5.5 图像尺寸混合导致batch内目标scale跳跃
现象:loss曲线正常下降,但val的mAP50-95一直低于预期,抽查bad case发现模型对中距离目标特别好,对特写大目标反而漏检。
原因:数据集里可能混着1920×1080的大图和800×600的小图,YOLO训练时统一resize到640×640。大图里一辆接近满屏的卡车会被等比缩小,小图里同样一辆卡车却占据不同比例。目标是同一个语义,尺度分布却非常宽,模型学起来就分裂。
解决:先统计所有图像的宽高和宽高比,如果比例集中在1.7到1.9之间,说明是统一视频尺寸抽帧,没问题;如果从4:3到16:9都有,建议按宽高比分组训练,或者把四周做填充而不是直接拉伸。直接拉伸会让物体变形,标签框与物体外形失配,这是很多人没注意到的隐性错误。my做法是统一用letterbox填充到训练尺寸,YOLOv8默认就是这么处理的,所以如果你为了省事用cv2.resize直拉图片再喂自定义管线,才会踩进这个坑。
6. 用混淆矩阵和按类AP完成模型验收:到底能不能上线的量化标准
训练完best.pt,官方val命令会给你一堆指标,但我会额外做两件事。第一件是跑一遍验证集,生成混淆矩阵和PR曲线;第二件是按类别单独统计AP,因为总体mAP会掩盖少数类问题。
yolo detect val data=vehicle.yaml model=runs/detect/train/weights/best.pt conf=0.25验证结果会出现在runs/detect/val下,其中confusion_matrix.png横轴是预测类别,纵轴是真实类别,对角线越高越好。具体技巧是看非对角线上的热点:如果摩托车和自行车之间有明显高亮块,说明类间混淆仍然严重;如果background列很高,说明误检较多,需要提高conf阈值。单独看一张混淆矩阵图还不够,还要对数值敏感一点,矩阵右下角的数值是「无目标被正确拒绝」的比例,这个数字太低说明模型在空路上乱框,项目上线的拦路虎往往在这里。
按类AP的统计,我一般用脚本直接读取验证结果:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") metrics = model.val(data="vehicle.yaml", conf=0.25, iou=0.5) names = ["bus", "truck", "motorcycle", "pedestrian", "bicycle", "car"] for i, name in enumerate(names): result = metrics.box.class_result(i) print(f"{name}: mAP50={result[0]:.3f} mAP50-95={result[3]:.3f}")这里class_result返回的列表第一列是mAP50,第四列是mAP50-95,我更看重后者,因为它同时惩罚框的定位误差。如果某一类的AP比多数类低了0.3以上,基本可以判定类别不平衡问题没解决,需要回到第5章的过采样或者copy-paste方案,而不是加训练轮数。验收的时候还有一个习惯:把bad case图导出,按类别分目录保存,下次训练前先看这些图,确认是标注错还是模型错,避免盲目调参。这个习惯是某次mAP 0.9上线后被用户反馈「老是漏摩托车」换来的血泪经验——如果你手头这个模型也是准备拿去跑夜间巡航或批量分析,请务必按类别逐项验收,别只看总平均。希望帮到你。
本文还有配套的精品资源,点击获取