简介:本资源为面向工业质检与计算机视觉方向的塑料瓶缺陷目标检测数据集,适用于YOLO系列模型训练与算法验证,可帮助读者快速搭建缺陷检测实验环境。数据按YOLOv5标准文件夹结构组织,已划分训练集约3200张、验证集约130张、测试集约60张,每张图片均配有对应txt标注文件,类别包含凹陷、缺口、扭曲三类,具体以classes文件为准。压缩包共约2000个文件,以1999个txt标注与类别文件为主,另含1个可视化py脚本,整体约117MB。脚本可随机读取一张图片并绘制边界框,无需修改即可直接运行,便于快速核查标注质量。目前已有91人学习下载,适合从事缺陷检测、工业视觉或YOLO改进实战的开发者参考使用。
1. 塑料瓶缺陷检测数据集:从拿到手到跑通第一轮训练
产线上吹瓶机每分钟吐出上百个 PET 瓶,瓶身黑点、瓶口缺料、瓶底裂纹这些缺陷靠人眼盯,漏检率随班次直线上升。你手上如果正好有一份「塑料瓶缺陷图像目标检测数据集」,标注是 YOLO 格式、训练验证已经划分好、还带了 classes 文件,那它省掉的恰恰是最耗时的清洗和切分环节。这类数据集通常按缺陷类别组织,比如黑点、划痕、变形、瓶口破损,每张图配一个同名 txt,每行是类别序号 归一化中心x 归一化中心y 归一化宽 归一化高。它适合两类人:一类是想快速验证 YOLO 系列模型在工业质检场景落地效果的算法工程师,另一类是手里有产线相机但缺标注数据的视觉开发者。下面我按自己跑这类数据集的顺序,把目录结构、类别映射、训练参数和翻车点讲清楚。
2. 拆开数据集:目录结构、classes 文件与标注格式核对
2.1 拿到数据集先看这三样东西
不管数据集来自哪里,解压后第一件事不是急着写训练脚本,而是确认三件事:图像目录和标签目录是否一一对应、classes 文件里的类别顺序是否和标注里的数字对得上、划分文件是否真的存在。常见的目录长这样:
plastic_bottle_defect/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── classes.txtclasses.txt一般一行一个类别名,行号从 0 开始就是标注里的类别序号。我见过不少人直接拿classes.txt当类别名列表读,结果第一行是defect还是black_spot都没确认,训练完混淆矩阵全是乱的。核对方法很简单,写个脚本统计每个类别序号出现的次数,再和 classes 文件行数对照。
import os from collections import Counter label_dir = "plastic_bottle_defect/labels/train" class_file = "plastic_bottle_defect/classes.txt" with open(class_file, "r", encoding="utf-8") as f: classes = [line.strip() for line in f if line.strip()] counter = Counter() for name in os.listdir(label_dir): if not name.endswith(".txt"): continue with open(os.path.join(label_dir, name), "r") as f: for line in f: parts = line.strip().split() if len(parts) >= 5: counter[int(parts[0])] += 1 print("classes 文件类别数:", len(classes)) for idx, cls_name in enumerate(classes): print(f"类别 {idx} ({cls_name}) 标注框数量: {counter.get(idx, 0)}")这段脚本做两件事:读 classes 文件得到类别总数,再遍历训练标签统计每个类别序号出现次数。如果某个类别序号在标注里出现但 classes 文件里没有对应行,或者某个类别标注数为 0,就要回头查标注是否漏标或类别映射是否错位。参数上注意label_dir要指向训练集标签目录,验证集单独再跑一遍,确认两个划分的类别分布没有严重偏斜。
2.2 YOLO 标注格式的四个边界坑
YOLO 格式看着简单,但工业缺陷数据集里经常出现四类边界问题。第一类是坐标越界,归一化后的中心坐标或宽高小于 0 或大于 1,训练时损失会直接爆掉。第二类是宽高为 0,标注框退化成一条线,YOLO 在计算 IoU 时会出 NaN。第三类是同一张图里类别序号超出 classes 范围,比如 classes 只有 4 类但标注里出现了 5。第四类是图像和标签文件名不匹配,比如图像叫bottle_001.jpg但标签叫bottle_001.png.txt。
import os img_dir = "plastic_bottle_defect/images/train" lbl_dir = "plastic_bottle_defect/labels/train" num_classes = 4 # 按 classes.txt 实际行数改 bad_coord, bad_size, bad_cls, missing = [], [], [], [] for name in os.listdir(img_dir): stem = os.path.splitext(name)[0] lbl_path = os.path.join(lbl_dir, stem + ".txt") if not os.path.exists(lbl_path): missing.append(name) continue with open(lbl_path) as f: for line in f: p = line.strip().split() if len(p) != 5: continue c, x, y, w, h = int(p[0]), *map(float, p[1:]) if c >= num_classes: bad_cls.append((stem, c)) if not (0 <= x <= 1 and 0 <= y <= 1): bad_coord.append((stem, x, y)) if w <= 0 or h <= 0 or w > 1 or h > 1: bad_size.append((stem, w, h)) print("缺失标签:", len(missing)) print("类别越界:", len(bad_cls)) print("坐标越界:", len(bad_coord)) print("宽高异常:", len(bad_size))这段脚本把四类问题一次性扫出来。num_classes必须和 classes 文件行数一致,否则类别越界判断会误报。坐标越界和宽高异常通常来自标注工具导出时的精度问题,少量可以手动修,量大就要考虑重新导出或写脚本裁剪到 [0,1] 区间。缺失标签的图要么补标,要么直接从训练列表里剔除,别留着让 DataLoader 报错。
3. 用 YOLOv8 跑通第一轮训练:配置、命令与参数含义
3.1 生成 data.yaml 并确认路径写法
YOLO 训练不直接读 classes.txt,而是通过一个 yaml 文件告诉模型图像目录、验证目录和类别名。这个文件我一般放在数据集根目录下,叫data.yaml:
path: /home/user/plastic_bottle_defect train: images/train val: images/val nc: 4 names: 0: black_spot 1: scratch 2: deformation 3: mouth_brokenpath是数据集根目录,train和val是相对路径。nc必须等于 names 列表长度,也等于 classes.txt 行数。names 的顺序必须和标注里的类别序号严格对应,错一个位置,训练出来的模型就会把黑点认成划痕。我习惯在写 yaml 之前先把 classes.txt 复制过来,逐行核对序号,这个动作花不了一分钟,但能省掉后面几小时的排查。
3.2 训练命令与关键参数怎么设
假设你已经装好 ultralytics 包,一条命令就能启动训练:
yolo detect train \ data=plastic_bottle_defect/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/plastic_bottle \ name=exp1model=yolov8n.pt表示从 COCO 预训练权重开始微调,工业缺陷数据集通常几千张图,从头训容易过拟合,用预训练权重收敛更快。imgsz=640是输入分辨率,如果瓶身缺陷很小,比如黑点只有十几个像素,可以提到 1024,但显存占用会明显上升。batch=16在 8GB 显存上跑 640 分辨率一般够用,爆显存就降到 8。lr0=0.01是初始学习率,微调场景下如果 loss 震荡厉害,可以降到 0.001。patience=20表示验证指标 20 轮不提升就早停,避免无效训练。
训练过程中重点看三个输出:box_loss是否稳定下降、mAP50是否在验证集上持续上升、cls_loss是否出现异常尖峰。如果 box_loss 下降但 mAP50 不动,大概率是类别不平衡或标注框质量差。如果 cls_loss 突然飙升,检查是不是某个 batch 里出现了类别越界的标注。
3.3 训练完怎么验证模型真的学到了缺陷
训练结束后,runs/plastic_bottle/exp1/weights/下会有best.pt和last.pt。别只看 mAP 数字,拿几张验证集里没见过的图跑推理,肉眼确认缺陷框位置和类别:
yolo detect predict \ model=runs/plastic_bottle/exp1/weights/best.pt \ source=plastic_bottle_defect/images/val \ conf=0.25 \ save=Trueconf=0.25是置信度阈值,工业质检场景如果漏检代价高,可以降到 0.1 让模型多报一些,再人工复核。如果误检太多,就提到 0.5。推理结果会保存在runs/detect/predict/下,重点看小缺陷有没有被漏掉、瓶口和瓶底的缺陷有没有被混淆。我一般会挑 20 张出来逐张看,比盯着 mAP 曲线有用得多。
4. 塑料瓶缺陷检测的避坑与排查:五条血泪经验
4.1 类别序号错位导致 mAP 虚高但实际全错
现象:训练时 mAP50 能到 0.9 以上,但推理时模型把黑点标成划痕,把划痕标成变形。原因:classes.txt 的类别顺序和 data.yaml 里 names 的顺序不一致,或者标注文件里的类别序号是从 1 开始而不是从 0 开始。解决:写脚本统计标注里出现的所有类别序号,和 names 的 key 逐一对照。如果标注从 1 开始,要么全局减 1,要么在 names 里补一个占位类别。这个坑我踩过两次,第一次排查了一整天才发现是序号偏移。
4.2 图像和标签文件名大小写不一致
现象:训练启动时报No labels found,但明明 labels 目录下有文件。原因:图像叫Bottle_001.jpg,标签叫bottle_001.txt,Linux 下文件名区分大小写,YOLO 按 stem 匹配时找不到。解决:统一转成小写,或者写脚本批量重命名标签文件使其与图像 stem 完全一致。Windows 上开发时不容易发现,部署到 Linux 服务器就翻车。
4.3 验证集里出现训练集图片导致指标虚高
现象:验证集 mAP 比训练集还高,或者高得离谱。原因:数据集划分时没有去重,同一张图或高度相似的连拍图同时出现在 train 和 val 里。解决:用图像哈希或感知哈希对全量图片做去重,再重新划分。工业相机连拍产生的相邻帧相似度极高,如果按随机划分,很容易泄漏。我一般会按时间戳或产线批次划分,确保验证集来自不同批次。
4.4 小目标缺陷在 640 分辨率下直接消失
现象:瓶身黑点这种小缺陷在推理结果里完全看不到,但标注文件里确实有框。原因:YOLO 下采样 32 倍后,十几个像素的缺陷在特征图上只剩不到一个像素。解决:把imgsz提到 1024 或 1280,或者在数据增强里关闭马赛克增强的随机缩放,避免小目标被进一步缩小。如果显存不够,可以改用 YOLOv8 的 P2 检测头,专门针对小目标。
4.5 训练中 BN 层崩溃导致 loss 变 NaN
现象:训练到几十轮时 loss 突然变成 NaN,之后再也降不下来。原因:某个 batch 里出现了宽高为 0 的标注框,或者学习率过大导致梯度爆炸,BN 层统计量被污染。解决:训练前用 2.2 节的脚本扫一遍宽高异常,把 w 或 h 小于 1e-6 的框直接删掉。如果已经出现 NaN,降低lr0到 0.001 并加warmup_epochs=5,让模型在前几轮慢慢适应数据分布。
5. 从跑通到可用:提升塑料瓶缺陷检测精度的三个进阶技巧
第一轮训练跑通只是起点,工业质检对漏检率的要求通常比通用目标检测苛刻得多。我一般会从三个方向继续压精度。第一个方向是难例挖掘:把验证集里漏检和误检的图挑出来,单独复制到一个hard_examples目录,在下一轮训练时通过data.yaml把它作为额外的训练集加入,或者用copy_paste增强把缺陷区域粘贴到正常瓶身上生成合成样本。第二个方向是调整锚框和损失权重,YOLOv8 默认的锚框基于 COCO 数据集,塑料瓶缺陷的宽高比和 COCO 差异较大,可以用yolo detect train的anchors参数传入自己聚类得到的锚框,同时把box损失权重从默认 7.5 提到 10,让模型更关注定位精度。
第三个方向是推理后处理。工业场景下同一张图里同类缺陷可能相邻,NMS 的 IoU 阈值设 0.5 容易把两个靠近的黑点合并成一个。我通常会把iou阈值降到 0.3 并开启agnostic_nms,让不同类别的框也参与抑制,减少重叠误报。下面这段代码是我常用的验证脚本,把推理结果和标注真值做逐类对比,输出每个类别的漏检数和误检数:
from ultralytics import YOLO import os model = YOLO("runs/plastic_bottle/exp1/weights/best.pt") val_img_dir = "plastic_bottle_defect/images/val" val_lbl_dir = "plastic_bottle_defect/labels/val" classes = ["black_spot", "scratch", "deformation", "mouth_broken"] for img_name in os.listdir(val_img_dir)[:50]: stem = os.path.splitext(img_name)[0] results = model(os.path.join(val_img_dir, img_name), conf=0.25, iou=0.3) pred_cls = [classes[int(c)] for c in results[0].boxes.cls] gt_path = os.path.join(val_lbl_dir, stem + ".txt") gt_cls = [] if os.path.exists(gt_path): with open(gt_path) as f: for line in f: p = line.strip().split() if len(p) == 5: gt_cls.append(classes[int(p[0])]) miss = [c for c in gt_cls if c not in pred_cls] false = [c for c in pred_cls if c not in gt_cls] if miss or false: print(f"{img_name} 漏检: {miss} 误检: {false}")这段脚本对验证集前 50 张图逐张推理,把预测类别和真值类别做集合对比,输出漏检和误检的类别名。conf=0.25和iou=0.3是我在塑料瓶缺陷场景下比较常用的组合,你可以根据实际漏检和误检的比例微调。跑完这个脚本,你会得到一份具体的难例清单,比看整体 mAP 更有针对性。
最后说个习惯:每次改完参数或增强策略,我都会固定用同一批 50 张验证图跑一遍这个脚本,记录漏检和误检数量的变化。模型指标是波动的,只有同一批图上的对比才能告诉你这次改动到底有没有用。希望帮到你。
本文还有配套的精品资源,点击获取