简介:本资源为玻璃瓶缺陷检测数据集,聚焦瓶盖(cap)类缺陷的识别任务,面向从事工业质检、机器视觉算法开发与深度学习模型训练的研究人员及学生。包内共255个文件,以125张jpg图像和121个xml标注文件为核心,另含少量txt说明文件,压缩包约48.31MB,图像与标注一一对应,可直接用于目标检测模型的训练与验证。数据集覆盖多种瓶盖缺陷样本,适合作为YOLO、Faster R-CNN等检测框架的实战素材,帮助读者快速搭建缺陷检测流程、验证算法效果并完成模型调优。目前已有201人学习下载,可作为工业缺陷检测入门与算法对比的参考数据。
1. 玻璃瓶缺陷检测里,125 张 cap 缺陷图到底能干什么
先泼一盆冷水:125 张图,放在常规目标检测任务里连一个像样的训练集都算不上。但如果你手上拿到的就是这么一个压缩包,缺陷类型只标了 cap(瓶口/瓶盖区域缺陷),那它大概率不是让你从零训一个大模型的,而是让你验证一条小样本缺陷检测链路能不能跑通。玻璃瓶产线上的 cap 缺陷,典型表现是瓶口崩边、盖体歪斜、密封圈缺失、压盖不到位,这些缺陷在整瓶图像里占比极小,属于典型的小目标 + 小样本叠加场景。
我见过太多人拿到这种数据集,第一反应是直接上 YOLO 开训,结果 mAP 卡在 0.3 上下,然后开始怀疑人生。问题不在模型,在于 125 张图连划分验证集都捉襟见肘,更别说覆盖产线上光照、角度、瓶型的真实分布。所以这篇笔记要讲清楚的是:125 张 cap 缺陷图,正确的用法是搭一套可复现的基线流程,把数据增强、迁移学习、评估口径这三件事做扎实,先跑出一个能解释的结果,再谈要不要扩数据。适合正在做玻璃瓶质检、小样本工业缺陷检测的从业者,尤其是手上数据量不大、又必须给出可行性结论的人。
2. 小样本 cap 缺陷检测的技术选型:为什么不能直接套通用检测器
2.1 cap 缺陷的成像特点决定了预处理优先级
玻璃瓶瓶口区域是曲面高反光材质,cap 缺陷在图像里往往表现为边缘的暗裂、局部高亮异常或盖体轮廓的几何偏移。这意味着两件事:第一,缺陷信号高度依赖打光方式,如果数据集里的成像条件不统一,模型学到的很可能是光照伪影而不是缺陷本身;第二,缺陷区域相对整图极小,直接缩放到 640×640 输入,一个瓶口崩边可能只剩几个像素。
我一般会先做一轮数据体检,而不是急着写训练脚本。体检的核心是看三件事:图像分辨率分布、瓶口区域在画面中的位置是否稳定、缺陷标注框的宽高分布。这三项直接决定你后面是走整图检测还是先做 ROI 裁剪。
import os import cv2 import numpy as np # 遍历数据集,统计图像尺寸和标注框尺度分布 img_dir = "dataset/images" label_dir = "dataset/labels" sizes = [] box_areas = [] for name in os.listdir(img_dir): img = cv2.imread(os.path.join(img_dir, name)) h, w = img.shape[:2] sizes.append((w, h)) # YOLO 格式标注:class cx cy bw bh(归一化) label_path = os.path.join(label_dir, name.rsplit(".", 1)[0] + ".txt") if os.path.exists(label_path): with open(label_path) as f: for line in f: parts = line.strip().split() if len(parts) == 5: bw, bh = float(parts[3]), float(parts[4]) box_areas.append(bw * bh) # 归一化面积 print("图像尺寸种类:", set(sizes)) print("标注框归一化面积: 最小 %.5f 中位 %.5f 最大 %.5f" % ( np.min(box_areas), np.median(box_areas), np.max(box_areas)))这段脚本的作用是先摸清家底。sizes集合如果只有一两种尺寸,说明采集设备固定,预处理可以统一;如果五花八门,就得考虑 letterbox 还是直接 resize。box_areas的中位数尤其关键——如果归一化面积中位数低于 0.01,说明缺陷框在整图里占比不到 1%,这时候整图训练基本是浪费算力,应该先裁 ROI。参数上,bw * bh用的是归一化宽高,所以面积是无量纲的,方便跨分辨率比较。
2.2 迁移学习是小样本场景唯一理性的起点
125 张图从零训练,卷积核根本学不出有判别力的特征。常见做法是用在通用数据集上预训练过的检测骨干做初始化,然后只微调检测头加部分高层。选型上,如果追求部署轻量,我会优先考虑 YOLO 系列的小模型变体;如果缺陷更偏纹理异常而非明显几何目标,也可以考虑基于特征重建的异常检测思路,但那条路对标注依赖低、对成像一致性要求极高。
这里有个反直觉的点:小样本下,模型容量不是越大越好。大骨干在小数据上过拟合的速度快得惊人,训练 loss 一路降,验证集 mAP 纹丝不动甚至掉。我一般会把骨干冻结前若干层,只训后段和检测头,学习率也压到常规的十分之一量级。
# 以 YOLO 风格配置为例,说明冻结策略与学习率设置思路 # 冻结前 10 层骨干,只训练剩余层与检测头 freeze_layers = 10 lr0 = 0.001 # 初始学习率,小样本下比常规 0.01 低一个量级 warmup_epochs = 3 # 小数据 warmup 不宜过长,否则有效训练轮次太少 batch_size = 8 # 125 张图,batch 太大一个 epoch 只有十几步,梯度噪声反而有益 epochs = 150 # 小样本需要更多轮次观察收敛,但要配合早停参数说明:freeze_layers是经验值,骨干浅层学的是边缘、颜色这类通用特征,小数据没必要动;lr0压到 0.001 是为了避免预训练权重被少量样本带偏;batch_size取 8 是在显存允许下尽量保留梯度噪声,小样本训练里适度的噪声反而有正则效果;epochs给大是因为每轮迭代步数少,但必须配早停,否则后面全是过拟合。
2.3 数据增强要针对 cap 缺陷的物理特性设计
通用增强里,颜色抖动、随机裁剪、马赛克这些对小样本有帮助,但用在 cap 缺陷上要小心。瓶口缺陷很多是几何性的(崩边、歪盖),水平翻转通常安全,垂直翻转就可能把瓶口朝向搞反,制造出产线上不存在的样本。颜色抖动幅度也要克制,因为玻璃反光对亮度极敏感,抖太狠会让缺陷信号淹没在光照变化里。
我的习惯是分两层增强:几何层只做小幅平移、缩放、水平翻转;光度层做轻微亮度对比度扰动,幅度控制在 ±15% 以内。另外针对小目标,可以额外做一次以缺陷框为中心的局部裁剪放大,人为提升缺陷像素占比。
import random import cv2 import numpy as np def augment_cap_defect(img, boxes, brightness=0.15): # 几何增强:仅水平翻转 + 小幅平移缩放 if random.random() < 0.5: img = cv2.flip(img, 1) h, w = img.shape[:2] boxes = [[1 - x - bw, y, bw, bh] for x, y, bw, bh in boxes] # 翻转后框坐标同步 # 光度增强:亮度对比度微扰 alpha = 1.0 + random.uniform(-brightness, brightness) # 对比度 beta = random.uniform(-20, 20) # 亮度偏移 img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta) return img, boxes逻辑说明:翻转时标注框的 x 坐标要按1 - x - bw重算,这是 YOLO 归一化坐标下最容易写错的地方,漏改就会导致框和图像对不上,训练直接崩。alpha控制对比度,beta控制亮度,两者都限制在小范围,是为了模拟产线光照的正常波动而不是制造极端样本。这段增强函数应该挂在数据加载流程里,每个 epoch 随机触发,而不是离线把数据集扩增几倍存盘——离线扩增会让验证集也被污染。
3. 用 125 张图跑通训练与评估的完整步骤
3.1 数据集划分:125 张怎么分才不至于评估失真
125 张图,如果按 8:1:1 分,验证集只有 12 张,测试集 12 张,评估结果的方差大到没有参考意义。我的做法是:训练集留 100 张,验证集 25 张,先不单独切测试集,用交叉验证的思路多跑几轮,看 mAP 的波动范围。等流程稳定后,再固定一份划分做最终报告。
划分时必须做分层,保证 cap 缺陷的几种亚型(崩边、歪盖、缺圈)在训练和验证里都有分布。如果某类缺陷只有个位数样本,那评估时就要单独看它的召回,而不是只看总体 mAP。
# 按 8:2 划分,并保证每类缺陷在验证集中至少出现一次 python split_dataset.py \ --images dataset/images \ --labels dataset/labels \ --val_ratio 0.2 \ --seed 42 \ --stratify_by_class--seed 42固定随机种子,保证划分可复现;--stratify_by_class是分层的关键,没有这个参数,随机划分很可能把某类稀有缺陷全分进训练集,验证集里一个都没有,召回率就没法算。划分脚本跑完后,务必人工抽查验证集里是否每类缺陷都有代表样本。
3.2 训练配置与启动命令
配置文件的每一项在小样本场景下都要比常规更保守。除了前面说的冻结和学习率,还要特别注意锚框(anchor)的设置。如果你的 cap 缺陷框普遍很小,默认锚框尺度可能完全不匹配,导致正样本匹配不上,训练直接不收敛。
# data.yaml path: ./dataset train: images/train val: images/val nc: 1 names: ["cap_defect"] # hyp.yaml 关键项 lr0: 0.001 lrf: 0.01 # 最终学习率为初始的 1% momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 box: 7.5 # 框回归损失权重,小目标可适当调高 cls: 0.5 anchors: [[4,5, 8,10, 13,16]] # 针对小缺陷重设的小尺度锚框锚框那行是重点。默认锚框往往覆盖 32×32 以上的目标,而 cap 缺陷归一化后可能只有十几像素,必须用聚类的方式在你自己数据集的标注框上重新算一组锚框。box权重调高是为了让模型更关注定位精度,因为小缺陷的分类信号本来就弱,定位不准分类也跟着错。
启动训练:
python train.py \ --data data.yaml \ --hyp hyp.yaml \ --weights pretrained_backbone.pt \ --img 640 \ --batch 8 \ --epochs 150 \ --freeze 10 \ --patience 30 \ --project runs/cap_defect--patience 30是早停耐心值,验证集 30 轮不提升就停,避免在 125 张图上无限过拟合。--freeze 10对应前面的冻结策略。--img 640是输入尺寸,如果你的缺陷框特别小,可以考虑提到 960 甚至 1280,但显存和速度要权衡。
3.3 评估口径:小样本下别只看 mAP
125 张图的验证集,mAP 的小数点后第二位基本没有统计意义。我更关注三个指标:召回率(漏检在质检里代价最高)、误检率(过杀会导致产线频繁停机)、以及不同缺陷亚型的召回分布。评估时把置信度阈值调低一点看召回上限,再调高看误检下限,找到业务能接受的平衡点。
from collections import defaultdict # 按缺陷亚型统计召回,而不是只看总体 def eval_by_subtype(preds, gts, iou_thr=0.5): stats = defaultdict(lambda: {"tp": 0, "fn": 0}) for img_id, gt_list in gts.items(): pred_list = preds.get(img_id, []) matched = set() for gt in gt_list: hit = False for i, pred in enumerate(pred_list): if i in matched: continue if iou(gt, pred) >= iou_thr: matched.add(i) hit = True break key = gt["subtype"] # 崩边/歪盖/缺圈 stats[key]["tp" if hit else "fn"] += 1 return {k: v["tp"] / max(v["tp"] + v["fn"], 1) for k, v in stats.items()}这段代码的核心是把召回拆到亚型维度。iou_thr取 0.5 是检测任务的常规口径,但小目标下可以放宽到 0.3 观察趋势。matched集合防止同一个预测框被多个真值重复匹配。跑完你会看到,很可能总体召回 0.7,但某一类缺陷召回只有 0.3——这才是真正要回去补数据或调增强的方向。
4. 玻璃瓶 cap 缺陷检测的避坑清单:5 个我踩过的坑
4.1 坑一:验证集 mAP 很高,上线全漏检
现象:本地验证集 mAP 跑到 0.85,换一批新采集的图,漏检率飙升。原因:125 张图大概率来自同一批次、同一光照条件,验证集和训练集同分布,模型学到的是这批图的特定成像特征,不是缺陷本质。解决:划分验证集时尽量按采集批次或时间段切分,而不是纯随机;如果数据里有多批次信息,务必按批次分层。上线前一定要留一批完全没参与训练的图做盲测。
4.2 坑二:锚框没重设,训练 loss 不降
现象:训练几十轮,box loss 几乎不动,模型输出全是背景。原因:默认锚框尺度和 cap 缺陷框严重不匹配,正样本匹配阶段就没有足够的 anchor 被激活。解决:用 k-means 在你自己标注框上聚类出锚框尺度,替换配置里的 anchors。聚类时用归一化宽高,聚类数取 3 到 5 组即可,小数据集不需要太多。
4.3 坑三:增强过度,缺陷被"增强没了"
现象:加了马赛克和大幅随机裁剪后,训练集 loss 正常,但验证集表现反而下降。原因:cap 缺陷本身占比极小,马赛克拼接和大幅裁剪很容易把缺陷区域裁掉或拼到边缘,模型看到的大量样本其实不含有效缺陷信号。解决:小目标场景慎用马赛克,随机裁剪要保证裁剪后缺陷框仍完整保留,可以写个过滤逻辑,裁剪后缺陷框面积小于原面积 50% 的样本直接丢弃。
4.4 坑四:类别不平衡被忽略
现象:某一种 cap 缺陷召回极低,其他类正常。原因:125 张图里各类缺陷数量天然不均,稀有类可能只有几张,损失被多数类主导。解决:在损失里给稀有类加权,或者用重采样让每个 batch 里稀有类出现概率提升。加权系数不要拍脑袋,按类别频率的倒数来设,再裁剪到合理范围,避免权重过大导致训练震荡。
4.5 坑五:置信度阈值照搬默认值
现象:评估时按默认 0.25 阈值,误检一堆;调到 0.5,漏检又上来了。原因:小样本模型输出的置信度分布和在大数据集上训出来的模型完全不同,默认阈值不适用。解决:在验证集上画 PR 曲线,按业务对漏检和过杀的容忍度选阈值。质检场景通常优先保召回,阈值可以压到 0.15 到 0.2,再用后处理规则过滤明显误检。
5. 从 125 张到可落地:小样本缺陷检测的进阶技巧
把基线跑通只是第一步,真正决定这套方案能不能落地的是你怎么处理"数据永远不够"这件事。我自己的习惯是,先用 125 张图跑出一个可解释的基线,把召回和误检的数字摆出来,然后拿这个基线去反推需要补什么样的数据。比如评估发现歪盖缺陷召回只有 0.3,那就针对性地去采集歪盖样本,而不是盲目扩到一千张。
一个具体技巧是主动学习式的迭代:用当前模型对未标注的产线图做推理,挑出置信度处于中间区间(比如 0.3 到 0.6)的样本,这些是模型最"犹豫"的,人工标注后加进训练集,信息量最大。这比随机标注新样本的效率高得多。下面这个筛选逻辑可以直接用:
def select_hard_samples(preds, low=0.3, high=0.6): # 挑选模型置信度处于中间区间的样本,优先送标 hard = [] for img_id, pred_list in preds.items(): for p in pred_list: if low <= p["conf"] <= high: hard.append((img_id, p["conf"])) break # 按置信度升序,越接近决策边界的越优先 return sorted(hard, key=lambda x: abs(x[1] - (low + high) / 2))low和high的区间不是固定的,取决于你模型当前的置信度分布。如果模型整体置信度偏低,区间要相应下移。排序用abs(conf - 中点)是为了把最靠近决策边界的样本排前面,这些样本对模型改进的价值最大。
另一个值得做的验证是跨批次稳定性测试。把不同时间段采集的图各留一小批,分别评估,看召回波动有多大。如果波动超过 15 个百分点,说明模型对成像条件太敏感,这时候与其继续调模型,不如先回去统一打光和拍摄角度——工业质检里,成像稳定性带来的收益往往比换模型大得多。
最后说个我自己的教训:早期做这类小样本缺陷检测,我总想着一步到位把模型调到最好,结果在 125 张图上反复调参,调到最后连自己都不确定哪个配置是真的好。后来改成先固定一套保守配置跑出基线,把每次改动的效果记录清楚,反而推进得更快。小样本场景下,可复现的流程比单次的高指标值钱得多。希望帮到你。
本文还有配套的精品资源,点击获取