尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

香烟破损检测数据集:YOLOv5格式与训练实战指南

香烟破损检测数据集:YOLOv5格式与训练实战指南 简介面向目标检测方向研究者及工业质检工程的一款香烟破损检测数据集采用YOLOV5标准目录格式存储按6个破损类别划分涵盖头部破损、滤嘴破损等缺陷类型图片为3024×4032分辨率的高清RGB图适合直接训练与验证。数据总量388MB共803个文件包含401个jpg图片、401个对应的txt标注以及1个可视化py脚本其中训练集320张、验证集80张。标注txt按YOLO格式生成py脚本可随机读取一张图片绘制边界框并保存到当前目录方便快速检查标注质量。当前已有140人浏览学习适合刚接触YOLOV5训练流程或需要缺陷检测数据做迁移学习的开发者使用收到后无需额外转换即可开展模型训练与效果验证。1. 香烟破损检测数据集YOLOv5格式、6个类别拿到就能训练的缺陷检测资源在烟草产线的质检环节烟包侧边的破损、滤嘴缺角、头部裂口这类瑕疵靠人工肉眼很容易漏掉而且每个人判定标准还不太一样。这份「香烟破损检测数据集」就是为这个场景准备的6 个破损类别训练集 320 张、验证集 80 张全部按 YOLOv5 的目录格式存好图片是 3024×4032 的 RGB 大图旁边配好了同名的 txt 标签文件。拿到手之后不需要再做 VOC 转 YOLO 之类的格式折腾补一个 data.yaml 就能直接丢给 YOLOv5 训练。它特别适合做工业缺陷检测的工程师、拿 YOLO 跑量写毕设的学生以及第一次接触目标检测、想跑通完整流程的新手——既可以当数据资源也能当 YOLOv5 的入门练习项目。2. 数据到底长什么样YOLOv5 目录约定与标签格式拆解2.1 目录结构train/val 分离与 images-labels 配对规则拿到压缩包解压后先别急着一通操作把目录树完整看一遍。这份数据是按 YOLOv5 官方约定的 images 和 labels 分目录存放的没有额外的 JSON 或 XML省掉了从 VOC 转 YOLO 这一层麻烦。展开后大致是这样一个结构smoke_damage/ ├── images/ │ ├── train/ │ │ ├── IMG_8363.jpeg │ │ ├── IMG_8384.jpeg │ │ ├── MVIMG_20220720_111325.jpeg │ │ └── ... │ └── val/ │ ├── IMG_8354.jpeg │ └── ... ├── labels/ │ ├── train/ │ │ ├── IMG_8363.txt │ │ └── ... │ └── val/ │ ├── IMG_8354.txt │ └── ... └── visualize.py关键点是 images/train 和 labels/train 里的文件是一一对应的图片叫 IMG_8363.jpeg标签就叫 IMG_8363.txt文件名完全相同大小写也要一致。YOLOv5 在读取标注时是根据同名图片去匹配 labels 下的 txt 文件。只要后缀名不一致例如图片是 .jpeg 而标签文件被标注工具写成了 .jpg 对应关系这张图就会被训练流程自动跳过而且日志里只出现一行 warning不仔细看根本发现不了。所以拿到数据后的第一件事我建议先跑一遍配对检查。下面这段 bash 脚本可以快速找出没有对应标签的图片cd smoke_damage for f in images/train/*.jpeg; do base$(basename $f .jpeg) if [ ! -f labels/train/$base.txt ]; then echo missing label for $base fi done这段脚本的原理很简单用 basename 去掉图片路径和后缀得到文件名主干再判断 labels 目录下是否存在同名 txt。如果循环结束没有任何输出说明训练集的标签配对是完整的。实际操作时建议把*.jpeg和*.jpg都覆盖到写成for f in images/train/*.jpeg images/train/*.jpg; do这样避免两种后缀混用导致的遗漏。我当时拿到手第一轮跑出来的结果没有缺失说明数据集整理得还算规范但这一步不能省。2.2 标签txt解析YOLO格式的归一化坐标怎么读随便打开一个标签文件比如 labels/train/IMG_8363.txt内容长这样0 0.542356 0.367812 0.120534 0.098213 1 0.823450 0.451231 0.080156 0.073245每一行代表一个目标框一共 5 个值依次是类别 id class_id、中心点 x 坐标、中心点 y 坐标、框宽度、框高度。这里全部是归一化坐标范围是 0 到 1不是像素坐标。x_center 的真实含义是“目标中心点在图像宽方向上的相对位置”也就是像素中心 x 坐标除以图像宽度width 是框的像素宽度除以图像宽度height 同理。这也是 YOLO 系列格式和 VOC 格式最大的区别。VOC 的 XML 里给的是x_min, y_min, x_max, y_max的绝对像素值一旦图像分辨率变了标签就失效了。而 YOLO 的 txt 里全是相对值所以这份数据即使将来你想从 3024×4032 这个原始分辨率改成训练时常用的 640×640 输入尺寸也不用手动换算任何坐标。YOLOv5 内部会把图片 letterbox 缩放标签跟着等比缩放完全对得上。不过有一个隐患如果标签生成工具在写 txt 时搞错了归一化基准比如用宽高反了或者直接把像素坐标写进去可视化时所有框都会飞掉。为了提前排查这类问题我写了一个标签合法性检查脚本import glob import os root smoke_damage label_dirs [labels/train, labels/val] cls_set set() min_vals [1.0, 1.0, 1.0, 1.0] max_vals [0.0, 0.0, 0.0, 0.0] bad_lines 0 for d in label_dirs: for txt in glob.glob(os.path.join(root, d, *.txt)): with open(txt, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(fbad line in {txt}: {line}) bad_lines 1 continue cls int(parts[0]) vals list(map(float, parts[1:])) cls_set.add(cls) for i, v in enumerate(vals): min_vals[i] min(min_vals[i], v) max_vals[i] max(max_vals[i], v) print(classes:, sorted(cls_set)) print(x_center min/max:, min_vals[0], max_vals[0]) print(y_center min/max:, min_vals[1], max_vals[1]) print(width min/max:, min_vals[2], max_vals[2]) print(height min/max:, min_vals[3], max_vals[3]) print(bad lines:, bad_lines)逻辑说明程序遍历 labels/train 和 labels/val 下所有 txt按行解析出类 ID 和四个坐标值。min_vals和max_vals分别统计四个维度在全部标签中的最小值和最大值并打印出来。如果这份数据的标签规范那么 x_center、y_center 应该在 0 到 1 之间width、height 也应该在 0 到 1 之间且 height 不可能是 0。只要看到最小值小于 0 或最大值大于 1基本可以断定有个别标签写成了像素坐标必须修掉再训练。参数说明里 root 改成你的数据集根目录如果只想检查一个目录就把 label_dirs 改成[labels/train]即可。2.3 为什么选 YOLOv5 格式而不是 VOC/COCO很多从计算机视觉课程过来的人习惯用 LabelImg 标出 VOC 的 XML或者用 labelme 生成 JSON最后到训练阶段才发现数据格式压根不对。VOC 需要专门的 Dataset 类去解析 XMLCOCO 则要处理一个巨大的 annotations.json看类别 id 映射看到头晕。而 YOLOv5 格式是所有主流 YOLO 版本通用的“最小公约数”——一个 txt 一行一个目标不需要额外依赖解析库也不需要维护复杂的目录嵌套。这个优势在只有 400 张图的小规模数据上尤其明显。数据量小的时候管理成本比模型结构对结果的影响更大。如果你把一半时间耗在格式转换上后面训练的时间和心情都会大打折扣。另外这份数据本身的分辨率是 3024×4032属于高分辨率大图缩到 640 时目标信息仍然保留得不错算是对“高分辨率小目标”场景的一个很好的压测样本。如果你以后要接手像素更高的工业相机图像先拿这份数据跑通全流程比盲目改模型结构更有价值。提示训练前可以先用awk {print $1} labels/train/*.txt | sort | uniq -c枚举一遍标签里实际出现的类别 id 和数量分布确保与期望的 6 类一一对应这一步能避免后面训练了半天才发现类别 id 错位。3. 直接开跑用这份数据集训练 YOLOv5 的完整流程与参数解读3.1 准备环境Python、PyTorch 与 YOLOv5 仓库这份数据集本身不依赖任何特殊库但训练 YOLOv5 还是需要配一套环境。之前踩过的坑是 PyTorch 版本和 CUDA 版本不匹配装完以后一跑 train.py 就报torch.cuda.is_available()为 False。所以建议用 conda 单独建一个环境别污染系统的 Pythonconda create -n yolo python3.8 conda activate yolo pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt参数说明这里指定了 torch 1.13.1 和对应的 torchvision主要原因是它和 CUDA 11.7 配套稳定N 卡驱动只要是 450 以上基本都能跑。如果你不想折腾 conda 或显存不够也可以直接用 CPU 版 torch但 320 张图训练一轮会慢到怀疑人生。Python 3.8 到 3.10 都可以不要用 3.11 以上的版本部分依赖库的编译版本对不上。装完后把 smoke_damage 数据集放到一个独立目录比如放在 yolov5 的同级目录下后面 data.yaml 里用相对路径或绝对路径都行。我的习惯是把它放到项目根目录之外避免和 yolov5 仓库的文件混在一起清理起来也方便。3.2 写 data.yaml类别名必须与标签 id 严格对应YOLOv5 的训练入口需要你提供一个 data.yaml。新建一个文件比如 smoke_damage.yaml内容如下train: ../smoke_damage/images/train val: ../smoke_damage/images/val names: 0: head_damage 1: filter_damage 2: body_damage 3: side_damage 4: inner_damage 5: other_damage有两个地方最容易出错。第一train 和 val 的值要写 images 目录的路径不要写 labels 目录。YOLOv5 的 DataLoader 会自动把路径里的 images 替换成 labels然后去对应目录找同名 txt。如果写成 labels 目录它会去 labels/labels 下面找文件直接报错。第二names 列表的序号必须和标签文件里的 class_id 完全一致。比如标签里 class_id 是 0names 里第 0 项就是 head_damage。如果顺序反了训练和推理时类别名显示就会错位。数据集解压后如果有一个 classes.txt一定要优先以里面的文件为准我给的这段只是演示命名实际类别名以你手里这份数据提供的为准。data.yaml 写好后可以先用下面这段 Python 快速验证路径是否有效import yaml with open(smoke_damage.yaml, r) as f: cfg yaml.safe_load(f) import os for split in [train, val]: img_dir cfg[split] print(split, img_dir, os.path.exists(img_dir), len(os.listdir(img_dir)))这段代码通过 yaml 读取 data.yaml 中的路径再用 os.path.exists 判断目录是否存在并输出目录下图片数量。正常应该看到 train 对应目录存在且有 320 个文件val 对应目录存在且有 80 个文件。如果数量对不上多半是目录路径没配对。3.3 训练命令与关键参数img、batch、epochs 怎么调环境准备好、data.yaml 写好后就可以启动训练。在 yolov5 目录下运行python train.py \ --data smoke_damage.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --patience 20 \ --cache disk \ --device 0解释一下这些参数的实际意义--img 640表示把所有图片统一 letterbox 缩放到 640×640 输入网络。这不是简单粗暴地拉伸而是等比缩放后填充灰边。标签坐标不受影响所以原始 3024×4032 的大图可以放心用。--batch 16是经验值。显卡显存 16G 以上可以尝试 32但小数据集上 batch 太大容易更快过拟合。如果你用的是 8G 显存建议调成 8。--epochs 150对于 320 张训练图来说一般 100 到 150 轮就能收敛。如果看到 mAP 在 100 轮以后还在涨可以加到 200 轮。别一上来就 300 轮浪费时间和显卡寿命。--patience 20表示连续 20 轮 mAP 都没有提升时自动停止训练。这是我最推荐的习惯尤其你晚上睡觉前挂上去早上起来看结果而不用盯着一行行日志。--cache disk会把图片以缓存形式预先加载到磁盘避免每轮重复读图造成 IO 瓶颈。如果不加 cache320 张大图读到内存里也不会爆但 epoch 之间会慢一些。权重建议直接用yolov5s.pt作为预训练起点不要从零开始。YOLOv5s 在 COCO 上学过通用特征迁移到香烟破损这种表面纹理场景收敛速度和最终精度都会明显更好。训练结束后结果会保存在runs/train/exp目录下里面包括每轮的 loss 曲线、验证集 PR 曲线、混淆矩阵还有 best.pt 和 last.pt 两个权重。判断训练是否正常主要看results.png里 mAP50 曲线有没有平稳上升以及 train/loss 曲线有没有震荡下降。如果 mAP50 在 20 轮以内就冲得很高并且 val 曲线和 train 曲线差距越来越大那就是过拟合过早回头要把 epochs 调小或者开更强的数据增强。3.4 可视化脚本先跑一遍确认标签真的贴到了图上数据集里带的 visualize.py 是我每次上手必跑的工具。它的作用就是把一张图片和它的 txt 标签叠加画出来方便人工确认框是不是正好框在破损区域上。这里我写了一个增强版支持批量处理一个目录下的所有图片并把结果输出到单独文件夹import cv2 import glob import os import sys def draw_boxes(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] colors [(0, 0, 255), (0, 255, 0), (255, 0, 0), (255, 255, 0), (0, 255, 255), (255, 0, 255)] with open(label_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls int(parts[0]) xc, yc, bw, bh map(float, parts[1:]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) label class_names[cls] if cls len(class_names) else str(cls) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls % len(colors)], 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 1.0, colors[cls % len(colors)], 2) return img class_names [head_damage, filter_damage, body_damage, side_damage, inner_damage, other_damage] if __name__ __main__: image_root sys.argv[1] if len(sys.argv) 1 else smoke_damage/images/train out_dir visualized os.makedirs(out_dir, exist_okTrue) files [] for ext in (*.jpeg, *.jpg, *.png): files.extend(glob.glob(os.path.join(image_root, ext))) for f in files: label f.replace(/images/, /labels/).rsplit(., 1)[0] .txt if not os.path.exists(label): print(skip, f, label not found) continue img draw_boxes(f, label, class_names) out os.path.join(out_dir, os.path.basename(f)) cv2.imwrite(out, img) print(saved, out)运行方式很简单python visualize.py ../smoke_damage/images/train代码里有几处关键设计label f.replace(/images/, /labels/)是靠目录约定定位标签路径xc, yc, bw, bh是归一化坐标必须乘上当前图片的实际宽高 w 和 h 才能得到像素框位置class_names 传入的列表顺序必须和 data.yaml 一致画图时会直接把类别名打在框上。输出会保存到当前目录下的 visualized 文件夹。如果你看到框和图像中破损部位错位比如框偏了一半那就不是画框代码的问题而是标签文件本身存在坐标基准错误一定要在训练前修正否则训练出来的模型精度会被带偏。4. 避坑指南香烟破损数据的五个高频翻车点4.1 显存OOM分辨率别贪高现象训练命令里把 --img 设为 3000 或 4032程序运行不到几分钟就报RuntimeError: CUDA out of memory显卡直接被打爆。原因YOLOv5 的模型输入尺寸由 --img 决定如果你把输入设成接近原始分辨率特征图尺寸会指数级上升显存占用远远超过正常范围。解决默认 640 就够用想获得更好的小目标检测效果可以往上加 1280前提是你的显卡显存至少在 12G 以上。千万不要觉得“原图是 3024×4032 就一定要用原图训练”YOLOv5 的 letterbox 逻辑已经处理好了缩放标签也会同步缩放。4.2 标签和图片文件名后缀不一致现象训练日志里频繁出现类似WARNING: Could not find label file: xxx而且最终训练样本数明显少于 320。原因图片是 .jpeg标签生成器却写成了 .jpg或相反还有可能是文件名包含空格或特殊字符导致路径拼接失败。解决一拿到数据先统一后缀。在 Linux 下用rename或脚本批量修改在 Windows 下先检查图片扩展名再跑配对脚本。另外路径中尽量不要带中文、空格、#这类字符YOLO 的某些底层 IO 对特殊字符处理很脆弱。4.3 验证集只有 80 张mAP 波动剧烈现象每轮训练在 val 集上评估的结果忽高忽低mAP50 可能在 80%、65%、78% 之间跳来跳去看上去模型像“抽风”。原因80 张验证图对于 6 类目标检测任务来说太少平均每类分到手只有十来张单张难例的表现会显著拉高或拉低整体指标。解决不要死守官方验证集。我建议做一次 5 折交叉验证——把整个 400 张数据随机分成 5 份每次用其中 4 份训练 1 份验证跑 5 次取平均。虽然耗时会增加到 5 倍但指标可信度高很多。如果想要快速部署直接把 val 并入训练集用全部 400 张微调然后自己再拍一批真实产线照片当外部验证集。4.4 类别不平衡少数类完全被模型忽略现象训练完成后用模型跑验证集发现某几类检测效果很好但“头部破损”这种类别几乎一个都检测不出来召回率极低。原因6 个类别在 320 张训练图里的样本数量是不均衡的常出现的类别可能占了一半以上冷门类别只有几十张甚至更少。模型在训练时被多数类别主导少数类很难学出泛化特征。解决第一训练时不要把 epochs 拉太长否则多数类过拟合而少数类欠拟合第二用 YOLOv5 的 mosaic 增强配合--multi-scale能在一定程度上增加小目标样本的多样性第三针对少数类单独做离线增强例如旋转、平移、亮度扰动扩到和多数类接近的数量后再训练。注意离线扩增后要保证新增图片也有对应 txt否则 YOLO 会跳过它们。4.5 手机拍摄图的 EXIF 旋转隐患现象可视化脚本跑某张图片时发现所有边界框都整体偏转 90 度或 180 度框的位置明显和画面内容对不上。原因文件名像 IMG_8363、MVIMG_20220720 这种多来自手机相机而手机 JPEG 常在 EXIF 信息里写一个 Orientation 旋转方向。图像查看器会按 EXIF 自动摆正图片开源 CV 库默认不处理这个信息于是读取到的像素矩阵和标注时看到的画面方向不一致。解决用 Pillow 的ImageOps.exif_transpose把所有图片重写一遍去掉 EXIF 方向信息让像素本身变成正的方向from PIL import Image, ImageOps import os def fix_exif(root): for dirpath, _, files in os.walk(root): for fname in files: if fname.lower().endswith((.jpg, .jpeg)): path os.path.join(dirpath, fname) img Image.open(path) img ImageOps.exif_transpose(img) img.save(path, quality95) print(fixed, path)这段代码会遍历指定目录下所有 jpg/jpeg按 EXIF 信息重新排列像素后覆盖保存。标签坐标本来就是在摆正后的画面上标注的所以处理后框就能对齐。如果你用这份数据没遇到这个问题不代表以后不会遇到跑可视化脚本时多留意几张就行了。注意做完 EXIF 修复后图片文件的 MD5 会变如果你已经把数据交给别人训练记得同步通知不要在同一份数据上再跑一遍这个脚本否则可能二次旋转。5. 验证模型和进阶从可视化到扩展训练集训练跑完以后第一件要做的事不是盯着 mAP 数字高兴而是拿验证集图片跑一遍推理肉眼对比模型预测框和真实标签的差异。YOLOv5 自带的 detect.py 就能做到python detect.py --weights runs/train/exp/weights/best.pt \ --source ../smoke_damage/images/val \ --conf-thres 0.25 \ --save-txt命令的含义是在验证集图片上做一次前向推理置信度高于 0.25 的框会画在输出图上同时把预测的 txt 保存下来。跑完之后打开 runs/detect/exp 里的图片重点看两类问题一是漏检即图片里有破损但模型没框出来二是误检即框住了一个看起来完好的区域。漏检通常说明该样本的特征在训练集中出现得不够误检则可能是背景纹理干扰。这两种情况都值得回去翻一翻训练集里对应类别的数量和多样性。如果你想把这套数据往更高版本迁移比如 YOLOv8 或 YOLO11其实非常直接因为这份数据本身就是 YOLO 格式的 txt只需要把 data.yaml 从 YOLOv5 的写法调整成 ultralytics 库能识别的写法然后调用 API 训练from ultralytics import YOLO model YOLO(yolov8n.yaml) model.train(datasmoke_damage.yaml, imgsz640, epochs150, batch16)这里要注意迁移训练时不要直接读取 YOLOv5 的 best.pt 权重给 YOLOv8 用模型结构不同权重不能直接加载。建议重新初始化一个新的输出目录避免缓存文件相互干扰。进阶思路上香烟破损检测本质是一个小而典型的工业外观缺陷场景。这份 400 张的数据可以作为冷启动的种子库真实产线上拍到的每一批新图片都可以先让已训练模型打上初标签再人工修改微调然后回灌进训练集继续迭代。这个流程比重新标注几百张新图要省一半以上时间。我每次拿到新数据集都会强制按“可视化确认标签 → 跑基线 → 看失败 case → 补数据”四步走而不是一上来就调超参数。记得有一次我为了图省事跳过可视化结果训练了两天才发现某个类别的标签 id 整体错了白白浪费大量时间。从那以后我拿到任何一份标注好的 YOLO 数据集第一件事永远是先跑一遍可视化脚本把标签错位问题排除掉希望帮到你。本文还有配套的精品资源点击获取
返回列表