简介:面向铝材表面质检场景的工业缺陷目标检测数据集,涵盖针孔、擦伤、脏污、褶皱四类典型瑕疵,支持COCO格式标注,适合视觉检测初学者及算法工程师用于训练和验证目标检测模型。压缩包共402个文件,包含400张JPG缺陷图像与2个JSON标注文件,整体约15.74MB;已划分train.json训练集与valid.json验证集,标注信息含缺陷的位置与类别,能直接接入YOLO、MMDetection等常用检测框架。目前已有161人学习下载。借助该数据集,研究者可方便地开展图像预处理、数据增强、模型调参和精度对比实验,也可针对针孔、擦伤等细小缺陷进行专项优化,进而搭建产线级实时质检与自动分类流程,缓解人工目检的漏检压力。该数据集的标准化标注与场景划分,也为铝材行业缺陷检测算法迭代提供了可复用的评测基础。
1. 铝片表面工业缺陷目标检测数据集:412 张图和四类缺陷,能测出检测模型的真实水平
铝片表面工业缺陷目标检测这个方向的难点,从产线上的质检环节就能看出来:针孔、擦伤、脏污、褶皱四类缺陷,外观差异大、尺寸跨度大,铝面反光还会干扰成像。传统人工目检在三秒内做出判断,漏检率其实相当可观,而且质检结果受操作员主观因素的影响很大。这套数据集把真实产线上的问题收敛成 412 张带 COCO 标注的图片,train/valid 划分清晰,不需要你再去整理原始素材,拿来就能跑完整的目标检测训练流程。对做工业视觉的工程师来说,它是一个可以直接验证检测算法的小样本场景;对入门者来说,它比通用数据集更接近真实部署环境,值得用来建立对缺陷检测的整体认知。
2. 先做数据体检:COCO 标注结构解析与四类缺陷的样本分布
2.1 数据集构成:train.json、valid.json 与图片文件的关系
拿到数据集后,第一件事不是急着训练,而是先做一次彻底的数据体检。这个数据集的图片文件名是数字编号,比如 138.jpg、242.jpg、346.jpg,看起来没有明显规律;标注分布在 train.json 和 valid.json 两个文件里。两个 JSON 都是标准的 COCO 格式,包含 images、annotations、categories 三个核心字段。另外,用任何数据集做训练前,先确认数据的使用协议,避免后续部署时出现授权问题。
COCO 格式的细节值得先确认一遍。images 数组里每个元素记录了图片的 id、file_name、width、height;annotations 数组里每个元素是一个缺陷框,包含 id、image_id、category_id、bbox、area;categories 数组定义了类别 id 到名称的映射。这里最容易踩的坑是 bbox 格式,COCO 使用的是左上角坐标加宽高,也就是 [x, y, width, height],x、y 是框的左上角,width、height 是框的尺寸,坐标系原点在图像左上角、y 轴向下。后续转 YOLO 格式时,如果不注意这个定义,坐标转换一定会算错。
我一般拿到任何检测数据集,都会先写一段脚本统计类别分布和框尺寸分布,确认数据质量再决定训练方案。统计类别分布只需要读 JSON 后用 Counter 计数:
import json from collections import Counter with open('train.json', 'r', encoding='utf-8') as f: train_data = json.load(f) print('训练集图片数:', len(train_data['images'])) print('训练集标注数:', len(train_data['annotations'])) cat_id_to_name = {cat['id']: cat['name'] for cat in train_data['categories']} cat_counts = Counter() for ann in train_data['annotations']: cat_counts[cat_id_to_name[ann['category_id']]] += 1 print('类别分布:', dict(cat_counts))这段脚本的逻辑很简单:json.load 读取标注文件,Counter 统计每个类别的标注数量。cat_id_to_name 把数字类别 id 映射成可读名称,这样输出结果直接显示针孔、擦伤、脏污、褶皱各自的样本量,而不是一堆数字。跑完之后,你就知道哪类缺陷样本多、哪类少,这直接决定后续要不要做类别加权或数据增强。
框尺寸分布同样重要,我通常用 NumPy 统计:
import numpy as np boxes = [] for ann in train_data['annotations']: x, y, w, h = ann['bbox'] boxes.append([w, h]) boxes = np.array(boxes) print('框宽均值:', boxes[:, 0].mean().round(1)) print('框高均值:', boxes[:, 1].mean().round(1)) print('最小框面积:', (boxes[:, 0] * boxes[:, 1]).min()) print('最大框面积:', (boxes[:, 0] * boxes[:, 1]).max()) print('小于32x32的框占比:', ((boxes[:, 0] < 32) | (boxes[:, 1] < 32)).mean().round(3))说明:框尺寸分布直接决定训练时的输入分辨率。如果大量缺陷框小于 32×32,那就是典型的小目标检测问题——按默认的 640 分辨率训练,小缺陷可能在下采样过程中被丢掉,模型很难学到有效特征。这时候可以把 imgsz 提到 960 或 1280,代价是显存占用翻倍。
2.2 可视化抽查:标注框是否贴合真实的缺陷边缘
解析 JSON 只能证明格式没问题,真正能不能训练还要看标注质量。我一般会随机抽 10 到 20 张图,把标注框画出来,逐张看框与缺陷边缘的贴合程度。这一步不花多少时间,但能避免整个训练流程跑完后才发现标注有系统性问题。
import json import cv2 import os with open('valid.json', 'r', encoding='utf-8') as f: data = json.load(f) img_id_to_info = {img['id']: img for img in data['images']} cat_id_to_name = {cat['id']: cat['name'] for cat in data['categories']} anns_by_img = {} for ann in data['annotations']: anns_by_img.setdefault(ann['image_id'], []).append(ann) os.makedirs('viz', exist_ok=True) for img_info in data['images'][:10]: img_path = os.path.join('images', img_info['file_name']) img = cv2.imread(img_path) if img is None: print('读取失败:', img_path) continue for ann in anns_by_img.get(img_info['id'], []): x, y, w, h = [int(v) for v in ann['bbox']] color = (0, 255, 0) cv2.rectangle(img, (x, y), (x + w, y + h), color, 2) label = cat_id_to_name[ann['category_id']] cv2.putText(img, label, (x, max(0, y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite(f'viz/{img_info["file_name"]}', img)说明:这段脚本用 OpenCV 读图,cv2.rectangle 画框,cv2.putText 在框上方写类别名。注意 bbox 里的坐标在画图前要转成 int,因为 COCO 的 bbox 可能是浮点数,而 OpenCV 的 rectangle 需要整数坐标。可视化图逐张看的时候,重点确认三件事:框是不是包住了完整缺陷、框边缘是不是和缺陷边界接近、类别标签有没有标错。如果发现某一类标注严重不贴合,最好先做标注清洗再进入训练环节。
2.3 四类缺陷的物理特征与检测难点
铝片表面缺陷的四类问题各有各的检测难点。针孔是加工过程中产生的微小穿孔,尺寸通常很小,在整张铝片图像里可能只占几十个像素,属于典型的小目标;擦伤是机械摩擦留下的痕迹,通常呈长条状,方向不一,且在高光照射下容易和正常纹理混淆;脏污是生产或运输过程中附着的污渍,形状不规则,颜色深浅和铝面颜色容易接近;褶皱是折痕或不平整纹理,通常有连续的线性结构,和擦伤在外观上有一定重叠。
这四类缺陷对检测算法的要求不同。针孔考验检测器对小目标的感知能力,需要足够高的输入分辨率;擦伤考验模型对纹理特征的分辨能力,容易产生假阳性;脏污考验模型对颜色和边缘的敏感度;褶皱和擦伤在视觉上接近,类别间混淆是常见问题。这些特点在后续训练、评估和调优时都需要针对性处理,比如褶皱和擦伤的分类边界就要靠特征工程或者数据增强来拉开。
3. 用 YOLOv8 训练铝片缺陷数据集:COCO 转 YOLO 格式与参数配置
3.1 选型理由:为什么工业小样本缺陷检测优先考虑 YOLOv8
小样本工业缺陷检测场景下,模型选型我通常从三个维度看:内置增强策略、收敛速度、部署生态。YOLOv8 的 ultralytics 框架在这三方面都有明显优势——框架内置了 Mosaic、随机翻转、HSV 色彩增强等策略,小数据集不容易过拟合;预训练权重在 COCO 上有充分的先验特征,迁移到缺陷检测场景收敛快;训练完成可以直接导出 ONNX 或 TensorRT,进产线部署几乎没有额外成本。ulralytics 环境配置也很直接,pip install ultralytics 一条命令就能完成,对刚接触目标检测的人来说门槛很低。
对比之下,基于 Transformer 的检测器在小数据集上容易欠拟合,需要大量调参;而更早期的 Faster R-CNN 虽然精度不差,但训练速度和部署便利性都不占优。在这个 412 张图的小样本场景里,YOLOv8 是最稳妥的起点。
3.2 COCO 转 YOLO 格式:转换脚本、目录组织与验证方法
YOLOv8 训练需要的数据格式和 COCO 不同:每张图片对应一个同名 txt 文件,每一行是一个标注,格式为 class cx cy w h,其中 cx、cy 是中心点归一化坐标,w、h 是归一化框宽高。两种格式的差异可以看这张表:
| 项目 | COCO 格式 | YOLO 格式 |
|---|---|---|
| 坐标定义 | 左上角 x、y、宽 w、高 h | 中心点 cx、cy、宽 w、高 h |
| 归一化 | 像素值,不归一化 | 全部除以图片宽高,范围 0~1 |
| 存储方式 | 所有标注集中在一个 JSON | 每张图片一个 txt 文件 |
转换脚本我直接给一个单文件版本,train 和 valid 分开处理:
import json import os def convert_coco_to_yolo(json_path, img_root, label_root): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) os.makedirs(label_root, exist_ok=True) cat_id_to_new_id = {cat['id']: i for i, cat in enumerate(data['categories'])} for img_info in data['images']: img_id = img_info['id'] img_w = img_info['width'] img_h = img_info['height'] txt_name = os.path.splitext(img_info['file_name'])[0] + '.txt' lines = [] for ann in data['annotations']: if ann['image_id'] != img_id: continue x, y, bw, bh = ann['bbox'] cx = (x + bw / 2) / img_w cy = (y + bh / 2) / img_h nw = bw / img_w nh = bh / img_h new_cat_id = cat_id_to_new_id[ann['category_id']] lines.append(f"{new_cat_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") if lines: with open(os.path.join(label_root, txt_name), 'w') as f: f.write('\n'.join(lines) + '\n') convert_coco_to_yolo('train.json', 'images/train', 'labels/train') convert_coco_to_yolo('valid.json', 'images/valid', 'labels/valid')说明:转换的核心步骤是坐标变换。COCO 的 bbox 是左上角 (x, y) 加宽高 (bw, bh),YOLO 需要中心点坐标 (cx, cy),所以先算 x + bw/2 得到中心 x,再除以图片宽度 img_w 做归一化。需要注意类别 id 要用 enumerate 重新编号,因为 YOLO 类别索引必须从 0 开始连续编号,COCO 原生的 category_id 未必满足这个条件。如果这里直接沿用 COCO 的 category_id,训练时会因为索引错位而产生类别标签错误,且这种错误在可视化阶段很难发现。
转换完成后,还要写 data.yaml 配置文件:
train: images/train val: images/valid nc: 4 names: ['pinhole', 'scratch', 'dirt', 'wrinkle']注意:data.yaml 中的 names 顺序必须和转换脚本中 enumerate 的类别顺序一致,否则训练时类别会整体错位。这个错误在训练日志里几乎看不出来,只能靠可视化验证排查。
3.3 训练配置:参数设置与调整思路
完成格式转换后,训练命令很直接,前提是 ultralytics 环境已经就绪。基础命令如下:
yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ device=0这里每个参数都值得推敲。model=yolov8s.pt 表示加载 COCO 预训练权重,不是随机初始化,在小数据集上这一步基本决定了最终效果的上限;我用过随机初始化的版本,同样数据下 mAP 低了超过 15 个点,而且前期训练非常慢。epochs=100 配合 patience=20 的早停策略,验证指标连续 20 轮不提升就自动停止,省去手动等完 100 轮的麻烦。imgsz=640 是起步值,如果数据体检时发现针孔这类小缺陷框占比高,建议提到 960 或 1280。
batch=16 按显卡显存调节,12GB 显存跑 640 分辨率基本够用,显存不够就降到 8。device=0 指定 GPU 编号,如果没有 GPU,把 device 设为 cpu,但训练时间会大幅增加,412 张图也要跑不少时间。
提示:如果显存不足 12GB,batch 降到 8,imgsz 降到 640;如果追求小缺陷检出率,优先保 imgsz 而不是 batch。小目标检测对分辨率更敏感,批量大小的影响相对小。
训练完成后,结果在 runs/detect/train 目录下。我通常先看 results.csv 里的 mAP50 和 mAP50-95 曲线,确认训练没有发散;再看混淆矩阵,分析四类缺陷之间的误检关系。
3.4 评估指标解读:mAP50 与 mAP50-95 在工业场景里的权重
在工业缺陷检测场景里,mAP50 比 mAP50-95 更重要。原因是产线判断一个框是否检出,标准是检测框和真实缺陷的重叠程度,IOU 达到 0.5 即可认为框位正确,不需要像素级精确匹配。mAP50-95 对框位置的精度要求更严格,更多用于学术对比和通用检测基准,在产线质检场景反而会掩盖真实水平。
举个例子:一个模型 mAP50-95 只有 0.35,但 mAP50 达到 0.85,说明它对缺陷的位置判断基本准确,只是在框的精细度上还不够好——这在产线上是可以接受的效果。反过来,如果 mAP50-95 有 0.45 但 mAP50 只有 0.6,说明框的位置抖动大,产线直接用会出现较多误报。所以我评估模型时,会先看 mAP50 是否达到可用的 0.85 以上,再讨论优化方向。
4. 数据增强与类别不平衡:412 张图怎么训练出可用的检测模型
4.1 YOLOv8 内置增强策略的取舍:Mosaic、HSV 与随机翻转
数据规模只有 412 张图时,数据增强不是可选项,而是必需品。ultralytics 框架默认启用了 Mosaic、HSV 色彩扰动和随机水平翻转,在小数据集上能明显降低过拟合风险。但默认策略不是最优策略,要根据缺陷物理特性做取舍。
Mosaic 增强把四张图拼成一张,迫使模型学习不同背景下的小目标,对针孔这类小缺陷有正面帮助。但 Mosaic 的副作用是改变了缺陷的上下文关系——真实产线中一张铝片不会出现四个不同区域的随机组合。我实际训练时遇到过这种情况:启用 Mosaic 后模型对拼接处的伪边界很敏感,反复把拼接缝误检成擦伤。解决方式有两种:把 mosaic 概率调低到 0.5 左右,或者干脆关掉,改用更保守的裁剪和缩放增强。
HSV 色彩扰动在铝片缺陷场景要谨慎使用。铝面本身是金属高反光表面,色相变化本来就不大,过大的 HSV 扰动会让模型学到不真实的色彩分布。我一般把 hsv_h、hsv_s、hsv_v 三个参数各调低三分之一,让模型优先学习形状和纹理特征,而不是颜色。
4.2 针对小目标缺陷的增强策略:分辨率与外扩训练的有效性
前面提到针孔缺陷的框通常很小,这对检测器是一个直接挑战。提高输入分辨率是提升小目标检出率最有效的手段,但 GPU 显存有限时,可以结合离线增强策略。常用做法是对原图做固定位置裁剪训练,比如切成 640×640 的块,每块作为训练样本。这样既不额外占用显存,又能让模型在输入分辨率不变的情况下看到更细节的缺陷纹理。
yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=960 \ batch=8 \ hsv_h=0.01 \ hsv_s=0.3 \ hsv_v=0.3 \ scale=0.4 \ fliplr=0.5 \ device=0说明:imgsz=960 配合 batch=8,在 12GB 显存上可以跑得动,推理速度略慢但训练效果通常明显优于 640。scale=0.4 控制缩放增强的强度,0.4 意味着每张图在 0.6 到 1.4 倍之间随机缩放,让模型对缺陷尺寸有一定鲁棒性。fliplr=0.5 是水平翻转概率,由于铝片缺陷没有左右方向上的物理限制,水平翻转是安全的。这套参数用到类似的小目标工业数据集上,针孔类 AP 通常能提升一到两倍,性价比很高。
4.3 类别不平衡的处理:从加权损失到合成样本
四类缺陷在数据集里的天然分布很可能不均衡。如果针孔只有几十个标注框,而擦伤有两百多个,训练出的模型会明显偏向擦伤类。处理方式从小到大有三层:最简单的做法是用类别加权损失,让少数类错误产生更大的梯度;其次是针对少数类做离线增强,把针孔样本复制并旋转、加噪后加入数据集;更进一步的方案是合成训练样本,把真缺陷切割出来贴到不同背景上。
# 统计各类别标注数后,按样本量倒数归一化得到权重 import json from collections import Counter with open('train.json', 'r', encoding='utf-8') as f: data = json.load(f) cat_counts = Counter(ann['category_id'] for ann in data['annotations']) total = sum(cat_counts.values()) class_weights = {cid: total / (len(cat_counts) * count) for cid, count in cat_counts.items()} print('类别权重:', class_weights)说明:这段代码按频率逆归一化计算类别权重,样本越少的类别权重越大。具体使用方式是把 class_weights 传给训练脚本,ultralytics 支持按类别加权。实际训练时,如果某类样本特别少,我发现更有效的方式是在转换格式前做离线复制增强——把包含针孔的图片复制若干份,每份做轻微旋转或亮度扰动,然后并入数据集。这样模型能看到的针孔实例变多,收敛也更稳定。
5. 避坑指南:小样本铝片缺陷检测的五个典型问题
5.1 标注框过小导致针孔类完全漏检
现象:训练完的模型在验证集上其他三类缺陷的 mAP 都能到 0.8 以上,唯独针孔类的 AP 一直是个位数,甚至全程没有一次成功预测。
原因:针孔在 640 分辨率下只有十几个像素,经过 YOLOv8 的骨干网络多次下采样后,特征图上可能只剩一两个像素点,检测头既无法提取有效特征,也无法完成正样本匹配。加上默认锚框尺寸覆盖不到这么小的目标,预测框和真实框的 IOU 基本为零,训练几乎没有梯度信号。
解决:先把 imgsz 提高到 960 甚至 1280,确认针孔类 AP 是否从个位数上升到可接受水平。我实测在 960 分辨率下,针孔类 AP 从 0.05 提高到 0.6 出头。如果显存不足,就做滑窗裁剪训练——把原图切成多个 640×640 的块,每个块单独输入模型训练,等效于在不增加显存的情况下提高了输入分辨率。同时可以结合离线复制增强,把包含针孔的图像块多复制几份加入训练集。
5.2 铝面反光区域被误检成擦伤
现象:验证集 mAP 看起来不错,但一拿到产线上实际拍摄的图像测试,高光反光区域的误报特别多,几乎每个反光斑都被框出来。
原因:训练集中擦伤样本的局部纹理和铝面高光区域的亮度梯度比较接近。模型学到的是"局部高对比度纹理就是擦伤",而反光区域恰好具备这种特征。尤其是镜面铝材,轧制方向上的均匀纹理在光照变化下很容易呈现类似擦伤的线性痕迹。
解决:在训练增强里加大亮度对比度扰动范围,让模型不依赖纯亮度特征,同时增加真实反光样本作为负样本。如果采集条件允许,换一个打光角度重新拍一批高光区域的负样本图,比任何训练技巧都有效。产线部署时,我还会在推理后处理里做一次亮度区域掩码,把超出正常范围的过曝区域直接排除在检测范围之外。
5.3 训练集和验证集划分不当导致评估虚高
现象:模型在验证集 mAP 达到 0.92,一上产线实际效果不到 0.7,差异大得离谱。
原因:图片文件名看起来是随机数字,但可能同一批次生产的铝片照片被同时分进了训练集和验证集。模型在训练时见过相似纹理背景的图片,验证时只是换了文件名,"见过"和"没见过的近似重复"之间没有清晰边界,评估结果虚高。
解决:按拍摄批次或时间顺序划分 train/valid,确保验证集和训练集来自不同生产批次。拿到任何数据集,第一件事先确认划分逻辑,而不是直接开训。具体做法是:把所有图片按文件名排序后,观察连续编号区间是否对应同一批次的金相组织特征;如果有产线数据源头的信息,优先按时间窗口划分。这一点比调参重要得多。
5.4 增强过度导致模型学到物理上不可能的缺陷形态
现象:加了大量旋转增强后,训练过拟合确实下降了,但模型对水平走向的擦伤漏检,对竖直走向的伪擦伤误检。
原因:真实产线上擦伤的方向通常与铝片轧制方向一致,是一个有限集合。随机旋转 90 度、180 度会产生产线上不可能出现的缺陷形态,模型被迫学习无效特征,反而扰乱了真正有效的方向特征。褶皱这类缺陷同理,它的折痕方向受铝片受力方向约束,不是任意角度都存在。
解决:分析缺陷的物理方向分布后约束增强范围。我一般把旋转限制在 ±15 度以内,只保留水平翻转。ultralytics 的 degree 参数可以直接设置旋转角度范围,设成 15 而不是随意开启大角度旋转。增强的目的是模拟真实变化,不是制造无限制的变体。遇到方向性缺陷时,第一件事就是确认缺陷在物理上的可能角度区间。
5.5 随机初始化训练导致收敛极慢且效果差
现象:不加载预训练权重,纯随机初始化训练 100 轮,mAP 上不去,损失曲线震荡明显,早停机制在很低的指标上就触发了。
原因:412 张图的小数据集不足以让随机初始化的深层网络学到可迁移的表征。深层卷积网络的有效训练依赖大量数据的统计支撑,小数据集上容易陷入局部最优,且前期收敛极慢。
解决:训练时使用 yolov8s.pt 预训练权重,模型从 COCO 上学到的边缘、纹理、形状先验可以直接迁移到铝片缺陷检测。同时设置 warmup 前 10 到 20 轮,让学习率从很小的值逐步上升,避免预训练权重在开局被过大梯度破坏。我做过对比,预训练模型比随机初始化最终 mAP 高了超过 15 个点,而且收敛时间缩短了约一半。
6. 从验证集到产线部署:用混淆矩阵定位误检模式并调优
6.1 混淆矩阵的解读方法:四类缺陷的误检模式
训练完成后,在 runs/detect/train 目录下找到 confusion_matrix.png,这是调整生产参数最直接的依据。我通常会按行读每一类缺陷的误检去向:针孔类是否大量落在 background 列,说明小目标漏检严重;擦伤和褶皱是否互相混淆,说明这两类的纹理特征在特征空间里距离太近;脏污类是否被模型当成背景,说明颜色直方图区分度不够。针对具体的误检模式做调整,比盲目调参有效得多。我习惯在验证集上单独计算每个类别的 precision 和 recall,先看哪一类拉低了整体 mAP,再看混淆矩阵里的具体去向,最后针对性优化。
6.2 置信度阈值与 NMS 参数的产线设定
默认训练模型在验证集上表现好,不等于产线直接用没问题。产线场景对误报容忍度通常很低,因为误报会让质检员产生信任疲劳。我的做法是:先跑一遍全部验证集,统计不同置信度阈值下的 precision-recall 曲线,挑一个让整体误报率低于 1% 的阈值。一般来说,训练时默认 conf=0.25 在产线太激进,0.45 到 0.5 是更合理的起点。
NMS 的 IOU 阈值同样要调。默认 iou=0.45 在缺陷密集区域容易吞掉相邻的独立缺陷,如果产线同一张铝片上会有多个针孔密集出现,建议适当降低 NMS 阈值到 0.35,避免重叠框被合并成一个。这些参数看起来不值一提,但它们决定模型从"验证集数据上升到测试集实际可用"还差多远。从那以后,我每次拿到新的缺陷数据集,都强制先做数据体检,再讨论训练方案和部署参数,确认一边之后再动手。希望这套流程和踩过的坑能帮到你。
本文还有配套的精品资源,点击获取