首页 / 资讯中心 / 文章详情

YOLOV5汽车目标检测数据集格式搭建与避坑指南

YOLOV5汽车目标检测数据集格式搭建与避坑指南 ★ FEATURED ARTICLE
简介这套汽车目标检测数据集采用YOLOV5标准目录结构聚焦单类别car的车辆检测任务。训练集包含1000张图片及对应的1000个txt标签测试集包含208张图片及208个txt标签标签为YOLO格式的边界框坐标图片以jpg为主、png为辅覆盖道路、停车场、园区等多样化生活场景。数据划分清晰压缩包共2000个文件总大小约243MB主要由1200余个txt标注文件、近800张jpg/png图片、1个类别字典txt和1个可视化py脚本组成。其中可视化脚本无需修改即可运行随机传入一张图片就能绘制边界框并保存到当前目录便于快速核对标注效果。这份数据集可直接接入YOLOV5等主流目标检测框架省去数据收集与格式转换环节适合目标检测初学者、计算机视觉开发者以及需要车辆识别能力的项目使用也可作为模型训练、算法对比或教学演示的优质示例。目前已有187人学习下载是开展车辆检测实践的高性价比选择。1. 不是有图就能训YOLOV5 目录格式才是汽车目标检测数据集能跑通的关键拿到一个「目标检测数据集(YOLOV5目录格式)汽车目标检测数据集」多数人的第一反应是图片在哪、有多少张但我负责任地讲图片反而是这件事里最不值钱的部分。真正决定你能不能在一个晚上把训练跑通、把 mAP 刷到能看的水平的是目录结构是否严格对齐 YOLOV5 的读取逻辑images 和 labels 怎么分 train/val、同名 txt 放哪、data.yaml 写什么、类别编号是否错位。这套东西错一个字符训练不会报错但指标就是全 0。下文按我平时自建车辆数据集的路子从公开数据选型、目录搭建、质量体检到踩坑清单把这条链路完整走一遍。2. 汽车数据集的选型逻辑BDD100K、CCPD、UA-DETRAC 与自建怎么选2.1 先分清四类车载数据的「性格差异」做汽车目标检测最常碰到的公开数据是 BDD100K、UA-DETRAC、CCPD 和 KITTI。很多人以为它们只是标注格式不同换一下脚本就行实际上它们的差异暴露在三个维度场景构成、目标尺度、标注噪声。BDD100K 的优势是全是真实道路场景包含晴天、雨天、夜晚、遮挡、稀疏和拥挤交通共 10 万帧标注了 car、bus、truck、traffic light 等类别。它适合做泛化性要求高的模型缺点是早期版本标注有小幅错位边界框偶尔松紧不一。UA-DETRAC 是纯车辆检测数据集场景集中在城市道路和收费站带 8250 个视频的逐帧框标注单帧车辆数多、遮挡频繁适合做密集车流检测。它的成像多为监控视角和行车记录仪视角有一定差异。CCPD 专攻中国车牌场景是从停车场监控里截出来的车辆图类别只有车这一个语义但它对大规模车辆检测预训练非常友好因为样本量超过 20 万张。它的局限是视角单一全是停车场斜俯视。CrowdHuman 不是汽车专用数据但如果你要做行人车辆的混合场景它的密集行人标注可以当作难例补充。它的标注框质量很高类别只有 person不适合单独训练车辆检测器。还有一大类思路是直接用 COCO2017 的 person、car、truck、bus 四个类做裁剪。COCO 的边界框标注相对干净类别名标准化转换脚本成熟。下表是我常用来给同事说明选型的参考数据集适合场景标注格式转换成本主要坑BDD100K自动驾驶多场景泛化JSON中夜间/雨雾质量参差UA-DETRAC密集车流、监控视角MAT/XML中高视频帧去重难CCPD大规模车辆预训练文件名自带框低单类别、视角单一COCO2017多类别迁移JSON低类别多需过滤自建小样本特定场地/特定光照任意高标注一致性靠人工我一般建议先确定你的部署场景如果是园区门口的车牌识别、小区入口的车辆计数CCPD 加自建补充如果是路侧感知或无人车BDD100K 起步如果只做车流量统计UA-DETRAC 的密集帧更划算。2.2 把 BDD100K 转成 YOLOV5 目录格式转换脚本与过滤逻辑选定数据后最硬的活是把 JSON 转成 YOLO 的 txt。这一步的常见翻车点不是 JSON 解析而是坐标归一化和类别索引两个地方。下面这个脚本处理 BDD100K 的训练标注输出结构和 YOLOV5 完全对齐。import json import os from pathlib import Path # 只保留车辆相关类别按目标检测数据集(YOLOV5目录格式)的要求输出 txt CATEGORY_MAP {car: 0, truck: 1, bus: 2} def bdd100k_to_yolo(json_path, img_dir, out_label_dir): with open(json_path, r, encodingutf-8) as f: annos json.load(f) os.makedirs(out_label_dir, exist_okTrue) for item in annos: name Path(item[name]).stem # 去掉 .jpg 后缀 img_w item[width] img_h item[height] lines [] for obj in item[labels]: cat obj[category] if cat not in CATEGORY_MAP: continue # BDD100K 的 box2d 是 [x1, y1, x2, y2]注意是整数像素 x1, y1, x2, y2 obj[box2d].values() bw max(x2 - x1, 1) bh max(y2 - y1, 1) cx (x1 x2 / 2) / img_w cy (y1 y2 / 2) / img_h w bw / img_w h bh / img_h # 做一次夹紧防止坐标越界导致训练时 OpenCV 读取异常 cx, cy, w, h [max(0.0, min(v, 1.0)) for v in (cx, cy, w, h)] lines.append(f{CATEGORY_MAP[cat]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_txt Path(out_label_dir) / f{name}.txt out_txt.write_text(\n.join(lines), encodingutf-8) # 调用示例请按实际路径修改 bdd100k_to_yolo(bdd100k_labels_train.json, images/train, labels/train)这段代码的关键点有三个。第一box2d是像素坐标不除以img_w和img_h就会得到大于 1 的标签YOLOV5 会在 loss 计算时产生极不稳定的梯度。第二类别映射必须从 0 开始连续排列如果你把bus写成 3 而 data.yaml 里只有 3 个类训练不会报错但推理时类别名全错位。第三max(bw, 1)是为了防住某些标注框宽度为 0 的脏数据这类数据在公开集里不少。3. 从零搭 YOLOV5 目录格式目录结构、data.yaml 与训练集划分3.1 标准目录树一个空目录引发的连锁报错YOLOV5 的数据加载逻辑非常「死板」它只认dataset/ images/train和dataset/ labels/train这种固定路径。你要是多建一层car_images/train但没匹配上运行时会直接显示train: No labels found但训练进程不退出只是每轮都在空跑。这是新手最容易误判成数据集有问题的点实际上是目录映射没写对。我惯用的目录格式如下你可以直接照着建datasets/vehicle/ ├── vehicle.yaml ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ ├── 000002.jpg │ └── val/ │ ├── 000100.jpg └── labels/ ├── train/ │ ├── 000001.txt │ ├── 000002.txt └── val/ ├── 000100.txt注意三个硬性规则。第一图片和 txt 必须同名只允许后缀不同如果你有一张000001.png标签就必须是000001.txt。第二val 是必建目录即使你暂时不验证也要留一个空目录加占位文件否则train.py --data会因找不到 val 而中断。第三文件名不要用中文和空格YOLOV5 的多线程 DataLoader 对非 ASCII 字符的兼容性很差会出现间歇性报错。用下面这段脚本可以一键建好目录顺带把已有图片按比例搬进去。from pathlib import Path import random root Path(datasets/vehicle) for split in [train, val]: (root / images / split).mkdir(parentsTrue, exist_okTrue) (root / labels / split).mkdir(parentsTrue, exist_okTrue) # 假设原始图片都在 datasets/raw_images 下 raw_imgs list(Path(datasets/raw_images).glob(*.jpg)) random.seed(42) random.shuffle(raw_imgs) val_cnt int(len(raw_imgs) * 0.2) for idx, img in enumerate(raw_imgs): if idx val_cnt: img.rename(root / images / val / img.name) else: img.rename(root / images / train / img.name) print(ftrain: {len(raw_imgs) - val_cnt} imgs, val: {val_cnt} imgs)这里random.seed(42)保证每次划分结果一致这是定位问题的基础。如果你发现某个 epoch 的 validation 指标忽高忽低先检查是不是划分时没有固定随机种子导致每次加载的数据分布不一致。3.2 labels 文件里那 5 个数字边界条件比你想得多一个 YOLO 格式 txt 的每一行是class_id cx cy w hcx cy是归一化中心点w h是归一化宽高四者都必须是 0 到 1 之间的小数。很多从 LabelImg 转出来的标签是像素绝对坐标直接写进 txt 会让模型把 anchor 学成全图一个大框。写一个轻量校验函数可以在训练前把脏标签找出来import numpy as np def check_yolo_label(txt_path): bad_lines [] with open(txt_path, r) as f: for ln, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: bad_lines.append((ln, field count ! 5)) continue cls, cx, cy, w, h map(float, parts) if not (0.0 cls 2): # 假设只有 0/1/2 三类 bad_lines.append((ln, class id out of range)) if not all(0.0 v 1.0 for v in (cx, cy, w, h)): bad_lines.append((ln, fcoord out of range: {parts[1:]})) return bad_lines # 检查整个 labels/train 目录 from pathlib import Path for p in Path(datasets/vehicle/labels/train).glob(*.txt): issues check_yolo_label(p) if issues: print(f{p.name}: {issues[:3]})这个脚本在数据量大时尤其值钱。公开数据集经过压缩、切图、二次标注后很容易产生小数位过长或负值的标签如果没做这步训练出来的模型在推理时会把背景当车因为 loss 在引导它全图都给框。3.3 data.yaml 的路径、类别名与下载路径三个易错点YOLOV5 的 data.yaml 是唯一牵动训练、验证、推理三端配置的文件它写错了不会像 Python 那样抛异常而是悄悄加载空数据集。我的最小模板是# datasets/vehicle/vehicle.yaml path: ../datasets/vehicle # 相对于运行 train.py 的目录 train: images/train val: images/val nc: 3 names: 0: car 1: truck 2: bus三个易错点。第一path最好不要写绝对路径。绝对路径在你自己机器上没问题但数据集打包给别人或挪到服务器后直接就是找不到路径的报错而且排查时会让你误以为是文件被移动了。用相对路径让 YOLOV5 按照path拼上train和val去读是最稳的方式。第二nc必须和 labels 里实际出现的class_id最大值加一相等。比如你只有 car 和 truck 两个类别truck 的 id 是 1那么nc必须大于等于 2写成 1 不会报错但 truck 样本全部变成背景。第三很多公开数据集附带的 data.yaml 里带download:字段指向官方下载脚本。如果是在内网或离线环境用自己的数据请务必删掉这一行否则训练前 YOLOV5 会尝试访问外网下载数据卡在等待超时上看起来像训练进程挂起。4. 训练前的数据集体检可视化标注、类别分布与最小训练试跑4.1 用 OpenCV 直接画框先肉眼确认 50 张图训练前我从不直接跑 train.py而是先做一轮可视化抽检。画框脚本很简单但能暴露两类问题一是标签和图片不对应框画在空地上二是地物太小或太大导致标注张数失衡。代码逻辑是把 txt 里的归一化坐标乘回图片宽高再用矩形画出来存成_check.jpg供人工过目。import cv2 from pathlib import Path def visualize_label(img_path, txt_path, out_dir): img cv2.imread(str(img_path)) h, w img.shape[:2] with open(txt_path, r) as f: for line in f: cls, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) color (0, 255, 0) if cls 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, fcls{int(cls)}, (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) out_path Path(out_dir) / f{Path(img_path).stem}_check.jpg cv2.imwrite(str(out_path), img) # 抽查 val 里的前 50 张 val_imgs sorted(Path(datasets/vehicle/images/val).glob(*.jpg))[:50] for img_p in val_imgs: txt_p Path(datasets/vehicle/labels/val) / (img_p.stem .txt) if txt_p.exists(): visualize_label(img_p, txt_p, check_output)抽检时重点看三类图含有公交车的大尺寸目标、夜晚车辆的弱光目标、多车重叠的目标。如果这三类里有两类的框明显偏移就说明标注质量不稳定不值得直接进训练。人工标注的话我一般要求框要紧贴车身边缘留 2 到 3 个像素的余量不要为了框得全而放大很多。4.2 类别分布统计别让 99% 都是小车样本车辆数据集的另外一个暗坑是类别分布极度不均衡。停车场数据里轿车可能占 95%卡车和公交车各占 2% 和 3%。YOLOV5 的 loss 本身对不均衡有一定容忍度但极端情况下会学成只输出 car的退化模型。写一个类别统计脚本用每张图的 txt 累计各 class 的出现次数和框面积可以快速量化不均衡程度。import numpy as np from collections import Counter from pathlib import Path dist Counter() area_sum {0: [], 1: [], 2: []} # car/truck/bus for txt in Path(datasets/vehicle/labels/train).glob(*.txt): with open(txt) as f: for line in f: cls, cx, cy, w, h map(float, line.split()) dist[int(cls)] 1 area_sum[int(cls)].append(w * h) print(class count:, dict(dist)) for cls_id, areas in area_sum.items(): if areas: print(fcls {cls_id}: mean area{np.mean(areas):.4f}, fmedian{np.median(areas):.4f}, max{np.max(areas):.4f})如果某个类别的mean area比其他类小一个数量级比如 car 的框占图面积只有 0.02 而 truck 有 0.2则模型对远处小车天然不敏感。缓解方式有两种一种是后续提到的高分辨率输入或者多尺度训练另一种是直接按框面积筛选掉过于极端的数据比方说删掉占图面积小于 0.005 的框因为它们本身也不可标注。统计脚本输出的数字就是你决定要不要动数据、怎么动的依据。4.3 用一个 epoch 试跑验证数据和代码链路通不通体检完图片和标签最后一步是用极小配置跑一次完整训练链路。这一步很多人嫌浪费时间但我认为它是性价比最高的操作能同时验证数据集路径、DataLoader 缓存、loss 是否收敛、GPU 显存是否够用。我惯用的命令是python train.py \ --data datasets/vehicle/vehicle.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 3 \ --cache disk \ --noval--weights yolov5s.pt用官方预训练权重不要用随机初始化否则 3 个 epoch 什么都看不出来--cache disk先把图片缓存到磁盘避免首次训练大量读取小文件时的 I/O 等待--noval是跳过验证因为这次只检查链路不追求验证指标。跑完 3 个 epoch 后看两点第一train loss 是否在下降第二日志里labels这一行是否为 0如果labels为 0说明标签没被加载到后面跑 100 个 epoch 也是白费。5. 避坑清单汽车目标检测数据集制作与训练最容易翻车的 5 个环节5.1 loss 不降、mAP 全程为 0标签坐标没归一化现象训练日志显示 loss 从 0.09 降到 0.07 就不再动了所有类别的 mAP 全部是 0手动推理时框要么全图一个要么完全落在背景。原因这是最经典的坐标翻车。我在 2.2 节强调过YOLO 格式的cx cy w h必须是 0 到 1 的归一化值。很多人从 LabelImg 导出 XML 后只做了x2 - x1的宽度计算忘了几img_width和img_height导致标签里全是 1300、780 这种像素值。YOLOV5 不会对这类数据报错因为 loss 计算时对超过 1 的宽高也有反应但网络无法学到稳定的目标尺度。解决写一个全量遍历脚本读回每个 txt发现任意坐标大于 1 就用对应图片的宽高重新归一化。注意要先读图片尺寸再重写 txt不能只把坐标截断到 0~1那样会改变框的位置。修复后重新跑 4.3 节的单 epoch 试跑看labels是否正常、loss 是否显著下降。5.2 验证结果中所有车全变成了卡车类别编号错位现象训练的指标看起来不错mAP50 有 0.85但推理时车全部被标成 truck而手动标注明明写的是 car。原因类别编号从标注阶段就串了。比如你在第 2.2 节的CATEGORY_MAP里定义car: 1, truck: 0而 data.yaml 的names里写的是0: car, 1: truck两边的索引没有对齐。YOLOV5 的训练代码只消费标签里的数字不会反向校验类别名所以这种错位可以完整地传递到推理端。解决做一次一票否決式的抽查脚本随机取 200 张图片同时打印 txt 里的类别编号和图像上肉眼看到的车型人工核对。更省事的方式是在第 4.1 节的可视化代码里把cls直接显示成类别名而不是编号这样一旦错位字面上就穿帮。这个检查建议在数据集首次交付时做不要拖到模型训完再查。5.3 夜间车辆基本漏检数据里只有白天样本现象train 数据在白天路况下 val 指标都超过 0.9一换到夜间监控画面召回率直接跌到 0.3。原因这是数据分布和部署场景不一致的问题而不是模型问题。很多车辆数据集偏重晴天白天的停车场或高速场景夜间样本要么少要么因为低照度被标注人员删掉了。YOLOV5 在训练时虽然有 mosaic 和 HSV 扰动但色彩空间的变化幅度有限不能凭空造出灯光明亮的夜晚车灯特征。解决向 BDD100K 的 night 子集或自建夜间样本补齐训练集的场景比例。我在实际操作中会让夜间样本占训练集 30% 左右并且额外对夜间图片做一次亮度均衡预处理但不要在训练和验证时用不同的预处理函数否则指标失真。补数据后重新统计各类别占比夜间车灯和反光物体要单独看它们的标注是否紧贴车身很多标注工具在半暗环境下会把光晕也框进去。5.4 val 指标高但现场全崩训练图片分辨率远低于部署分辨率现象训练用--img 416val 指标看着还行部署时摄像头有 1080 分辨率结果小目标车辆完全检测不到。原因输入分辨率直接决定了模型能看到多小的目标。--img 416时一张 1080p 的图被压缩到 416远处一辆 60 像素宽的车在压缩后只剩 23 像素这已经接近 YOLOV5 small 的检测极限。很多车辆数据集里大量远距离目标它们的归一化框非常小低分辨率训练会直接忽略。解决训练时把输入提到--img 640或--img 1280。注意这会让显存占用翻倍甚至更多我是先跑 5 个 epoch 的--img 640观察显存再决定是否使用--img 1280。同时检查目标尺寸分布如果小于 0.03 的目标占比很高考虑对图片做切片训练但切片的边界框要重新计算不能直接沿用原图的归一化坐标。5.5 数据集拷贝到新环境训练报「No labels found」相对路径和绝对路径混用现象在自己电脑上训练一切正常把datasets/vehicle压缩包发给同事他解压后运行训练直接报train: No labels found in .../vehicle/images/train但目录里明明有文件。原因data.yaml 里的path写的是C:\Users\me\datasets\vehicle这类绝对路径或者压缩时把目录结构裁剪过导致path指向的位置不存在。YOLOV5 对数据路径的解析是先读 yaml 里的path再拼接train: images/train所以只要path无效后面全废。这不是代码问题是数据包的可移植性问题。解决按 3.3 节的规范path一律写相对路径并且打包时把 data.yaml 和 images、labels 放在同一级。如果你经常要把数据集发给别人建议再写一个verify_dataset.py在训练前自动检查 images 和 labels 的完整性列出缺失标签的图片数量和空标签的比例让接收方一眼看出数据包是否有问题。这也是我最常用的交付习惯。6. 进阶技巧用 cutout 和难例回灌把现有车辆数据扩出 3 倍有效样本当你的目标检测数据集(YOLOV5目录格式)目录已经规范、训练能跑通后下一步的核心不再是收集更多图片而是怎么让每张图贡献更多有效监督信号。我常用的三个手段按性价比从高到低排列。第一个是 YOLOV5 自带的copy_paste增强。在data/hyps/hyp.scratch-low.yaml里打开copy_paste: 0.1训练时每个 batch 有 10% 的概率把一张图的车辆目标复制到另一张图上。这对小目标尤其有效因为车辆与车辆的相对位置被强行多样化模型不会再把车必须出现在路面中央这种场景 bias 学进去。注意copy_paste对显存和训练时间的影响有限初学者可以直接开。第二个是难例回灌。训练完第一个版本后拿那个模型去跑一批你没有标注过的实拍图把置信度在 0.2 到 0.5 之间的预测框全部导出人工快速筛选出真正的车补标后加入labels/train。这一步能显著提升模型在复杂场景下的召回率比单纯增加白天样本更管用。我一般把这批补标数据单独放在一个新目录比如datasets/vehicle_hard/训练时在 data.yaml 的 train 项里写上[images/train, ../vehicle_hard/images/train]这样原始数据不会被污染随时可以回退。第三个是输入分辨率的分阶段训练。先用--img 640训 200 个 epoch 让模型收敛到大致的特征表达再用--img 1280接着训 50 个 epoch 微调。这个做法的效果接近换更大的模型但显存压力小很多。注意切换分辨率后学习率要调低一半否则前面学到的精确定位信息会被大梯度冲掉。最后说一个血泪经验每次在数据集上做增删改之前我会把当前完整目录压缩归档成vehicle_v1.0_date.zip训练实验记录也存成同名 yaml。这样模型翻车时我能快速对比是数据改了还是代码改了而不是在几百 GB 的目录里做黑匣子排查。这套习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站