
简介面向钢板表面缺陷检测与目标检测模型训练的数据集由铝型材公开数据集与德国DAGM数据集混合构建从两类各含10个类别的源数据中筛选出划伤、孔洞、焊缝三类典型缺陷并使用高斯噪声扩充样本模拟贴近工业现场的噪声干扰。压缩包共2000个文件以jpg原图和对应xml标签文件为主体另含txt说明整体约754.93MBxml为VOC格式部分标签经手工标注修正目录整理后可直接接入YOLO、Faster R-CNN等常见检测框架无需额外转换。标签内含缺陷类别与位置框既可用于分类任务也可用于检测任务的训练与评估。该包为完整资源近1.67GB的第一部分适合高校学生、算法工程师及竞赛选手在样本准备、模型训练和缺陷识别效果对比中直接取用目前已有1023人学习下载。1. 钢板表面缺陷数据集把DAGM和铝型材混在一起到底能解决什么问题“钢板表面缺陷数据集”这个名字听起来像是某份现成的官方发布但把它拆开看做法其实很直白拿德国DAGM工业纹理缺陷数据与铝型材产线缺陷图混合再用高斯噪声把样本量撑起来。这类混合数据集在缺陷检测项目里非常常见因为真实产线的缺陷样本积累周期长、数量少先用公开数据打底、合成一份能用来训练检测或分类模型的底料是最经济的启动方式。适合的人群也很明确正在做金属表面缺陷检测手里真实缺陷图不够又希望先把标注格式、训练链路和数据增强策略跑通的人。这篇文章从一个做缺陷检测的工程师视角把这个混合数据集从头拆开——怎么制作、参数怎么设、哪里容易翻车以及怎样证明它值得被用进你的项目。2. 数据源拆解DAGM的灰度纹理与铝型材实拍图各自的长处、短板与格式差异2.1 DAGM数据的核心价值纹理缺陷的多样性先讲DAGM。这个数据集来自德国DAGM德国模式识别学会当年的工业光学检测公开评测业内习惯直接叫DAGM数据集。它的设计初衷是模拟真实工业纹理表面上的缺陷检测场景常见的是10类左右的纹理背景与缺陷组合图像大多是灰度图缺陷形态以划痕、斑点、纹理异常为主而且缺陷在整幅图里占比很小有的只有几十个像素宽。对钢板缺陷检测来说DAGM最大的价值恰恰在于“它不是钢板照片”。钢板表面有轧制纹、氧化色、油污和反光DAGM里大量“缺陷混在纹理背景里”的视觉形态和钢板表面缺陷的难点在本质上是同一类问题。用它的灰度纹理去扩充数据模型更容易学到在复杂表面中把目标分离出来而不是简单地靠高对比度去套框。这也是为什么很多金属表面检测的起步项目都会先拿DAGM做预训练或者做源码混合成本低、纹理覆盖面广。2.2 铝型材数据的核心价值真实产线的噪声与遮挡铝型材数据解决的是另一个问题真实感。公开渠道能拿到的铝型材表面缺陷数据多数来自型材生产线的光学相机常见缺陷类别包括擦花、脏点、碰伤、起皮等特征非常鲜明目标对比度低、光照不均匀、背景经常夹杂型材凹槽和反光纹路。这些图像与钢板产线相机的成像方式很接近尤其是缺陷往往出现在边缘或凹槽附近对模型学习“缺陷与边界共存”很有帮助。但铝型材数据的短板同样明显。公开版本通常样本量不大几百到几千张不等标注质量参差不齐有些来源给的是VOC格式XML有些给COCO格式JSON甚至有些只给原图不标注。常见做法是拿到手后先根据自己项目的类别体系重新清洗一遍把模糊框、漏标框、跨类目标全部捋一遍。混合制作之前先别急着合并文件把两套数据的“家底”盘清楚是后续所有流程的地基。2.3 两类数据合流前先想清楚标注粒度的差异DAGM和铝型材最核心的差异不在图像格式而在标注粒度。DAGM官方的标注是像素级掩码也就是说“缺陷的每一个像素是否属于缺陷”都被标记过铝型材数据多数是框级检测标注一个缺陷给一个矩形框。如果目标是做目标检测必须把DAGM的掩码先转成检测框如果目标做分割则要把铝型材的框反向改造成掩码后者几乎没有现成工具所以我一般建议做检测而不是分割。另一个容易忽略的点是类别命名。DAGM里一个类别可能叫“scratches”铝型材数据里叫“擦花”语义上是一类东西但在标注文件里对应完全不同的类别编号。混合前必须拉出一张类别映射表把“划痕/擦花/scratch”映射到同一个类别ID否则模型训练完会发现同类缺陷被拆成两类相互干扰学习。这个表在制作开始时就要定下来后端改一次要重新生成全部标签非常折腾。3. 用高斯噪声扩充数据集sigma怎么取加在哪些样本上才算科学3.1 高斯噪声为什么适合缺陷数据扩增高斯噪声是工业成像系统里最接近真实的扰动之一。产线相机的传感器噪声、光照抖动、信号传输干扰在统计上都近似服从高斯分布。给缺陷图像添加高斯噪声相当于模拟同一块钢板在不同相机参数、不同光照下的成像结果能有效提升模型对传感器差异的鲁棒性。相比旋转、裁剪、色彩抖动这类几何或颜色增强高斯噪声对缺陷形态的影响更小更适合“结构缺陷”类任务。这个混合数据集的做法是先拿到DAGM和铝型材的原始缺陷图再用高斯噪声对样本做离线扩增。扩增倍数一般控制在2到4倍比较合理比如原始数据每类有500张扩增后到1500到2000张太少效果不明显太多会让训练集中充满相似样本模型容易在验证集上过拟合。3.2 一个可用的高斯噪声扩充函数与sigma取值常见的做法是写一个独立的扩充脚本遍历原始图像逐张添加不同强度的噪声并输出新文件。下面这段代码是我习惯用的核心函数输入是一张0到255范围的图像输出是加噪后的图像import cv2 import numpy as np def add_gaussian_noise(img, sigma10): 给图像添加高斯噪声 img: uint8 类型的图像shape 为 (H, W) 或 (H, W, C) sigma: 高斯分布标准差0~255 灰度空间下经验取 5~15 noise np.random.normal(0, sigma, img.shape) noisy np.clip(img.astype(np.float32) noise, 0, 255).astype(np.uint8) return noisy if __name__ __main__: img cv2.imread(sample.jpg) noisy add_gaussian_noise(img, sigma12) cv2.imwrite(sample_noise12.jpg, noisy)这段代码里有两个关键参数。第一个是sigma它决定噪声强度。灰度0到255空间下sigma取5左右属于轻微噪声肉眼几乎看不出变化sigma取10到12时噪点明显适合扩充训练集sigma超过20时图像画质严重劣化缺陷边缘也会被噪声淹没不推荐。如果图像已经归一化到0到1空间sigma对应取0.02到0.06。第二个关键是np.random.normal的随机性每次运行都会生成不同噪声图案这正是扩增想要的效果——同一张原图可以生成多张细节不同的样本。提示建议把sigma设成区间而不是固定值比如[6, 8, 10, 12]轮换使用能让扩充后的样本噪声水平更多样模型泛化效果更好。3.3 什么时候不该加噪声三类典型场景第一原始缺陷的对比度本来就低于噪声强度时。铝型材里的某些暗色擦花灰度差只有20左右sigma加到10以上缺陷就彻底溶进噪点里模型训练时根本学不到缺陷特征。这种情况宁可不加噪声或者只加sigma小于5的轻微噪声。第二小目标检测场景。钢板表面的细小凹坑、针眼缺陷可能只有几个像素高斯噪声会让这些目标的边缘特征被覆盖。针对小目标扩增策略应该优先保证缺陷轮廓锐利而不是用噪声去模拟传感器扰动。第三验证集和测试集绝对不能加噪声。很多人为了统一扩增把全部数据都加了噪声再划分训练集和验证集结果就是验证集指标虚高真实部署到产线上表现明显下滑。正确做法是只对训练样本做离线加噪验证集保持原始图像这样才能衡量模型在真实成像条件下的真实水平。4. 混合制作实战统一标注、转成YOLO格式并完成训练/验证划分的最小流程4.1 建立统一目录结构避免混到最后找不着北混合制作第一步不是写转换脚本而是先把目录结构定下来。很多数据集做了一半才发现DAGM的文件夹层级和铝型材数据的命名规则完全不一样脚本改来改去非常痛苦。我习惯先把所有图片和标注文件拉平到同一个目录体系中mkdir -p steel_defect_dataset/images/{train,val} mkdir -p steel_defect_dataset/labels/{train,val}images目录放所有图像labels目录放所有标注文本train和val提前分开。这个结构可以直接对应YOLO系列的训练约定后面用ultralytics或者detectron2训练时不需要再改目录。图片命名要统一比如原始数据里DAGM是“class1_xx.png”铝型材是“al_20220601_xx.jpg”混在一起后建议统一改成“cls01_0001.jpg”这种带编号的格式方便后面按前缀做分组切分。4.2 把DAGM掩码标注转成检测框代码与参数DAGM的标注是像素级掩码转为YOLO检测框的核心思路是读取掩码图二值化找轮廓对每个轮廓取外接矩形再换算成YOLO需要的归一化中心点坐标和宽高。下面这段脚本是我常用的转换方式import cv2 import numpy as np def mask_to_yolo(mask_path, img_w, img_h, cls_id): DAGM 掩码转换为 YOLO 标签 mask_path: 掩码图像路径缺陷区域为白色 img_w, img_h: 原始图像的宽高用来归一化坐标 cls_id: 该缺陷类别在最终类别表中的编号 mask cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) _, binary cv2.threshold(mask, 128, 255, cv2.THRESH_BINARY) # 用形态学闭运算去掉掩码里的细小孔洞让外接框更贴合 kernel np.ones((3, 3), np.uint8) binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) lines [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) # 面积过滤太小的轮廓多为噪声直接丢弃 if w * h 16: continue cx (x w / 2) / img_w cy (y h / 2) / img_h bw w / img_w bh h / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines这个脚本里有三个参数值得注意。第一个是二值化阈值128DAGM掩码通常是白色缺陷区配黑色背景阈值固定128足够如果掩码是灰度渐变格式需要先看直方图再定阈值。第二个是形态学闭运算的kernel尺寸3x3能去掉小孔洞但不会过度改变缺陷轮廓缺陷面积大时可以适当加大。第三个是面积过滤阈值16像素DAGM原始掩码里经常有孤立噪点不过滤的话会生成大量只有几个像素的无效框训练时严重影响精度。注意如果缺陷是细长划痕cv2.boundingRect会产生一个面积很大的空白框框内真正缺陷占比很低。这种框会让模型学习到错误的正样本区域。处理方式是先统计每个框的缺陷像素占比低于10%就把轮廓拆成多段再分别取框。4.3 铝型材数据清洗与格式转换的常用做法铝型材数据最常见的是VOC格式XML标注偶尔也有COCO格式JSON。以VOC为例转换脚本比掩码转框简单得多因为标注本身就是矩形框只需要把像素坐标改成归一化坐标import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, cls_map): 将 VOC XML 标注转换为 YOLO 格式文本 cls_map: 类别名称到类别 ID 的映射字典 tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in cls_map: continue bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) # 过滤掉明显不合理的框宽高为负或面积为零 if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{cls_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines这段脚本里最重要的参数是cls_map。以钢板表面缺陷为例如果项目最终只关心“划伤”和“脏点”两类那么需要把DAGM里的scratch、铝型材里的擦花都映射到划伤类ID把DAGM里的stain、铝型材里的脏点映射到脏点类ID。我在实际制作时会把映射表单独存成一个字典文件方便随时调整。VOC转YOLO的边界检查也很关键公开数据集中经常出现xmin大于xmax这种脏标注不过滤会导致后续训练直接报坐标错误。4.4 合并与分组划分防止数据泄漏的切分方式两张数据集转换完成后合并成一个文件列表并做训练验证划分。这里最容易犯的错误是直接对整个文件列表随机切分。如果铝型材同一张产线大图被切成了若干小块随机切分很可能让同一块缺陷的不同局部同时出现在训练集和验证集造成数据泄漏验证指标虚高。正确的做法是按图片名称的前缀分组切分。假设所有图片命名成“cls01_0001.jpg”这种格式取前缀“cls01_0001”作为分组单位同一组下的所有小块只能进入训练集或验证集其中一边。下面这段代码是我常用的分组划分脚本import glob import random import os src steel_defect_dataset/images/all val_ratio 0.2 random.seed(42) groups {} for img_path in glob.glob(f{src}/*.jpg) glob.glob(f{src}/*.png): base os.path.basename(img_path) group_key base.rsplit(_, 1)[0] # 按最后一个下划线切分取前缀作为分组ID groups.setdefault(group_key, []).append(img_path) val_keys set(random.sample(list(groups.keys()), int(len(groups) * val_ratio))) for img_path in glob.glob(f{src}/*.jpg) glob.glob(f{src}/*.png): base os.path.basename(img_path) group_key base.rsplit(_, 1)[0] img_dst steel_defect_dataset/images/val if group_key in val_keys else steel_defect_dataset/images/train label_name base.rsplit(., 1)[0] .txt label_src os.path.join(steel_defect_dataset/labels/all, label_name) label_dst os.path.join(steel_defect_dataset/labels/val if group_key in val_keys else steel_defect_dataset/labels/train, label_name) os.rename(img_path, os.path.join(img_dst, base)) if os.path.exists(label_src): os.rename(label_src, label_dst)这段脚本的关键是group_key的切分逻辑。命名规范里“cls01_0001”的后半段代表原始对象ID同一对象的多张裁剪块共用这个前缀所以按前缀分组能保证同源样本不泄漏。如果原始数据命名不规则建议在步骤4.1统一命名时就考虑好这个分组需求。切分比例用0.2作为验证集比例对于几百张的小数据量来说足够支撑一轮可信的评估。划分完成后数据集麻雀虽小五脏俱全目录结构已经可以直接给YOLO系框架用接下来只需要写一个yaml配置指定类别数量和类别名就能开始训练。5. 混合数据集避坑指南5条高频踩坑记录从标注坐标系到噪声强度5.1 通道不一致导致训练直接报错现象训练刚启动数据加载阶段就报错提示图像通道数不匹配或者模型输入层维度与数据维度对不上。原因DAGM原始图像是单通道灰度图读取后shape是(H, W)铝型材数据是RGB三通道shape是(H, W, 3)。两者混合后数据加载器按同一个预处理逻辑处理通道维度不一致必然报错。解决在处理DAGM图像时统一转成三通道。最简单的方式是在读取后调用cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)把灰度图复制到三个通道再保存。不要试图在训练时动态转换那样会增加数据加载时间而且容易漏掉部分文件。5.2 掩码转框后框住大量背景细长缺陷检测效果差现象模型训练完成后对细长划痕的召回率很低检测框要么框得过大、要么漏检严重。原因DAGM掩码里细长缺陷用cv2.boundingRect转外接矩形时会产生一个长宽比极端的框框内包含大量无缺陷背景。YOLO系模型要回归的是框内目标与背景的分布这种大空心框会干扰模型对正样本范围的判断。缺陷是弯曲的一个水平矩形根本无法贴合。解决在掩码转框时对轮廓做分段处理。把长宽比超过5的轮廓沿主方向切成若干段每段单独取外接框同时过滤掉缺陷像素占比过低的框。如果项目允许优先考虑旋转检测像mmrotate这类框架就是专门处理细长目标的场景。5.3 高斯噪声加过头模型把噪声当成了特征现象加了高斯噪声扩充后训练loss下降得很快但验证集上的mAP与扩增前基本没差别甚至略有下降。原因sigma取值过大噪声纹理在视觉上已经覆盖了缺陷本身的边缘结构。模型学到了“图像上有密集噪点”这个特征却没有学到缺陷的长条形状、颜色突变等本质信息。测试集是干净的产线图像没有同样强度的噪声所以检测能力退化。解决做一组sigma消融实验分别用sigma取5、10、15扩增同一份数据训练三个模型比较验证集AP。多数金属表面缺陷场景下sigma取8到12之间是安全区间。记住一个原则扩增的目的是模拟成像差异不是制造肉眼可见的“雪花屏”。5.4 类别合并映射错误相似缺陷互相干扰现象训练完成后发现同一块缺陷会被模型同时识别成两个类别或者两个类别的PR曲线都异常低。原因混合制作时没有建立完整的类别映射表。DAGM的“stain”和铝型材的“脏点”在语义上是同一类但一个映射到了类别0另一个映射到了类别1模型被迫去区分本身没有明确边界的相似样本自然学不好。解决制作一张完整的类别映射表把所有来源数据的类别名与最终类别ID对应起来。如果一个缺陷在DAGM和铝型材数据里都出现但形态有差异宁可先不合并等模型跑通后再根据误检情况决定要不要拆类。类别合并是所有数据制作中最需要人工介入的步骤建议做完后打印一张映射对照表人工复核一遍。5.5 分组切分不当验证集指标虚高上线后崩盘现象离线验证时mAP很高但模型部署到现场相机后性能明显下降甚至不如在训练集上直接测试。原因划分训练集和验证集时用了全随机切分同一缺陷对象的多个裁剪块被同时分到两边验证集里包含了训练集目标的“近似副本”模型其实已经见过这些目标。这类问题在裁剪块类数据里尤其严重是典型的数据泄漏。解决按分组完成切分确保同一个原始对象的所有图像块只出现在训练集或验证集的一侧。第4.4节的按前缀分组脚本就是为这个目的写的。切分后可以抽样验证一下从验证集里随机取10张图去训练集里找是否存在文件名前缀相同的样本如果有就重新切分。6. 验证数据集质量跑一轮YOLO看loss和分布比什么都有说服力6.1 用数据分布检查暴露混合问题混合制作完成后先别急着开训练花十分钟检查数据分布很多问题在这个阶段就能暴露。我一般会跑一个统计脚本打印每个类别的样本数、每张图像的标注框数量分布、框的宽高分布然后直接看图。判断标准很简单各类样本数不要差出10倍以上如果某些类别只有几十张二阶段训练或重采样优先处理。检查分布最直观的方式还是把标签画回图上。用opencv把YOLO格式的标注框画到原始图像上逐个检查是否有框错位、框过大、漏标的情况。这个步骤虽然原始但比任何自动化校验都可靠。特别是DAGM掩码转框后的结果一定要看实际渲染效果不要只看数字。6.2 最小训练验证跑一轮YOLO看什么指标分布检查通过后建议跑一个最小规模的YOLO训练来验证数据集质量而不是直接上大规模训练。用ultralytics的YOLOv8做例子配置文件直接指向刚才制作好的数据集目录yolo detect train \ datasteel_defect_dataset.yaml \ modelyolov8n.pt \ imgsz640 \ batch8 \ epochs30 \ valTrue这里特意选yolov8n这种轻量模型、30个epoch、batch为8目标是快速确认数据链路是否通畅而不是追求精度。观察三件事训练loss有没有稳定下降、验证mAP有没有随epoch缓慢上升、训练结束时PR曲线是否与类别分布相符。如果loss从一开始就抖动剧烈不收敛大概率是标注框有大量错误如果mAP起步很高但涨不动要怀疑验证集存在数据泄漏如果某些类别的AP数字明显低于其他类别说明该类样本量或标注质量有问题。我自己的习惯是在训练结束后把整个流程固化下来目录结构、映射表、扩增脚本、划分代码都放进同一个工程目录这样下次补充新数据时重跑一遍脚本就能得到新的规范化版本。形成这个习惯是因为上一次做类似项目时加了三个月新数据后映射表找不到了所有标签都要重新生成那种返工成本一次就够了。希望你制作数据集的时候也能把这些步骤沉淀下来少走这些弯路希望帮到你。本文还有配套的精品资源点击获取