ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5目标检测数据集训练全指南:从目录格式到避坑实战

YOLOv5目标检测数据集训练全指南:从目录格式到避坑实战 简介面向自动驾驶场景的目标检测数据集包采用YOLOv5目录格式组织包含十一个常见道路目标类别如卡车、行人、交通信号灯、车辆等。训练集提供两万一千零三十一张五一二乘五一二分辨率的RGB图像及对应文本标签验证集提供五千二百六十六张图像与标签边界框标注清晰每张图像存在多个目标适合自动驾驶感知与密集目标检测等方向的模型训练和算法验证。压缩包内文件总数为两千个其中一千九百九十九个为标签文本文件另有一个可视化脚本该脚本可直接运行随机传入一张图片即可绘制边界框并保存至当前目录便于快速检查标注效果。资源同时附有类别名称文本文件解压后无需额外处理即可接入主流目标检测框架训练。资源包大小约四百九十二点七三兆字节目前已有约一百七十四人学习浏览适合目标检测初学者及相关研究者参考。1. 目标检测数据集YOLOV5目录格式为什么拿到“现成”数据不等于能直接训练做过目标检测的都知道模型训不出来八成问题出在数据上而不是网络结构。一个号称“YOLOV5目录格式”的大型自动驾驶道路信息检测数据集意味着它应该已经按照 images 和 labels 分好训练集、验证集标注文件也是 YOLO 风格的 txt。但真实情况和理想之间往往隔着一条鸿沟类别编号对不对得上、标注框有没有越界、验证集和训练集有没有数据泄漏这些都会让你的训练变成一场玄学。这套数据集面向的正是自动驾驶场景下的 11 类道路目标检测适合两类人一是想快速训出一个可用的自动驾驶感知基线模型二是想研究数据组织方式、自己整理数据集但需要一个完整参考的工程师。本文会从目录结构、类别定义、验证集划分、标注格式转换到训练启动命令把每一步的操作和坑一次讲透。2. 先读懂 YOLOV5 目录格式从文件树到 11 类目标编号2.1 YOLOV5 目录结构的标准形态与变体YOLOV5 的目录格式本质上是一种约定不是 YOLOV5 框架强制的但数据加载器默认按这套约定找文件。标准的目录树是 images 和 labels 两个大目录下面各自有 train、val 子目录对应的图片和标注文件在同一级路径下共享相同的主文件名只是后缀不同。dataset/ ├── images/ │ ├── train/ │ │ ├── 00001.jpg │ │ ├── 00002.jpg │ │ └── ... │ └── val/ │ ├── 00001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 00001.txt │ │ ├── 00002.txt │ │ └── ... │ └── val/ │ ├── 00001.txt │ └── ... └── data.yaml这里有几个容易忽略的细节。第一images 里子目录的名字不一定要叫 train 和 val叫 train2017、val2017 也能跑因为 data.yaml 里可以指定绝对路径或相对路径但 labels 的子目录名必须和 images 的子目录名一一对应否则加载器会在 labels 里找不到文件直接跳过这张图。第二YOLOV5 的加载逻辑是“根据图片路径把 images 换成 labels”如果你目录名对不上不要怪训练不出模型先检查路径匹配。2.2 11 个类别的编号不是随便定的数据集标题里写了 11 类别这个数字直接决定 data.yaml 里 nc 的值。常见的自动驾驶 11 类组合是小汽车、卡车、公交车、摩托车、自行车、行人、交通灯、限速标志、其他交通标志、锥桶、施工区域或车道线。不同数据集的类别顺序会不一样比如有的把行人放在第 0 类有的把汽车放在第 0 类这本身不影响模型精度但会严重影响你后续做迁移学习或评测。# data.yaml train: /data/dataset/images/train val: /data/dataset/images/val nc: 11 names: 0: car 1: truck 2: bus 3: motorcycle 4: bicycle 5: pedestrian 6: traffic_light 7: speed_limit_sign 8: other_traffic_sign 9: traffic_cone 10: construction_barrier命名规范上有个血泪经验类名里不要带空格不要用中文不要用特殊符号。YOLOV5 的标签加载器对类名不做校验但后续可视化、导出 ONNX、用 TensorRT 部署时类名会被直接拼接进输出张量的标签空格会变成下划线中文会变成乱码到时候你对着部署日志排查半天最后发现是命名问题。类名全用小写加下划线是业界最稳的做法。2.3 标注文件里每一行的含义每张图片对应的 txt 标注文件每一行代表一个目标框格式是五个空格分隔的数字类别编号、归一化的中心点 x、归一化的中心点 y、归一化的框宽 w、归一化的框高 h。需要注意的是归一化不是按图片宽高等比缩放到 0 到 1 之间这么简单。0 0.5234 0.4812 0.0623 0.0831 5 0.7125 0.6398 0.0387 0.1245这里的数值是相对图片宽度和高度的比例。比如一张 1920x1080 的图片一个框的像素坐标是 (1005, 520, 120, 90)转换成 YOLO 格式就是中心点 x(1005120/2)/19200.5234中心点 y(52090/2)/10800.4812宽 w120/19200.0625高 h90/10800.0833。这个转换看起来简单但用脚本批量转换时最常翻车的是把中心点算成了左上角点或者忘记除以图片宽高而用的是像素值。如果你拿到的数据集是别人转好的抽查十张图手动算几个框能确认标注坐标是否可靠。3. 验证集划分11 类目标在 train 和 val 之间怎么分才不翻车3.1 随机划分与按场景划分的取舍很多人拿到数据集直接跑 scikit-learn 的 train_test_split 按文件名随机分这在自动驾驶数据上是危险的。因为自动驾驶数据往往来自连续视频抽帧相邻帧的场景高度相似随机划分会导致同一辆车、同一个路口的画面同时出现在训练集和验证集里验证指标虚高模型一旦部署到新路段就崩。正确做法是先看数据来源如果原始数据有视频序列 ID 或场景 ID按场景划分保证同一场景的帧全部落在同一侧。import os import random from collections import defaultdict # 假设每张图片文件名格式为 seq_0001_frame_0100.jpg image_dir raw_images seq_map defaultdict(list) for fname in os.listdir(image_dir): if not fname.endswith(.jpg): continue seq_id fname.split(_)[1] # 提取序列ID seq_map[seq_id].append(fname) seq_ids list(seq_map.keys()) random.shuffle(seq_ids) split_point int(len(seq_ids) * 0.8) train_seqs set(seq_ids[:split_point]) val_seqs set(seq_ids[split_point:]) # 写入 train.txt 和 val.txt with open(train.txt, w) as f: for seq_id in train_seqs: for fname in seq_map[seq_id]: f.write(f{os.path.join(image_dir, fname)}\n) with open(val.txt, w) as f: for seq_id in val_seqs: for fname in seq_map[seq_id]: f.write(f{os.path.join(image_dir, fname)}\n)这个脚本的逻辑是先按序列 ID 分组再对序列列表做随机划分而不是对图片本身做随机划分。这样能避免同一序列的连续帧被拆到两边。如果你确定数据不是视频连续帧而是来自不同来源的独立图片直接用 random.shuffle 也没问题但前提是你能确认数据来源。3.2 类别均衡校验11 类里总有一两个类被验证集遗忘划分完验证集后跑一个类别数量统计脚本这一步能救你于水火。常见的问题是某个稀有类比如施工区域整个数据集只有几十个实例随机划分时有概率全部落到训练集里验证集的 mAP 曲线会一直在某个类别上显示 NaN 或 0。import os from collections import Counter label_dir labels/val class_counter Counter() for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname), r) as f: for line in f: cls_id int(line.split()[0]) class_counter[cls_id] 1 for cls_id in range(11): print(fClass {cls_id}: {class_counter.get(cls_id, 0)} instances)如果发现某个类在验证集里实例数接近 0有两个补救方向一是手动从训练集挪一部分该类别的图片到验证集保证每个类别在验证集里至少有两位数以上的实例数二是改用分层采样按类别分布对图片进行划分而不是按文件名单纯随机。分层采样做起来稍微复杂一点因为一张图里有多个类别常见做法是先给每张图打一个“主类别”标签比如该图面积占比最大的目标类别再按主类别分层。3.3 中文文件名和路径里的大坑这个坑几乎每个从实际项目里拿数据的人都踩过。数据集里图片文件名带中文比如“路口左转_001.jpg”在 Windows 上可能没问题但一旦把数据拷到 Linux 服务器上用 YOLOV5 训练会因为编码问题导致部分图片加载失败而且失败的是静默的——训练日志里不会报错只是那个 batch 少了一张图。另一种情况是路径里有空格特别是目录名里有空格数据加载器不会帮你处理。我一般会写一个小脚本统一重命名find . -type f -name *.jpg -o -name *.txt | while read f; do newname$(echo $f | tr _ | tr -d ()) if [ $f ! $newname ]; then mv $f $newname fi done这个命令把文件名里的空格替换成下划线并删除左右括号。注意运行前先备份因为如果文件名里有多个空格有的文件会被改成相同的目标名导致覆盖。做数据清理时任何时候都要先确认操作可回滚这是底线。4. 从标注格式到训练启动标签映射与 YOLOV5 训练配置4.1 把 COCO/JSON 标注转成 YOLO 格式的核心脚本很多“现成”数据集的原始标注并不是 YOLO 格式而是 COCO 的 JSON 或者 CSV 表格卖的人帮你转好了但如果你要做二次筛选或重新划分还是得自己能转。以 COCO 格式的 JSON 转 YOLO 为例核心逻辑是用 pycocotools 加载标注遍历每个 annotation把 bbox 从像素坐标换算成归一化的中心点宽高。import json import os from PIL import Image def coco_to_yolo(coco_json_path, img_root, out_label_dir): with open(coco_json_path, r) as f: coco json.load(f) img_info {img[id]: img for img in coco[images]} anns_by_img {} for ann in coco[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_id, anns in anns_by_img.items(): img_data img_info[img_id] img_path os.path.join(img_root, img_data[file_name]) width, height Image.open(img_path).size label_name os.path.splitext(os.path.basename(img_path))[0] .txt label_path os.path.join(out_label_dir, label_name) with open(label_path, w) as f: for ann in anns: cat_id ann[category_id] x, y, w, h ann[bbox] # COCO 的 bbox 是左上角坐标转中心点 cx (x w / 2) / width cy (y h / 2) / height nw w / width nh h / height # 边界裁剪防止越界数值 cx max(0, min(1, cx)) cy max(0, min(1, cy)) nw max(0, min(1, nw)) nh max(0, min(1, nh)) f.write(f{cat_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n) coco_to_yolo(annotations.json, images, labels)这段脚本做了两个关键处理。第一COCO 里 bbox 的坐标系原点是图片左上角宽高正向是右和下所以转中心点直接用 bbox 提供的宽高而不是重新算。第二边界裁剪很重要因为有些标注框的坐标会略超出图片边界几个像素如果不裁剪训练时 YOLOV5 会报坐标异常或者在计算损失时产生 NaN。注意脚本里硬编码了 cat_id 直接作为 YOLO 类别号如果 COCO 的类别 ID 是 1 到 90 这种稀疏编号必须提前做一个映射字典把 COCO 类别 ID 映射到 0 到 10 的连续编号。4.2 训练启动命令与超参数里的三个必调项目录结构、data.yaml 都准备好后训练启动命令本身没有秘密。python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0 \ --workers 8 \ --cache val这里有几个需要按实际调整的参数。第一--cache 建议用 cache val 而不是 cache ram 或不开缓存。对于大型数据集首次训练时加载所有图片到显存或内存会占用极大空间但不开缓存每次都要从磁盘读IO 会成为瓶颈。缓存验证集是一个折中方案验证集通常只有几百到一千张占用可控还能显著加快每个 epoch 的验证速度。第二--img 尺寸不是越大越好自动驾驶场景里目标尺寸跨度大车道远处的目标很小640 是显存和精度的平衡点如果显存充裕且目标普遍较小可以试 960 或 1280。第三--batch-size 需要根据显卡显存调节没调好会直接 OOM而且 OOM 后重启训练时如果没加 --resume 会从头开始前几个小时白干。4.3 类别不平衡问题小类目标不收敛怎么调11 类的自动驾驶数据集里小汽车和行人的样本量通常是施工区域和摩托车的几十倍。YOLOV5 自带 per-class 损失权重机制但默认是全 1。如果发现摩托车和锥桶的 AP 一直上不去查看训练日志里每个类别的 AP 曲线有时需要手动调 class weights。但更实用的手段是数据增强级别的区别对待比如对小类别图片做多副本采样。YOLOV5 的官方增强参数里mosaic 增强对小目标有奇效但它在训练后期会自动关闭关闭后小类目标 AP 会掉一截这个现象不代表模型退化了而是增强策略变化导致等训练完看最终结果别中途崩溃。# hyp.yaml 中与增强相关的关键参数 mosaic: 1.0 mixup: 0.2 copy_paste: 0.3 fliplr: 0.5copy_paste 增强在目标检测里这几年很有用它会把一张图里的目标实例随机粘贴到另一张图上对小类别的样本量和位置多样性都有帮助。如果训练时显存足够mosaic 和 copy_paste 可以保留更高概率但注意这两个增强会增加训练时间每个 epoch 的耗时可能翻倍做实验时用固定 epoch 数对比不要用固定时间对比。5. 避坑记录自动驾驶 YOLOV5 数据集训练的五个常见翻车点5.1 验证集上 mAP 很高实际路测一塌糊涂现象训练日志里 mAP50 接近 0.85验证集上预测框也很准但把模型接到实际道路视频流里漏检严重尤其远处的小目标全丢。原因验证集本身就存在数据泄漏或者验证集场景过于单一。典型的情况是验证集里的图片是在同一条道路、同一个时间段采集的模型只是背下了这条路的特征而不是学会了泛化的目标识别。解决重新划分验证集强制保证验证集里包含多个路段、不同天气和光线条件的样本。更严格的做法是在训练集和验证集之间按采集时间做隔离比如前 14 天的数据进训练集后 3 天的数据进验证集模拟真实部署时面对的时间偏移。检查数据泄漏有一个快速方法训练完后用模型对训练集里随机抽取的 100 张图做预测如果训练集上的损失明显高于验证集说明验证集太简单了。5.2 训练时 loss 出现 NaN反复重启反复翻车现象loss 在前几个 epoch 正常到第 10 个 epoch 左右突然变成 NaN学习率曲线也断掉。原因最常见的是标注框坐标越界比如归一化后的中心点 x 是 1.08超过了 0 到 1 的范围。另一个原因是标注文件里的类别编号大于 nc 的值比如 nc11 但标注里出现了 11 这个编号数据加载器会把边界框滤掉但 loss 计算时可能出现维度不匹配。解决写一个标注文件校验脚本遍历所有 txt检查每一行的类别编号和坐标范围。这条建议值得每次换数据集都执行一遍。import os def validate_labels(label_dir, nc): errors [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: for line_no, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: errors.append(f{fname}:{line_no}: expected 5 values, got {len(parts)}) continue cls_id int(parts[0]) if cls_id 0 or cls_id nc: errors.append(f{fname}:{line_no}: class id {cls_id} out of range) coords list(map(float, parts[1:])) if any(c 0 or c 1 for c in coords): errors.append(f{fname}:{line_no}: coordinate out of [0,1]) return errors errs validate_labels(labels/train, 11) if errs: print(errs[:20]) else: print(All labels valid)用这个脚本跑一遍能让大多数 NaN 问题消失而且排查速度远快于盯着 TensorBoard 猜测。5.3 训练速度奇慢GPU 利用率上不去现象nvidia-smi 显卡利用率在 30% 到 60% 之间波动一个 epoch 要跑很久显存也没吃满。原因数据加载瓶颈CPU 读图和预处理速度跟不上 GPU 的推理速度。常见诱因是 images 目录和 labels 目录分别存放在不同磁盘上或者读取的是机械硬盘。解决把整个数据集放到固态硬盘上这是最直接有效的手段。然后提高 --workers 的值从默认的 8 调到 CPU 线程数的一半左右。YOLOV5 还支持 --cache ram 把整个数据集加载进内存如果内存足够比如 64GB 内存数据集 10GB这个参数会让你感受到质的飞跃但要注意首次加载时长的平均值一次性等待是值得的。5.4 训练完成后发现验证集用的是训练集的数据现象mAP 训练到后期接近 0.9但换一张训练集里没出现过的新图片去预测效果瞬间打回原形。原因data.yaml 里 train 和 val 写的是同一个路径或者写的是绝对路径但训练时工作目录切换导致路径解析错误两条路径实际上指向同一个文件夹。解决训练启动后立即检查终端日志里打印的 val 路径。YOLOV5 在启动时会打印数据集配置信息不要扫一眼就过。可以用一个一行命令确认两张路径下文件是否重叠。find /data/dataset/images/train -type f -name *.jpg | sort /tmp/train_files.txt find /data/dataset/images/val -type f -name *.jpg | sort /tmp/val_files.txt comm -12 /tmp/train_files.txt /tmp/val_files.txt | wc -l如果输出非 0说明确实有重叠需要去重。5.5 训练正常但推理时框的位置全部偏移现象训练曲线漂亮验证集上精度也正常但把模型导出为 ONNX 或 TensorRT 后部署端的检测框整体偏向左上角或者右下角。原因YOLOV5 推理时默认输出是中心点加宽高的格式转换到像素坐标时需要在 decode 层做换算相当于把归一化的数值乘回图片的原始宽高。不同框架的 decode 方式不同常见错误是把输入图片 resize 后没有记录长宽比直接用 640 当作原始图片的宽高去计算框坐标导致小图上的偏移特别明显大图上偏移量肉眼可见。解决在部署代码里将输入图片 resize 到 640 时同时记录缩放比例推理结束后按照原始宽高换算。YOLOV5 的 detect.py 里有一行 scale_coords很多部署代码直接复制了模型权重而没有复制预处理逻辑。自己写推理脚本时建议用 OpenCV 而不是 PillowBGR 通道顺序容易在导出时踩坑而且 OpenCV 的 resize 插值算法和训练时保持一致避免额外误差。6. 用最小改动验证数据集可用性从 5 类到 11 类的增量这么练拿到一个大型数据集最忌讳一上来就全量训练。正确做法是先做一个小规模冒烟测试确认数据集无硬伤再逐步放大。我的习惯是取每个类的少量图片比如每类 30 张组成一个微型训练集和微型验证集训练 20 个 epoch如果损失能下降且验证集上每类都有非零 AP说明数据集组织是健康的。样本少时训练曲线会很抖不要在 20 个 epoch 内轻易下结论只有 loss 完全不动才说明有问题。冒烟测试通过后进入增量训练阶段。这时不用再从预训练权重开始可以基于冒烟测试训练好的模型继续但要注意一个问题如果冒烟测试阶段只用了 5 类而全量数据是 11 类直接加载旧权重会因类别数不一致报错。此时要么删掉最后一层重新随机初始化要么一开始就按 11 类的 data.yaml 跑冒烟测试即使某些类只有 10 张图也无妨。我建议第二种因为第一次完整跑通了后面的大规模训练只是量的变化不会再引入结构性的坑。增量训练时最值得盯的是每个类别的单独 AP 曲线。YOLOV5 的训练日志里会输出 per-class AP观察那些稀有类摩托车、施工区的 AP 是否随着样本量增加同步上升。如果某个类的 AP 一直原地踏步大概率是标注样本里该类目标的框质量参差画框偏大或偏小都会干扰损失计算。这时候可以单独把这个类对应的图片抽出来用标注可视化工具检查边界框和目标的贴合度。最后说一个我自己的习惯每次训练之前都会把数据集的目录结构、data.yaml 配置、训练命令和分类 AP 结果记录在一个文本文件里。这不是为了给别人看而是因为十次实验后你就会忘了上次用的是什么参数、哪些坑已经踩过。训练日志太长翻找很容易倦一个精简的记录能让你每次启动实验都在别人踩坑的路上走捷径。希望帮到你。本文还有配套的精品资源点击获取
返回列表