☰
YOLO车辆检测数据集:VOC/COCO/YOLO格式转换与训练全流程
2026/10/2 8:33:31 网站建设 项目流程

简介:本资源面向计算机视觉入门与进阶开发者,提供一套真实场景下的YOLO车辆分类检测数据集,可用于目标检测模型的训练、验证与调优,适合课程设计、毕业设计及算法练习等场景。压缩包共2000个文件,约51.91MB,包含1250个xml标注文件、741个txt标签文件,以及少量html教程、py脚本与yaml配置文件,分别对应VOC、COCO和YOLO三种标签格式,可直接接入YOLO系列检测框架。资源附赠数据集划分脚本,支持按需生成训练集、验证集与测试集,并配有环境搭建与训练教程,帮助读者快速跑通从数据准备到模型训练的全流程。目前已有621人学习下载,标注质量较高,场景覆盖丰富,能有效减少数据清洗与格式转换的重复工作,为车辆检测任务提供开箱即用的数据基础。

1. 从一份车辆数据集说起:1000 张图、三种标签格式到底解决了什么

做车辆检测项目的人,十有八九卡在同一个地方:模型代码抄好了,环境也配通了,结果手里没有一份能直接喂进去的数据。公开数据集要么类别太粗(只有 car 一个类),要么标注格式跟手头框架对不上,要么图片数量少到训不出东西。这份「YOLO 车辆分类检测数据集」把三件事一次性打包了:1000 张车辆图片、voc/coco/yolo 三种格式标签、划分脚本和训练教程。它解决的不是算法问题,而是从「有图」到「能训」之间那段最磨人的脏活。

这份数据适合谁?刚入门目标检测、想跑通第一个自定义数据集的新手;需要快速验证某个 YOLO 改进点、不想在数据准备上耗时间的熟手;以及做车辆计数、违章抓拍、停车场管理等落地场景、需要一份可复现基线数据的工程师。1000 张的规模不算大,但足够跑通全流程、验证 pipeline 是否正确。真正值钱的是那套格式转换和划分脚本——它把 VOC 的 XML、COCO 的 JSON、YOLO 的 TXT 之间的映射关系摊开给你看,理解了这套映射,以后换任何数据集都是同一套逻辑。

2. 三种标签格式的底层差异:为什么同一批图要存三份

2.1 VOC、COCO、YOLO 的坐标表示到底差在哪

很多人以为三种格式只是文件后缀不同,其实坐标系的定义完全不一样,搞混了就是框全歪。VOC 用绝对像素坐标,存的是左上角和右下角两个点(xmin, ymin, xmax, ymax),写在 XML 里,一个目标一个<object>节点。COCO 用绝对像素坐标,但存的是左上角加宽高(x, y, width, height),全部塞进一个 JSON 的annotations数组,靠image_id和category_id关联。YOLO 用归一化坐标,存的是中心点加宽高(x_center, y_center, width, height),四个值都除以图片宽高,范围落在 0 到 1 之间,一个目标一行 TXT。

差异的根源在于训练时框架怎么读数据。YOLO 系列在数据加载阶段直接读 TXT,归一化坐标省去了运行时除以图片尺寸的开销,还能天然适配不同分辨率的输入。VOC 和 COCO 更多是标注工具和历史数据集的默认输出,转换到 YOLO 是必经一步。理解这一点,你就明白为什么数据集要同时给三份——VOC 方便用 LabelImg 继续改标,COCO 方便接 mmdetection 这类框架,YOLO 直接开训。

格式坐标类型存储结构典型用途
VOC绝对像素 (xmin,ymin,xmax,ymax)每图一个 XMLLabelImg 标注、历史数据集
COCO绝对像素 (x,y,w,h)单个 JSON 汇总mmdetection、COCO 预训练权重
YOLO归一化 (xc,yc,w,h)每图一个 TXTYOLOv5/v8 直接训练

2.2 从 VOC 转 YOLO:转换脚本的四个关键步骤

转换的核心就一件事:把绝对坐标读出来,除以图片宽高,再算中心点。但实操里有几个地方容易翻车,下面这段脚本把关键逻辑都标出来了。

import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射:必须和 data.yaml 里的 names 顺序完全一致 CLASS_MAP = {"car": 0, "bus": 1, "truck": 2, "van": 3} def voc_to_yolo(xml_path, img_dir, out_dir): tree = ET.parse(xml_path) root = tree.getroot() # 从 XML 里拿图片尺寸,不要用 PIL 再读一遍,省 IO size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) img_name = root.find("filename").text lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in CLASS_MAP: continue # 未定义类别直接跳过,避免训练时报 index 越界 cls_id = CLASS_MAP[cls_name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 关键:先裁剪到图片边界内,再归一化 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) xc = (xmin + xmax) / 2.0 / img_w yc = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") # 写 TXT,文件名和图片名保持一致,只换后缀 txt_name = os.path.splitext(img_name)[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w") as f: f.write("\n".join(lines)) # 批量处理 for xml_file in os.listdir("annotations"): if xml_file.endswith(".xml"): voc_to_yolo(os.path.join("annotations", xml_file), "images", "labels")

逻辑说明:CLASS_MAP是整条链路最容易出错的地方,它决定了类别 id 的分配,必须和训练时data.yaml里的names列表顺序严格对应,否则模型学出来的类别全是错的。坐标裁剪那两行是后悔药——标注时手抖把框拖出图片边界是常事,不裁剪的话归一化后会出现大于 1 或小于 0 的值,YOLO 训练时虽然不一定报错,但会引入噪声框。保留六位小数是精度和文件体积的折中,四位在小目标上会有可见偏差。

参数说明:img_w、img_h从 XML 的<size>节点读取,这是 VOC 标注时自动写入的,比用 PIL 重新打开图片快得多。如果你的 XML 里没有 size 节点(某些标注工具会省略),就得改成Image.open()读取,记得加异常处理。out_dir要和图片目录分开,YOLO 训练时通过路径替换找标签,混在一起容易出问题。

2.3 COCO 转 YOLO 的坑:category_id 不连续怎么办

COCO 的category_id经常不是从 0 开始的连续整数,比如只有 1、3、7 三个类别。直接拿它当 YOLO 的类别 id 会出大问题——YOLO 要求类别 id 从 0 连续编号,中间断了会导致训练时类别数对不上。正确做法是建一个映射表,把原始category_id重映射到 0 开始的连续整数。

import json with open("annotations.json") as f: coco = json.load(f) # 建立原始 id 到连续 id 的映射 cat_ids = sorted([c["id"] for c in coco["categories"]]) id_map = {old: new for new, old in enumerate(cat_ids)} # 同时保留类别名,方便写 data.yaml names = [c["name"] for c in sorted(coco["categories"], key=lambda x: x["id"])] # 按 image_id 聚合标注 from collections import defaultdict img_anns = defaultdict(list) for ann in coco["annotations"]: img_anns[ann["image_id"]].append(ann) # 建立 image_id 到文件名的映射 img_info = {img["id"]: img for img in coco["images"]} for img_id, anns in img_anns.items(): info = img_info[img_id] w, h = info["width"], info["height"] lines = [] for ann in anns: cls_id = id_map[ann["category_id"]] x, y, bw, bh = ann["bbox"] # COCO 的 bbox 是左上角+宽高 xc = (x + bw / 2) / w yc = (y + bh / 2) / h lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {bw/w:.6f} {bh/h:.6f}") txt_name = os.path.splitext(info["file_name"])[0] + ".txt" with open(f"labels/{txt_name}", "w") as f: f.write("\n".join(lines))

逻辑说明:id_map是这段脚本的灵魂。COCO 的category_id是全局唯一标识,可能因为数据集合并、类别删除等原因变得不连续,而 YOLO 的类别 id 必须是 0 到nc-1的连续整数。names列表的顺序也要跟着id_map走,写data.yaml时直接用它。COCO 的 bbox 格式是[x, y, width, height],注意不是[xmin, ymin, xmax, ymax],转换时别搞反。

参数说明:ann["bbox"]里的宽高是绝对像素值,除以图片宽高得到归一化值。COCO 的images数组里每张图都有width和height字段,直接用,不要另外读图。如果数据集里存在iscrowd=1的标注,建议跳过,这类是密集人群之类的区域标注,不适合当普通检测目标训。

3. 数据集划分脚本:为什么随机划分会害了你

3.1 训练集验证集测试集的正确切分逻辑

拿到 1000 张图,很多人第一反应是random.shuffle然后按 8:1:1 切。如果这 1000 张图是从视频里抽帧来的,相邻帧几乎一模一样,随机切分会导致训练集和验证集里出现高度相似的图片,验证集精度虚高,上线就翻车。正确做法是先按场景或视频来源分组,再在组级别做划分,保证同一组的图片只出现在一个集合里。

import os import random import shutil from collections import defaultdict random.seed(42) # 固定种子,保证每次划分结果一致,方便复现 img_dir = "images" label_dir = "labels" out_dir = "dataset" # 按文件名前缀分组,假设命名规则是 场景_编号.jpg groups = defaultdict(list) for f in os.listdir(img_dir): if f.endswith((".jpg", ".png")): group_key = f.split("_")[0] # 按场景前缀分组 groups[group_key].append(f) group_keys = list(groups.keys()) random.shuffle(group_keys) # 按组划分,8:1:1 n = len(group_keys) train_g = group_keys[:int(n * 0.8)] val_g = group_keys[int(n * 0.8):int(n * 0.9)] test_g = group_keys[int(n * 0.9):] def copy_files(group_list, split): for g in group_list: for f in groups[g]: # 复制图片 shutil.copy(os.path.join(img_dir, f), os.path.join(out_dir, "images", split, f)) # 复制对应标签 txt = os.path.splitext(f)[0] + ".txt" src_label = os.path.join(label_dir, txt) if os.path.exists(src_label): shutil.copy(src_label, os.path.join(out_dir, "labels", split, txt)) copy_files(train_g, "train") copy_files(val_g, "val") copy_files(test_g, "test")

逻辑说明:groups字典按文件名前缀把图片分组,这是最朴素也最有效的去泄漏手段。如果你的图片命名没有规律,退而求其次可以用图片的感知哈希(pHash)做聚类,把相似图片分到同一组。random.seed(42)是血泪经验——不固定种子的话,每次跑划分脚本结果都不一样,实验没法复现,调参调到最后自己都糊涂。

参数说明:group_key = f.split("_")[0]假设文件名格式是场景_编号.jpg,如果你的命名规则不同,改这一行就行。划分比例 8:1:1 是常规做法,数据量小于 500 张时建议改成 7:2:1,验证集太小的话评估指标波动会很大。out_dir下的目录结构要符合 YOLO 的默认约定:images/train、images/val、labels/train、labels/val,YOLOv8 会自动按这个结构找标签。

3.2 划分后必须做的三项校验

划分完不是就完事了,直接开训大概率会遇到标签找不到、类别对不上、空标签文件这些问题。下面三项校验每次划分后都跑一遍,能省下大量排查时间。

第一项,图片和标签一一对应。遍历images下每个文件,检查labels下是否有同名 TXT,反过来也要查。缺失标签的图片在训练时会被跳过或报错,提前发现比训到一半崩了强。

第二项,类别 id 范围检查。读所有 TXT 文件的第一列,确认最大值小于nc(类别数),最小值大于等于 0。出现负数或超范围的值,说明转换脚本有 bug 或者类别映射表写错了。

第三项,空标签文件统计。有些图片可能确实没有目标,对应的 TXT 是空的,这是正常的。但如果空文件比例超过 20%,要么是标注漏了,要么是转换时类别过滤太狠,需要回头查。

import os def validate_dataset(img_dir, label_dir, nc): img_files = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith((".jpg", ".png"))} label_files = {os.path.splitext(f)[0] for f in os.listdir(label_dir) if f.endswith(".txt")} # 检查一一对应 missing_label = img_files - label_files missing_img = label_files - img_files print(f"缺标签的图片: {len(missing_label)}") print(f"缺图片的标签: {len(missing_img)}") # 检查类别 id 和空文件 empty_count = 0 bad_cls = [] for lf in label_files: path = os.path.join(label_dir, lf + ".txt") with open(path) as f: lines = [l.strip() for l in f if l.strip()] if not lines: empty_count += 1 continue for line in lines: cls_id = int(line.split()[0]) if cls_id < 0 or cls_id >= nc: bad_cls.append((lf, cls_id)) print(f"空标签文件: {empty_count}") print(f"越界类别 id: {len(bad_cls)}") if bad_cls[:5]: print("示例:", bad_cls[:5]) validate_dataset("dataset/images/train", "dataset/labels/train", nc=4)

逻辑说明:用集合运算做一一对应检查是最快的方式,img_files - label_files直接给出缺标签的图片名。类别 id 检查遍历每个 TXT 的每一行,虽然慢一点但值得。空标签文件单独统计,因为空文件在 YOLO 训练里是合法的负样本,但比例过高就有问题。

参数说明:nc传你的实际类别数,比如 4 类就传 4。bad_cls只打印前 5 个示例,避免刷屏。这个校验脚本建议存成check_dataset.py,每次重新划分后跑一次,养成习惯。

4. 训练教程落地:从 data.yaml 到第一个收敛的模型

4.1 data.yaml 的五个字段怎么写才不出错

YOLOv8 训练读的是data.yaml,这个文件写错一个字段,训练直接起不来。五个字段分别是path、train、val、test、names。path是数据集根目录,train、val、test是相对于path的图片路径,names是类别名列表,顺序必须和标签里的类别 id 对应。

path: /home/user/dataset train: images/train val: images/val test: images/test names: 0: car 1: bus 2: truck 3: van

逻辑说明:path用绝对路径最稳,相对路径在不同工作目录下跑容易找不到。train等字段指向的是图片目录,YOLO 会自动把images替换成labels去找标签,所以目录结构必须严格是images/xxx和labels/xxx平行。names写成字典形式,键是类别 id,值是类别名,这样比列表更不容易搞错顺序。

参数说明:如果你的数据集没有独立测试集,test字段可以删掉,YOLO 不会强制要求。names里的类别名不要用中文,虽然 YAML 支持,但训练日志和评估输出里中文容易乱码,用英文或拼音。

4.2 第一次训练的命令与关键参数

环境装好之后,一条命令就能开训。但默认参数不一定适合你的数据,下面这条命令把几个关键参数都显式指定了。

yolo detect train \ data=/home/user/dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=vehicle_exp1

逻辑说明:model=yolov8n.pt用的是 nano 版本预训练权重,1000 张图的小数据集从预训练权重微调比从头训收敛快得多。epochs=100配合patience=20,意思是 20 轮验证指标不提升就早停,避免过拟合。imgsz=640是 YOLOv8 的默认输入尺寸,如果你的图片里车辆目标很小,可以提到 1280,但显存占用会翻倍。

参数说明:batch=16在 8GB 显存上跑 640 尺寸基本够用,显存不够就降到 8 或 4。lr0=0.01是初始学习率,微调场景下这个值偏大,如果 loss 震荡厉害可以降到 0.001。project和name决定训练结果存哪,建议每次实验换个name,方便对比。

4.3 训练过程中该盯哪几个指标

训练日志刷屏,但真正需要盯的就几个。box_loss和cls_loss是训练损失,正常情况应该稳步下降,如果震荡或者不降,先查学习率和数据标签。mAP50是验证集上 IoU 阈值 0.5 的平均精度,这是最直观的指标,1000 张图 4 个类别的车辆检测,正常能到 0.85 以上。mAP50-95更严格,一般比mAP50低 10 到 20 个点。

如果mAP50在训练早期就冲到很高然后不动了,大概率是验证集和训练集有泄漏,回头查划分脚本。如果cls_loss一直很高,检查类别是否不平衡,比如 truck 只有几十张而 car 有几百张,这种情况需要加类别权重或者补充数据。

5. 避坑与排查:车辆数据集训练最常见的五个翻车现场

5.1 现象:训练启动就报 "No labels found"

原因:YOLO 按images替换成labels找标签,如果你的目录结构是train/images和train/labels,但data.yaml里写的是train: train/images,替换后变成train/labels,路径是对的。但如果标签目录名不是labels而是Annotations之类,就找不到了。

解决:统一目录命名为images和labels,data.yaml里train字段只写到images这一层,比如train: images/train。改完跑一遍第 3 章的校验脚本确认。

5.2 现象:训练 loss 正常下降但 mAP 一直是 0

原因:类别 id 映射错了。标签里的类别 id 和data.yaml里names的顺序对不上,模型学出来的预测和评估时的 ground truth 完全错位,mAP 自然为 0。

解决:打开任意一个 TXT 标签,看第一列的数字,对照data.yaml里names的键。比如标签里是0 1 2 3,names也必须是0: xxx到3: xxx。如果标签里出现了4但names只到3,说明转换时类别映射表漏了类别。

5.3 现象:验证集 mAP 很高但实际推理效果差

原因:数据泄漏。同一段视频抽的帧被随机分到了训练集和验证集,验证集里的图片和训练集高度相似,模型相当于在背答案。

解决:按第 3 章的分组划分逻辑重新切分,确保同一场景或同一视频的图片只出现在一个集合里。如果图片没有可用的分组标识,用感知哈希做相似度聚类,把相似图片分到同一组。

5.4 现象:训练到一半 loss 突然变成 nan

原因:学习率太大或者标签里有非法值。归一化坐标出现大于 1 或小于 0 的值,反向传播时梯度爆炸。

解决:先跑校验脚本查标签里的坐标范围,把所有值限制在 0 到 1 之间。然后把lr0降到 0.001 重训。如果还不行,加梯度裁剪,YOLOv8 默认没开,可以在训练参数里加clip=10。

5.5 现象:小目标车辆检测不出来

原因:输入尺寸太小。640 的输入下,如果原图里车辆只占几十个像素,缩放到 640 后目标更小,特征提取网络根本抓不到。

解决:把imgsz提到 1280,同时batch减半避免显存溢出。如果显存不够,用切片推理,把大图切成小块分别检测再合并。另一个方向是换更大的模型,yolov8s或yolov8m对小目标的特征提取能力比 nano 强。

6. 把 1000 张图用出 10000 张的效果:三个进阶技巧

第一个技巧是离线数据增强。YOLOv8 训练时自带在线增强(马赛克、翻转、色彩抖动),但每次 epoch 增强是随机的,小数据集下模型看到的多样性有限。我一般会在训练前用 albumentations 做一轮离线增强,把 1000 张扩到 3000 到 4000 张,重点加随机裁剪、亮度对比度扰动、运动模糊。车辆检测场景里,运动模糊和夜间低照度是实际部署时最常见的退化因素,提前在训练数据里覆盖这些情况,模型鲁棒性会好很多。注意增强后的标签要同步变换,albumentations 支持 bbox 参数,直接传 YOLO 格式的归一化坐标就行。

第二个技巧是用预训练权重做分层微调。yolov8n.pt是在 COCO 上训的,COCO 里本来就有 car、bus、truck 这些类,backbone 已经学到了车辆的基础特征。微调时把 backbone 的学习率设成 head 的十分之一,让 head 快速适配你的类别定义,backbone 只做轻微调整。YOLOv8 的命令行没直接暴露这个参数,需要改训练配置或者用 Python API 手动设置参数组。

第三个技巧是难例挖掘。第一轮训完,用模型在验证集上推理,把漏检和误检的图片挑出来,人工检查标注是否有问题,然后把这些图复制多份加入训练集。这个循环做两到三轮,mAP 通常能再涨 3 到 5 个点。1000 张的规模下,每一张难例的价值都很高,比盲目加数据有效得多。

# 用训练好的模型找难例 from ultralytics import YOLO model = YOLO("runs/train/vehicle_exp1/weights/best.pt") results = model.predict("dataset/images/val", save_txt=True, conf=0.25) # 对比预测结果和 ground truth,找出漏检 import os for r in results: img_name = os.path.splitext(os.path.basename(r.path))[0] pred_count = len(r.boxes) if r.boxes else 0 gt_path = f"dataset/labels/val/{img_name}.txt" gt_count = 0 if os.path.exists(gt_path): with open(gt_path) as f: gt_count = len([l for l in f if l.strip()]) if pred_count < gt_count: print(f"漏检: {img_name}, 预测 {pred_count}, 实际 {gt_count}")

逻辑说明:conf=0.25是置信度阈值,低于这个值的预测不输出。漏检判断用预测框数量小于真实框数量来粗略筛选,实际还要看 IoU 匹配,但作为第一轮筛选够用了。挑出来的图片人工确认后,复制到训练集目录重新训一轮。

参数说明:save_txt=True会把预测结果存成 YOLO 格式的 TXT,方便后续对比。conf阈值不要设太高,0.25 能覆盖大部分漏检情况,设 0.5 会漏掉很多低置信度的正确预测。

这套流程我跑过好几个车辆检测项目,1000 张起步的数据集,按上面的划分、校验、训练、难例挖掘走一遍,两周内拿到一个能用的基线模型是稳的。真正花时间的从来不是调参,而是数据准备阶段那些不起眼的格式转换和校验。把第 2 章和第 3 章的脚本吃透,以后换任何数据集都是同一套动作。希望帮到你。

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

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

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

立即咨询