YOLOV5目标检测实战:香烟破损检测数据集处理与模型训练全流程 简介这份目标检测数据集面向计算机视觉初学者与模型训练工程师以YOLOv5标准目录格式整理专用于香烟破损缺陷检测覆盖头部破损、滤嘴破损等6个类别包含训练集与验证集省去手动划分和格式转换环节可直接放入YOLOv5项目开展训练验证。包体共803个文件其中401张JPEG大尺度图像分辨率3024*4032对应401个txt标注文件另附1个Python可视化脚本压缩后整体约388MB目录结构清晰。数据划分方面训练集含320张图片及320个标签验证集含80张图片及80个标签并随包提供6个类别名称txt文件方便配置模型类别参数。可视化脚本无需修改即可运行随机读取一张图片即可绘制边界框并保存至当前目录便于快速核查标注质量。目前已有139人学习下载适合需要真实工业检测场景数据用于毕业设计、课程项目或算法验证的开发者。1. 香烟破损检测数据集到底解决什么问题6 类别、YOLOV5 目录格式拿到手第一件事不是训练生产线质检员一天要看几千支烟眼睛花是小事漏检才是大事。用目标检测模型做香烟破损识别核心就是把“有没有破损、破在哪、破损多严重”变成模型能学的问题。这个数据集按 YOLOV5 目录格式组织6 个类别训练集、验证集分开意味着拿到手不需要改目录结构直接喂给 YOLOV5 就能跑。真正值钱的不是那几千张图而是你已经把一个视觉判断问题翻译成了模型能理解的监督信号。这套数据适合两类人一是产线质检项目选型阶段想验证 YOLOV5 能不能区分破损等级的工程师二是刚接触目标检测想找一个类别清晰、任务边界明确的数据集来跑通训练全流程的新手。我拿到这类数据集从来不会急着跑 train.py而是先拆目录、看标签、验格式——因为训练翻车的原因八成在数据层面模型本身反而是最不容易出问题的部分。2. 拆开 labels 看门道YOLOV5 的归一化坐标、6 类别定义与目录对应关系2.1 目录结构images 和 labels 的配对比图片本身更值得检查YOLOV5 的数据集目录约定是死的图片放在 images 下标注文件放在 labels 下两个目录下的文件名必须完全一致只是一个后缀是 .jpg一个后缀是 .txt。训练时按 train.txt、val.txt 里的图片路径去读再找到同名的标签文件。这套约定是 YOLOV5 训练脚本硬编码的逻辑你改了它就翻车。拿到数据集先做三件事看目录、数文件、查配对。我一般会用一段脚本来校验配对率——所谓配对就是每一张训练图片必须有一个同名的 .txt 标签文件而且这个标签文件不能是空的。#!/bin/bash # 校验 images 与 labels 的配对情况 for sub in train val; do img_dirdatasets/cigarette/images/$sub lab_dirdatasets/cigarette/labels/$sub echo $sub echo 图片数量: $(ls $img_dir | wc -l) echo 标签数量: $(ls $lab_dir | wc -l) # 找出有图无标签和有标签无图的文件 for img in $(ls $img_dir); do base${img%.*} if [ ! -f $lab_dir/$base.txt ]; then echo 缺标签: $img fi done # 统计空标签文件有 txt 但里面没有内容 empty0 for txt in $(ls $lab_dir); do if [ ! -s $lab_dir/$txt ]; then empty$((empty1)) echo 空标签: $txt fi done echo 空标签总数: $empty done这段脚本的逻辑很直白先统计两边的文件数再按文件名前缀逐个比对。缺标签意味着这张图片在训练时会被 YOLOV5 当成背景图处理如果这类情况多了模型会对破损区域视而不见。空标签文件同理——它会让模型学到“这个位置什么都没有”。参数上唯一要注意的是 basename 的截取逻辑如果图片是 .jpeg 或 .png 后缀写法不变因为截的是最后一个点之前的部分。实际上我在处理这类数据时还会顺手跑一下图片完整性校验——用 Python 的 Pillow 库尝试打开每一张图打不开的就是损坏文件。这个校验在数据量超过 5000 张时特别值得做下载中断导致的半截图片YOLOV5 训练时会在数据加载阶段直接报错。2.2 归一化坐标不是玄学YOLOV5 的标签格式为什么是灰度的中心点加宽高打开任何一个 .txt 标注文件你会看到形如“2 0.4825 0.3167 0.1200 0.0833”的文本行。第一列是类别 ID整数从 0 开始后四列分别是目标中心点的 x、y 坐标以及目标的宽、高。关键约束是三、四、五、六列必须是训练图片宽高归一化后的结果——即实际像素坐标除以图片宽度或高度取值范围在 0 到 1 之间。归一化坐标的价值在于模型不关心图片的绝对分辨率只看目标在画面中的相对位置和相对大小。它的代价是如果你用标注工具直接导出的坐标是像素值就一定要先做换算import os # 将 VGG/LabelImg 导出的像素坐标转换为 YOLOV5 归一化格式 def convert_to_yolo(img_w, img_h, xmin, ymin, xmax, ymax, class_id, out_path): # 防止标注越界导致训练时报错 xmin max(0, xmin); ymin max(0, ymin) xmax min(img_w, xmax); ymax min(img_h, ymax) center_x ((xmin xmax) / 2.0) / img_w center_y ((ymin ymax) / 2.0) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h with open(out_path, w) as f: f.write(f{class_id} {center_x:.6f} {center_y:.6f} {box_w:.6f} {box_h:.6f}\n) # 示例480x640 的图目标框左上角(100,200)右下角(300,520) convert_to_yolo(480, 640, 100, 200, 300, 520, 2, output.txt)这段代码的核心不是格式化的写法而是两个容易忽略的细节。第一边界裁剪——标注工具画框时偶尔会把框拉出图片边缘如果坐标小于 0 或大于图片宽高YOLOV5 训练时会报“box coords out of bounds”的异常虽然有时候只是警告但积累多了会影响损失计算。第二保留 6 位小数——归一化坐标本身数值很小如果只保留 3 位小数中心点坐标的量化误差就能达到原图的千分之一对于 6000x4000 的高清产线图这相当于 6 个像素的偏移足以让一个破损区域和正常纹理之间的边界变得模糊。类别的顺序同样不能改。YOLOV5 的类别 ID 是根据 data yaml 文件里 names 列表的下标来映射的names[0] 对应 ID 0names[2] 对应 ID 2。如果你的数据集里标注的类别 ID 和 yaml 里 names 的顺序对不上训练不会报错但 mAP 曲线会诡异波动推理时输出的类别名完全是错的。2.3 6 类别怎么定边界破损等级划分背后的标注一致性香烟破损检测的 6 个类别常见做法是按破损部位和严重程度交叉定义。比如滤嘴压扁、烟纸破洞、爆珠外露、烟支弯折、端面破损、包装膜划痕。每一个类别都对应一个独立的视觉现象但这个“独立”是人工约定的存在天然的灰色地带——一张图里既有烟纸破洞、又有烟支弯折标注员应该画两个框还是一个框这个问题的答案决定了 mAP 的上限。我在验证这类数据集时会先做一件事把训练集里全部的标注框面积统计出来画一个分布直方图。为什么要看这个因为破损检测里最常见的标注错误就是框太大——标注员习惯把整根烟框进来而不是只框破损区域。YOLOV5 的 Anchor 是根据标注框的尺寸聚类生成的如果框的面积分布过于分散聚类出来的 Anchor 对小的破损区域不敏感。处理方式是用 YOLOV5 自带的 kmeans 聚类重新生成 Anchor在训练命令里加--noautoanchor参数或者删掉模型 yaml 文件里的 anchor 定义让程序重新聚类。训练集和验证集的划分同样值得留意。这个数据集已经分好了 train 和 val但我不建议盲信——我会抽查验证集中的图片确认它们和训练集没有同源的连续帧。产线拍摄的图片往往来自视频抽帧如果抽帧间隔太短相邻帧高度相似验证集就名存实亡最终评估的 mAP 会虚高 5 到 10 个百分点。一个简单的检测方法是对验证集做感知哈希把重复度过高的图片找出来删除。3. 用 YOLOV5 跑通第一轮训练数据校验、超参数设定与训练日志解读3.1 训练前的数据体检先用一段脚本把隐患暴露出来我不建议直接执行 train.py。先写一段数据体检脚本检查三类问题图片尺寸一致性、标注框面积异常、类别分布均衡性。这三项直接决定后续超参数怎么设。import os from PIL import Image import numpy as np data_dir datasets/cigarette def inspect_dataset(split): img_dir os.path.join(data_dir, images, split) lab_dir os.path.join(data_dir, labels, split) sizes [] # 图片尺寸 all_boxes [] # 所有标注框的面积归一化后 class_count {} # 每个类别的目标数量 for img_name in os.listdir(img_dir): img_path os.path.join(img_dir, img_name) with Image.open(img_path) as img: sizes.append(img.size) # (width, height) lab_path os.path.join(lab_dir, os.path.splitext(img_name)[0] .txt) if not os.path.exists(lab_path): continue with open(lab_path) as f: for line in f: parts line.strip().split() cls int(parts[0]) x, y, w, h map(float, parts[1:]) class_count[cls] class_count.get(cls, 0) 1 all_boxes.append(w * h) # 归一化面积 print(f[{split}] 图片数量: {len(sizes)}) print(f[{split}] 图片尺寸分布: {set(sizes) if len(set(sizes)) 5 else 多样}) print(f[{split}] 类别分布: {class_count}) if all_boxes: all_boxes np.array(all_boxes) print(f[{split}] 标注框面积: min{all_boxes.min():.6f}, fmax{all_boxes.max():.6f}, mean{all_boxes.mean():.6f}) # 面积小于 0.001 的框相当于 640x640 图中不到 20x20 像素 small_ratio (all_boxes 0.001).mean() print(f[{split}] 小目标比例: {small_ratio:.2%}) for split in [train, val]: inspect_dataset(split)这段脚本的产出全部指向训练参数的设定。图片尺寸分布多样时我会在训练时统一--img 640YOLOV5 会自动做缩放填充但如果你觉得小目标多就把--img提到 960 或者设成多尺度训练--multi-scale这个参数在破损检测里价值很大。小目标比例高过 5%关注点就得放在 Anchor 和损失权重上——具体说就是把--noautoanchor去掉默认开启自动聚类并在 loss 层面对小目标的 box 损失加权。类别分布不均衡时不是去改损失函数而是先确认验证集和训练集的分布一致否则训练的模型会在样本多的类别上过拟合。有一个参数名叫得不一样但跑通 yolov5 train.py 之后在data/cigarette.yaml里设置nc: 6是硬编码逻辑names 顺序必须和标注文件里的字符匹配。3.2 train.py 启动参数哪些是必调项哪些是增强项第一次跑命令越朴素越好。我会按下面这种写法启动python train.py \ --data data/cigarette.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --cache ram \ --workers 8 \ --project runs/cigarette \ --name first_run逐一说明这些参数的选择逻辑。--weights yolov5s.pt表示用 COCO 预训练权重做迁移学习初始权重这是小数据集跑目标检测的默认做法比从零训练收敛快很多——除非你是做二次开发否则不建议用--weights 。--img 640是输入分辨率YOLOV5 会先把图缩放到这个尺寸再训练。如果数据集中小目标比例高可以考虑升到 960但显存占用会按比例急剧上升8GB 显存跑不动。--cache ram是把图片缓存进内存数据量在 1 万张以内强烈建议开启磁盘 IO 会是训练速度的隐藏瓶颈尤其是机械硬盘场景。--workers 8是数据加载的进程数Windows 上建议调到 4 以下Linux 上 8 12 都没问题。有一个参数我要单独强调--cos-lr。这是余弦退火学习率调度比默认的线性下降在后期拟合阶段更平稳。我的经验是在细粒度分类型的检测任务比如破损等级区分上余弦退火能稳定提升 1 2 个 mAP 点代价只是训练时间几乎不增加。还有--label-smoothing 0.01它能让模型对标注噪声更鲁棒——破损检测的标注本来就有很强的主观性类别边界模糊的地方平滑一下损失函数是值得的。最后是--patience 30早停参数连续 30 个 epoch 验证集 mAP 没提升就自动停省时间。3.3 训练日志怎么读loss 曲线和 mAP 不是一回事训练启动之后终端里会定时打印一组指标box_loss、obj_loss、cls_loss、mAP0.5、mAP0.5:0.95。新手最容易犯的错误是盯着 mAP 不放看到它掉了就以为模型训坏了。实际上训练前 20 个 epochmAP 有波动完全正常更应该关注的是 loss 的下降趋势。box_loss 衡量预测框和真实框的位置偏差obj_loss 衡量“这个位置有没有目标”的置信度cls_loss 衡量类别判断的正确性。三个 loss 应该从头到尾都呈下降趋势但如果某一个 loss 先降后升那是过拟合的信号——此时不是调参而是该加数据增强或正则化。YOLOV5 默认开启 mosaic 数据增强把四张图拼在一起训练对小目标任务有奇效但如果你发现 loss 在一两个 epoch 内暴涨检查一下 mosaic 是不是把标签框拼没了——这类情况在训练集里小目标多时特别容易出现。训练结束后runs/cigarette/first_run/目录下会生成 weights 文件夹。里面有两个权重文件best.pt和last.pt。best.pt 是验证集 mAP 最高时保存的last.pt 是最后一个 epoch 保存的。部署时永远用 best.pt但在做更多的实验消融时last.pt 可以作为下一次训练的初始化权重它比 COCO 权重更贴近你的数据分布能省不少收敛时间。还有 results.png 和 confusion_matrix.png 这两张图前者是全部指标曲线后者是类别混淆矩阵。我每次训练完第一件事就是看混淆矩阵它能直接告诉你哪些类别之间在互相误判——这在香烟破损这种细粒度任务里是常态。4. 用 6 类别的香烟破损模型做推理验证val.py 的评估口径与 detect.py 的实战输出4.1 val.py 看什么mAP 只是及格线分类别 AP 才是决策依据训练完不是直接上产线而是先用 val.py 在验证集上做一次正式评估。这个数据集的验证集是单独划分的跑验证命令时路径和数据 yaml 里定义的 val 字段需要指向验证集根目录这样可以保证评估结果的可信度python val.py \ --data data/cigarette.yaml \ --weights runs/cigarette/first_run/weights/best.pt \ --img 640 \ --batch-size 32 \ --conf-thres 0.001 \ --iou-thres 0.6 \ --task val--conf-thres 0.001是推理置信度阈值在验证阶段必须调低目的是让模型把潜在目标全部找出来再做 NMS 去重。如果验证阶段把阈值设成 0.25等于让模型只输出高置信度结果mAP 会偏高但失真。--iou-thres 0.6是计算 TP 和 FP 时的 IoU 阈值evaluate 时预测框和真实框重叠度超过 60% 才算命中这是目标检测的标准操作。val.py 的输出会包括每个类别的 Precision、Recall、mAP0.5、mAP0.5:0.95 以及速度评估。我关注的不是那个综合 mAP而是逐类别的 AP 差距。如果类别 0滤嘴压扁的 AP 是 0.95但类别 4端面破损只有 0.6说明后者对模型太难——破损区域太小或者和正常烟支端面的外观差异不明显。这时候优先考虑的调参方向是把训练分辨率提上去或者针对类别 4 做数据增强。记住一个原则mAP 是平均值平均值掩盖了类别间的巨大差异。生产环境部署时每个类别的召回率线分别设阈值而不是统一套一个置信度。4.2 detect.py 的实际姿势单张图验证和批量推理的区别验证集评估通过后用 detect.py 做一次定性检查。这一步的目的不是看 mAP而是亲眼看模型在真实图片上画框的效果——框位置是否准确、有没有把相邻的正常区域框进来、破损区域小的目标漏检多不多。# 单张图推理 python detect.py \ --weights runs/cigarette/first_run/weights/best.pt \ --source datasets/cigarette/images/val/ \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --save-conf \ --project runs/detect \ --name val_visualdetect.py 会遍历--source下的所有图片把检测结果画框并保存到runs/detect/val_visual/。--save-txt会把每个检测框的类别、坐标、置信度写入文本文件--save-conf控制是否同时输出置信度数字。调试阶段可以把--conf-thres调到 0.1这样能看到模型全部的潜在输出刷出来的多框会让你明白漏检是因为阈值太高还是模型根本没学到位。实际项目中香烟破损检测的输出去向往往不是可视化图片而是把检测结果喂给下游的自动化分拣系统——一个烟盒里检出破损数量超过阈值就需要触发剔除机构。也就是说--save-txt输出的坐标和置信度才是对接生产系统的关键。坐标是归一化的下游如果要换算成实际坐标需要乘以原尺寸。置信度是 0 到 1 的浮点数是否剔除、剔除优先级可以在下游逻辑里结合置信度与类别综合决策。4.3 效果不达预期时的排查路径先数据、再模型、后阈值模型在验证集上的表现不理想我一般会按这个顺序排查。第一步看图把 val.py 输出的概率分布拿过来、把检测框和储存框画在同一张图上看是框偏了还是整个漏检。第二步看混淆矩阵确认是不是相邻类别在互相打架——比如类别 1烟纸破洞和类别 3烟支弯折如果在同一位置频繁交替标注模型确实很难区分。第三步检查数据切片看看是不是图像的成像条件太单一没有覆盖生产环境的逆光、模糊、烟尘干扰等真实场景。第四步才是调模型参数比如把 YOLOV5 从 s 换成 m 或 l、增大输入分辨率、降低学习率、加多尺度训练。这套排查顺序的逻辑是目标检测模型是数据和标注的拟合器。数据里没有的场景模型永远学不会数据里标错的边界模型也只能学到错误的决策。调参是最容易操作的但往往效果最差。5. 香烟破损检测训练时最常见的 5 个翻车现场现象、原因与解决5.1 现象训练到第 30 个 epochloss 直接变 NaN原因最常见的是学习率过大导致梯度爆炸其次是 batch size 太小加上训练数据里存在极端异常值——比如某个标注框的宽度或高度是 0或者归一化坐标出现了超出 [0,1] 范围的负数。这两种情况在香烟破损这类标注框偏小、偶尔有标注员手滑的数据里都不罕见。解决先检查训练集里是否有越界标注——用本文 2.2 节的脚本全量扫描把 w 或 h 小于等于 0 以及中心点坐标超出 [0,1] 的行删掉。确认标注干净后把学习率从默认的 0.01 降到 0.001配合--cos-lr并把 batch size 调大。YOLOV5 的学习率是跟 batch size 绑定的默认配置下 batch size 从 16 改成 8学习率会自动缩到二分之一但保险起见还是用--lr0显式指定。我在一次实际调试中把近乎报废的训练从 NaN 拉回来靠的就是删掉三个异常框加降学习率。5.2 现象验证集 mAP 到了 0.92但拿到产线真实图片上检测效果很差原因产线图片和训练图片的分布不一致——亮度不同、拍摄角度不同、烟支排列方式不同。还有一个常见原因是验证集和训练集太相似这个数据集的验证集虽然独立但如果来源于同一条产线、同一个摄像头、同一天拍摄那验证集的分布几乎等于训练集的分布mAP 虚高就不可避免。解决把产线采集的真实图片单独划出来作为测试集重新评估。如果效果确实不行就把真实图片按比例补充进训练集做一次增量训练用 best.pt 作为初始化权重。不要试图通过调阈值来弥补——分布差异不是后处理能解决的。我在项目里就会预留一批“不可见数据”训练阶段完全不碰它们只在部署前用它们做一次残酷的评估。5.3 现象类别 1烟纸破洞和类别 4端面破损在混淆矩阵上互相误检原因这两类在视觉特征上有重叠。端面破损可能表现为烟丝的破损外露而烟纸破洞如果发生在靠近端面的位置视觉上非常相似。标注员在标这一类时标准不统一有人标成了 1有人标成了 4模型学到的是一个模糊的边界。解决合并类目把边缘模糊的类别合并成一个“破损”等模型收敛后再在下游逻辑里细分。另一种方案是重新检视训练集的标注把那些边界样本挑出来重新标定。但重新标注的成本高、周期长实际项目中我建议先合并类别跑一版模型看合并后的 mAP 是否显著提升——如果提升明显说明标注的不一致性远比想象中的严重。5.4 现象训练速度越来越慢GPU 利用率却只有 30%原因数据加载成为瓶颈。图片存储在机械硬盘上读取速度跟不上 GPU 的计算速度训练进程大部分时间在等数据。如果开启--cache ram理论上是把图片全部加载进内存但数据量太大时内存不够会触发 swap性能反而更差。解决先看显存占用和数据加载的 CPU 利用率。如果 CPU 利用率很高而 GPU 利用率低说明数据加载线程在拼命工作但存储跟不上。在 Linux 上用ulimit -n 65535调高文件描述符限制Windows 上把--workers降到 2避免频繁切换进程的开销。极端情况下把整个数据集放到 NVMe 固态硬盘上或者用一个 SSD 专门做训练数据缓存加载速度会翻倍。另外开启--cache disk也能把数据缓存到本地磁盘效果比 ram 模式更稳。5.5 现象模型在训练集上几乎满分验证集也差不多但用 detect.py 批量跑时总是出重复框原因NMS 阈值设置得过高。--iou-thres设置的数值决定两个重叠的框是否被合并如果设成 0.9那么重叠度低于 90% 的框都会保留下来这就导致了同一个目标出现多个框。解决把--iou-thres从默认的 0.45 调整到 0.4 或更低。但这只是治标。真正的治本方案是检查是否存在“同一区域多个真实标注框叠加”的问题——如果不同标注员在同一张破损图上标了重叠但略有偏移的框模型会学着预测多个互相重叠的框NMS 无论如何都合并不掉。解决办法是清理训练集把 IoU 高于 0.7 且类别相同的重叠标注框合并。6. 让香烟破损检测模型真正能上产线剪枝、量化与场景化回归验证训练出一个 mAP 不错的模型只是第一步。产线部署面临的现实约束是推理速度要快、显存占用要小、单张图处理时间要稳。YOLOV5 的 s 模型在 GPU 上通常能跑到几十毫秒但在边缘设备或者 CPU 上就吃紧。常见的做法是用prune.py做通道剪枝把不重要的卷积通道裁掉然后做知识蒸馏或者直接微调恢复精度。剪枝比例从 0.3 开始逐步增大每剪一次就在验证集上跑一遍观察 mAP 的下降幅度。如果剪掉 30% 的通道只掉不到 1 个 mAP 点这个剪枝就是值得的。量化是另一条路线。YOLOV5 支持 TensorRT 的 FP16 和 INT8 量化INT8 需要校准数据集做缩放因子计算不能直接训练完就转。校准图片从验证集里挑 500 张覆盖全部 6 个类别以及不同的光照条件。量化后 mAP 通常会掉 1 3 个点但如果你的部署平台是 Jetson 系列或者专用加速卡INT8 带来的速度提升足以弥补精度损失。我自己的习惯是每次训练完一版模型都会拿一套固定的“黄金测试集”做回归验证——这个测试集包含 100 张图必须是模型从未见过的覆盖正常、破损、逆光、模糊等全部场景。任何改动无论是换模型结构、改数据增强、调超参都要在这套黄金测试集上跑一遍用同一套评估代码记录 mAP 和每类 AP然后和上一版对比。这个习惯救过我很多次因为改一个数据增强参数可能让整体 mAP 没变但某一类别的 AP 掉了 5 个点只在黄金测试集上做对比才能暴露出来。检视自己的训练过程我最大的教训是数据集本身的把控永远比模型调参重要。标注质量、类别定义、训练验证分布一致性这三件事决定了模型的上限而调参只是在逼近这个上限。每次打开一个新的数据集我会假设每一个文件都是有问题的而不是假设它没问题。希望这份拆解帮到你愿你的模型一遍跑通。本文还有配套的精品资源点击获取