ARTICLE DETAIL

资讯详情

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

香烟破损检测YOLOv5数据集格式与训练避坑指南

香烟破损检测YOLOv5数据集格式与训练避坑指南 简介面向需要训练香烟破损检测模型的研究者与工程师这是一个按YOLOv5目录格式整理的目标检测数据集覆盖头部破损、滤嘴破损等6类缺陷可直接用于模型训练和验证。资源包共803个文件内含400张训练/验证jpeg图像每张都配有对应txt标签另含py可视化脚本压缩包整体约388MB目录已按YOLOv5要求划分train/val无需额外格式转换即可直接接入训练流程。图像分辨率达3024×4032属于大尺度RGB样本适合提升细粒度缺陷识别效果标签采用YOLO格式记录类别与归一化坐标随附的可视化脚本可传入一张图片自动绘制边界框并保存到当前目录便于快速核查标注质量。数据已按训练集320张、验证集80张划分并附6个缺陷类别名称定义已有140人学习对于需要高质量香烟破损样本、希望省去数据整理环节的开发者是一份开箱即用的训练素材。1. 香烟破损检测数据集一份按YOLOv5目录格式准备好的6类别训练集到底该怎么上手做香烟破损检测最卡人的不是模型选型而是数据能不能被YOLOv5直接吃进去。这份目标检测数据集(YOLOV5目录格式)把6类别的破损样本按训练集、验证集分好了图片和txt标签一一对应拿到手改一下data.yaml就能开训。它解决的是从原始图片到可训练数据集的最后一公里尤其适合0基础纯小白先跳过标注工具和格式转换的坑直接把精力放在跑通yolov5训练自己的数据集这件事上。下面我会按目录规范、标注转换、训练配置和踩坑点逐层拆开把每个环节的参数和验证方法写清楚。2. 先搞懂YOLOv5目录格式从images/labels到数据划分的硬规则2.1 目录结构train/val下各放一份images和labelsYOLOv5的数据集目录不像COCO那样只有一个annotations文件夹而是按训练/验证分开每个split内部再分images和labels。拿到这份香烟破损数据集后先看一眼顶层结构应该是下面这种cigarette_damage/ ├── train/ │ ├── images/ │ │ ├── smoke_001.jpg │ │ ├── smoke_002.jpg │ │ └── ... │ └── labels/ │ ├── smoke_001.txt │ ├── smoke_002.txt │ └── ... └── val/ ├── images/ │ ├── smoke_051.jpg │ └── ... └── labels/ ├── smoke_051.txt └── ...train/val目录名不是硬编码真正指定路径的是data.yaml但约定俗成都写成train和val。images下放原图labels下放同名的txt标签。YOLOv5在加载数据时通过图片路径自动推导标签路径把images字符串替换成labels把图片后缀替换成.txt。所以图片和标签的文件名必须一致后缀也必须匹配否则会出现图找得到、标签找不到的情况。这里有个容易被新手忽略的点YOLOv5要求images和labels在目录层级上对称。如果train/images下有文件它就去train/labels下找同名txt如果你把标签放在了labels/train/下路径就不对称了训练时会直接提示No labels found。拿到数据集后先跑一下这个命令看结构tree /data/cigarette_damage -L 3如果发现结构不对称要么用mv把标签挪到对应的split/labels下要么另外创建一个软链接指向标签目录。我一般会先做规整而不是去改data.yaml里的label路径因为这样换机器、换环境都能保持目录一致。2.2 6个类别到底怎么对应labels第一个数字不是第几类而是names下标标题说这是6类别但具体类别名以你手上的业务定义为准。拿到数据集后先别急着训练打开任意一个labels下的txt看第一列数字。如果数字在0到5之间说明类别索引和data.yaml里的names列表一一对应。比如你定义names为[stub, tear, crush, spot, stain, wrinkle]那么txt中的0代表stub1代表tear以此类推。常见的数据集包里会附带classes.txt或categories.json但YOLOv5训练时不读这两个文件它只读data.yaml里的nc和names。所以你需要保证names的顺序和txt标签第一列完全一致否则模型会把烟纸破损当成盒体压痕去训练最后推理时分类全错。我一般会在训练前先写一个统计脚本把数据集中出现的类别索引打印出来再和data.yaml的names做对比。这一步在香烟破损这种细粒度检测里特别关键。6个类别里可能有烟支折断、烟纸破损、盒体压痕、封签开胶等外观接近如果索引错位模型学到的是错误的映射关系训练loss看着正常mAP却只有零点几。等你发现时往往已经跑了几十个epoch浪费时间。所以我在做数据集检查时会把下面这段命令跑一遍cat labels/train/*.txt | awk {print $1} | sort | uniq -c输出的索引分布一眼就能看出有没有缺类、有没有顺序异常。2.3 为什么要转成YOLOv5格式.txt比xml更适合快速迭代如果你手里拿到的是VOC XML或COCO JSON训练yolov5前必须转换。YOLOv5每次加载数据时都要实时解析标注如果在线解析xml训练速度会明显下降还要处理VOC里object没有name、bndbox不规范等问题。转成纯文本txt后读取就是一次简单的split几万张图也能快速加载。另一个原因是数据增强。YOLOv5在预处理阶段要做mosaic、仿射变换、随机平移这些操作需要把标注框直接拿来做数组运算。txt的归一化格式让坐标变换非常直接不需要像VOC那样每次带着绝对像素宽高。yolov5网络结构图里head部分会输出三个不同尺度的特征图anchor匹配时需要把标签中心点和宽高放到0到1的归一化空间里计算txt格式天生就匹配这套逻辑。所以如果你已经拿到了这份YOLOV5目录格式的数据集等于绕开了格式不统一这个大坑。后续只需要把精力放在训练参数和模型调优上不需要在数据加载上再花时间。3. 把原始VOC/COCO标注做成YOLOv5数据集转换脚本与数据划分3.1 动手前的三步体检文件名对齐、空标签、损坏图片无论数据集是谁给你的第一步不是转换而是体检。我一般按顺序做三件事每件事都能用一条命令或一个小脚本解决。第一检查图片和标签文件名是否一一对应。用bash做差集最快把图片名和标签名的扩展名去掉分别存到两个临时文件然后diff。如果有有图无标签的文件训练时那张图会被忽略但你不一定知道如果有标签无图转换时会白转。ls train/images | sed s/\.[^.]*$// | sort img_names.txt ls train/labels | sed s/\.[^.]*$// | sort lb_names.txt diff img_names.txt lb_names.txt第二检查空标签文件。香烟破损样本里可能存在没有目标的图片YOLOv5遇到全空txt会直接跳过该图片不让它参与训练。如果你的业务期望良品不参与训练空标签没问题但如果期望模型学会区分良品和破损空标签图片需要单独作为负样本处理不能简单丢进训练集。find labels/train -name *.txt -empty | wc -l第三检查图片是否完整可读。有些图片在传输或标注过程中损坏训练到一半才会报OSError让人误以为是显存不够或代码问题。用PIL也能快速扫一遍但更简单的是用file命令扫出所有非jpg/png的文件file images/train/* | grep -v JPEG image data\|PNG image data这三步做完数据集基本是干净的。不要跳过否则后面训练时每报一个错都要回头查数据浪费时间更多。3.2 VOC转YOLO的转换脚本坐标归一化与越界裁剪如果原始标注是VOC XML下面这个转换脚本可以直接用。它读取xml里每个目标的name和bndbox把绝对像素坐标转成归一化中心点宽高同时做了越界裁剪和无效框过滤。import os import glob import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(xml_path, txt_path, class_names): tree ET.parse(xml_path) root tree.getroot() size root.find(size) if size is None: print(f跳过缺少size的xml: {xml_path}) return img_w int(size.find(width).text) img_h int(size.find(height).text) if img_w 0 or img_h 0: print(f跳过尺寸异常: {xml_path}) return lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 越界坐标裁剪到图像边界避免归一化后出现负数或超过1 xmin max(0, min(xmin, img_w)) ymin max(0, min(ymin, img_h)) xmax max(0, min(xmax, img_w)) ymax max(0, min(ymax, img_h)) if xmax xmin or ymax ymin: print(f过滤无效框: {xml_path} - {name}) continue x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h if not (0 x_center 1 and 0 y_center 1 and 0 box_w 1 and 0 box_h 1): print(f越界丢弃: {xml_path} - {name}) continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: # 示例类别按实际标注里的类别名和期望顺序修改 class_names [stub, tear, crush, spot, stain, wrinkle] xml_dir annotations/ out_dir labels/train/ os.makedirs(out_dir, exist_okTrue) for xml_file in glob.glob(os.path.join(xml_dir, *.xml)): txt_path os.path.join(out_dir, Path(xml_file).stem .txt) convert_voc_to_yolo(xml_file, txt_path, class_names)这段代码的逻辑是先从xml根节点取size得到图像宽高再遍历object节点。对每个object先检查类别是否在class_names里如果在就取出bndbox坐标做一次min/max裁剪防止手工标注时框出界。随后计算归一化中心点和宽高并写入txt。参数说明class_names列表的顺序就是最终的类别索引顺序训练时data.yaml里的names必须和它完全一致一个都不能错。out_dir可以按train/val分别指定转换前确认好当前处理的是训练集还是验证集。写入时保留6位小数足够还原坐标精度同时避免文件过大。这段脚本不能处理多边形标注但香烟破损里的破损区域基本都是矩形够用。如果你的标注里有不规则多边形建议先用标注工具统一成矩形框再转。3.3 训练集/验证集划分按图片随机切分是新手最容易犯的错如果你的数据集没有预先划分需要自己做train/val切分。很多人直接random.shuffle图片列表然后按8:2切但这样容易把同一包香烟的多张连续帧拆到训练和验证里。目标检测的评估指标里验证集的作用是模拟模型没见过的新样本。如果同一根烟支出现在训练集验证集里再看到它的不同角度照片mAP会被虚高实际部署到新产线时性能立刻现原形。我一般会先按包或批次分组把同一个生产批次的所有图片放在一起然后以组为单位切分。如果你手上没有批次信息至少检查图片文件名是否有相同前缀把前缀相同的图片归到一组再切。这样切出来的验证集更有参考价值。切分完还要检查数据集中是否存在完全重复的图片。如果两张图片是复制粘贴的等同泄漏。用MD5做一次快速去重find images -type f -exec md5sum {} \; | sort | awk {print $1} | uniq -d如果有重复MD5仔细看看是不是同图。香烟包装看似一样但不同照片应该有不同MD5。这个检查和上面文件名检查一起做能避免一个很隐蔽的翻车点训练集和验证集重复图片导致mAP假高。4. 训练前的最后一道工序data.yaml配置与超参数调整4.1 写对data.yamltrain/val路径指向images还是labelsYOLOv5的data.yaml是训练入口最关键的配置文件。很多人在这里翻车把train/val路径写成了labels目录或者写成了包含图片和标签的父目录。正确写法是train和val的值都指向对应split的images目录YOLOv5会自动推导label路径。# data.yaml path: /data/cigarette_damage # 数据集根目录 train: train/images # 相对path路径 val: val/images nc: 6 # 类别数 names: [stub, tear, crush, spot, stain, wrinkle]这里三点要特别说明path字段在较新版本YOLOv5里统一前缀后面的train/val可以写相对路径。如果你写绝对路径就不要再用path拼接否则路径会变成/xxx//data/...这种双斜杠。nc的值必须和names列表长度一致并且等于标签文件里最大索引加1。如果nc写6、names只有5个训练时会在Dataloader阶段报错。names里的名称不一定要和txt中的原始字符串对应它只负责在结果文件里显示真正决定类别映射关系的是索引位置。写完data.yaml后用先一个简单命令确认能被yolov5正确加载python -c import yaml; dyaml.safe_load(open(data.yaml)); print(d[nc], len(d[names]))输出两个数字一致再启动训练。这步能省下几分钟的报错排查时间。4.2 yolov5超参数调整针对香烟破损这类小目标的四个重点参数YOLOv5把数据增强参数放在hyp.scratch-low.yaml、hyp.scratch-med.yaml这类文件里训练时用--hyp指定。香烟破损的检测目标通常面积小、纹理弱我调整时重点关注四个参数。第一是img_size它不是hyp文件里的字段而是train.py的命令行参数。YOLOv5默认640但对小目标建议提到960或1280只要GPU显存允许。烟支上的裂口可能只有十几个像素640输入下采样到1/32后小目标直接消失。注意img_size增大后训练时间和显存占用都成倍涨要根据显卡实际情况取舍。第二是mosaic。YOLOv5默认开启mosaic把4张图拼成一张能增加目标尺度多样性。但对小目标要小心mosaic裁剪概率太大时小目标容易被裁掉一半。可以适当把mosaic从1.0降到0.7左右。第三是fliplr。水平翻转对大多数目标检测有提升但烟支上的文字和图案方向敏感。如果最终部署的相机朝向固定fliplr可以关掉防止模型学到反过来的烟这种假规律。第四是hsv_h、hsv_s、hsv_v。颜色扰动参数香烟包装颜色比较统一过度扰动会让模型去拟合不存在的颜色变化。我一般把hsv_s从默认0.5降到0.2。改超参数不需要从零写一份hyp文件可以用--hyp传一个只覆盖部分字段的yaml。比如python train.py --data data.yaml --weights yolov5s.pt --hyp hyp_cigarette.yaml --epochs 100 --img 960hyp_cigarette.yaml里只放改动过的字段mosaic: 0.7 fliplr: 0.0 hsv_s: 0.2 scale: 0.5YOLOv5会把命令行传入的hyp和默认代码里的参数合并。这样比拷贝一整份hyp文件清爽也方便做对比实验。4.3 验证集的正确用法先跑一遍val.py建立baseline训练过程中train.py会在每个epoch结束后自动在val集上评估并保存best.pt。但很多人忽略了一个细节训练时的best.pt是只按验证集mAP挑出来的如果验证集划分有问题这个best顶多是个排名工具不代表部署效果。我习惯在训练完后单独跑一次val.py重新输出一份评估结果并和训练日志对比。命令python val.py --data data.yaml --weights runs/train/exp/weights/best.pt --img 960 --task val参数含义--task val表示对验证集做推理--img要和训练时的img一致否则输入分辨率不同精度会有偏差。--conf-thres和--iou-thres会影响mAP计算一般用默认值不需要动。跑完后会生成一个results目录里面对应每一类的AP、PR曲线、混淆矩阵。验证集不是调参工具。如果你反复看着同一个验证集改超参数改了一整天最后等于在验证集上过拟合。正确做法是验证集只看不改实在要调就留一份测试集或者用交叉验证选一轮参数再去另一批没见过的图片上验证。5. 避坑/常见问题/排查训练香烟破损模型最容易翻车的5个细节5.1 报错No labels found in ...训练卡在数据加载现象启动train.py后日志里出现No labels found in /data/cigarette_damage/train/labels然后一直卡住或退出。原因最常见的两种情况一是目录结构里根本没有labels文件夹或者labels文件夹里没有.txt文件二是图片和标签的后缀对不上比如图片是.jpg但标签叫smoke_001.txt系统靠图片路径替换扩展名去找标签如果你图片是.jpeg而数据生成时只处理了.jpg也会漏。解决先把labels文件夹建好并确认里面至少有一个非空txt。用bash检查find /data/cigarette_damage/train -name *.txt | wc -l find /data/cigarette_damage/train -name *.jpg | wc -l两个数量应该接近。如果txt数量明显少说明转换时漏了很多文件回第3章的转换脚本里找原因。5.2 训练loss正常但验证mAP全是0类别索引错位现象训练损失在下降训练集mAP偶尔很高但验证集mAP始终是0或者混淆矩阵对角线全是0。原因标签txt里的第一列数字与data.yaml中names的索引不一致。比如你把names写作[stub, tear, crush, spot, stain, wrinkle]但数据里的标签是按另一个顺序标注的。模型学了错误的类别绑定验证时也按错绑定推理自然对不上。解决写一个脚本遍历所有labels中的txt统计每个索引出现的次数和data.yaml的names核对。如果发现索引缺失或顺序不符合业务预期就用转换脚本重跑一遍或写一个小替换脚本把txt第一列映射到正确索引。这一步会在一开始多花几分钟但能避免后面所有结果变成废纸。5.3 坐标越界警告ignore corrupt label现象训练日志中出现WARNING: ignoring corrupt label后面跟着某个文件路径然后这个目标框被忽略。原因标注框的xmax/ymax大于图片宽高或xmin/ymin小于0导致计算出的center或宽高超出0到1范围。常见于手工标注后旋转了图片却没有同步坐标或VOC转YOLO时没有做裁剪。解决在转换脚本里加上max(0, min(...))裁剪并在归一化后再次检查。如果txt已经生成可以写一个快速校验python - EOF from pathlib import Path for txt in Path(labels).rglob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5 or not all(float(p) 0 for p in parts[1:]): print(txt, line) EOF发现越界文件后要么删除该框要么回到原始标注修正。不建议直接沿用因为一个出界的框会把模型梯度带偏尤其在早期epoch。5.4 验证集和训练集图片重复导致mAP虚高现象训练loss一直下不去但val mAP接近0.9训练日志里每个epoch的val结果波动很小好得不真实。原因数据切分时按图片随机分同一包香烟的多张连续照片被同时放进train和val。模型在训练时见过这些图的目标区域验证时再测记忆代替了泛化。解决按图片文件名前缀分组切分并做一次去重。如果实在分不清批次也可以用感知哈希做全局查重把相似度高的图片都归到同一侧。我习惯在数据准备阶段就生成一份train.txt和val.txt清单保存下来避免后面调试时无意改动了切分。5.5 小目标漏检严重破口太小模型直接当噪声滤掉现象模型能检测出大的烟盒压痕、烟纸撕裂但烟支上一道细小的切口识别不出来或者置信度很低。原因小目标在特征图上的有效感受野太小。YOLOv5默认的P3层stride是8一个8x8像素的破口在下采样到P3时只贡献一个像素如果原图尺寸是640很小的破口经过多次卷积后可能和背景融合。这类问题在移动小目标检测里尤其常见。解决优先把img_size提到960或1280让小目标在输入上占更多像素。其次在检测时用多尺度推理--augment。也可以把训练集里的破损区域crop后单独训练一个专用模型。对于香烟破损我更建议先在数据层做一次小目标增强比如把包含小目标的图片放大1.5倍后再进入训练代价是显存和训练时间都会涨但漏检改善很明显。6. 训练完怎么验证香烟破损检测效果mAP、混淆矩阵与bad case复盘训练结束不能只看日志里的mAP那只是模型在验证集上的平均表现具体到哪一类破损没检出来要看混淆矩阵和bad case。先单独跑一次val.py保存最佳权重对应的评估结果python val.py --data data.yaml --weights runs/train/exp/weights/best.pt --img 960 --task val --save-json跑完后打开runs/val/exp下的confusion_matrix.png重点看哪两个类别互相混淆。如果烟纸破损和盒体压痕经常互相认错说明它们的特征太接近需要在数据层面补充更细致的标注边界而不是急着调模型。再看labels_correlogram.jpg了解目标尺寸分布。如果大多数框都集中在图像右下角说明相机角度有偏差模型可能学到位置偏好而不是破损本身。然后是bad case复盘。用detect.py对验证集图片做批量推理把置信度低的检测结果单独存出来python detect.py --source /data/cigarette_damage/val/images --weights runs/train/exp/weights/best.pt --img 960 --save-txt --save-conf加上--save-txt后每张图会输出一个同名的txt里面是检测到的类别、归一化坐标和置信度。我接着会写一个小脚本把置信度低于0.3或漏检的图片挑到单独的文件夹里一张张看。看不懂的bad case我会把图片放大看看是否因为破损区域太小、光照反光或遮挡导致漏检。这个环节比调参更能发现问题。我做过的第一个香烟破损项目当时验证集mAP跑到0.9觉得稳了。结果去现场用相机实拍召回率直接掉到0.6。回来查数据发现验证集里混了大量和训练集同源的照片边框也有几处越界被当噪声滤掉了。后来重做数据划分按包分组再把越界修正模型才真正可用。那以后我每次都先跑一遍数据分布和相似度检查再谈精度。希望帮到你。本文还有配套的精品资源点击获取
返回列表