钢铁表面缺陷检测图像数据集:1800张带标签图像的工业质检应用指南
简介:这份钢铁表面缺陷检测图像数据集面向从事机器视觉与工业质检研究的开发者、学生及算法工程师,用于训练和评估裂纹、锈蚀、凹坑、划痕等表面缺陷的识别模型,可作为课程实验、毕业设计或缺陷检测算法对比的样本来源。压缩包为zip格式,共约2000个文件,主体是1800张jpg缺陷图像,并配有1400余个xml标注文件与1400余个txt说明文件,另有data、names等配置项,整体约26.81MB,体积轻便易于本地加载。数据集已按训练、验证、测试等划分整理,每张图像均带类别标签,可直接用于监督学习下的模型训练与精度、召回率、F1分数等指标验证,省去自行标注与切分的成本。目前已有2453人学习下载,适合希望快速搭建缺陷检测基线并复现实验的读者参考使用。
1. 钢铁表面缺陷检测图像数据集:1800 张带标签图像到底能撑起什么
产线上跑视觉检测的同行大概都遇到过这种局面:算法框架搭好了,模型结构也调通了,卡住的却是数据——要么样本量不够,要么缺陷类别标注得含糊,要么图像质量跟现场相机对不上。钢铁表面缺陷检测图像数据集(1800 张图像,含标签)这类资源,解决的正是这个冷启动问题。它面向的是热轧、冷轧带钢表面常见的夹杂、划痕、麻点、氧化皮压入等缺陷类型,图像带分类或检测标签,适合做基线模型训练、数据增强策略验证、以及小样本条件下的迁移学习实验。如果你正在做工业质检方向的算法预研,或者要给一条新产线快速搭一个能跑起来的缺陷识别原型,这个量级的数据集是够用的起点;但如果你指望直接拿它上线,那得先看清它的边界在哪。
2. 1800 张钢铁缺陷图像的数据结构与标签体系怎么读
拿到一个图像数据集,第一件事不是急着喂模型,而是把目录结构、标签格式、类别分布摸清楚。1800 张这个量级,说大不大,说小也不小——如果类别均衡、标注质量过关,训练一个中等规模的分类网络绰绰有余;但如果长尾严重,少数类可能只有几十张,直接训练必然翻车。所以这一章先把「怎么读数据」讲透,再谈怎么用。
2.1 目录结构与标签文件的三种常见组织方式
钢铁表面缺陷数据集常见的组织方式有三种:按类别分文件夹(class_name/*.jpg)、图像与标注文件分离(images/+labels/)、以及单文件 CSV/JSON 索引。前两种在分类和检测任务里最常见,第三种多见于从标注平台导出的原始数据。不管你拿到的是哪种,第一步都是写个脚本把结构打印出来,别靠肉眼翻文件夹。
import os from collections import Counter def inspect_dataset(root): """打印数据集目录结构与文件类型分布""" ext_counter = Counter() class_counter = Counter() for dirpath, dirnames, filenames in os.walk(root): for f in filenames: ext = os.path.splitext(f)[1].lower() ext_counter[ext] += 1 # 假设按类别分文件夹,取父目录名作为类别 parent = os.path.basename(dirpath) if ext in ('.jpg', '.jpeg', '.png', '.bmp'): class_counter[parent] += 1 print("文件扩展名分布:", dict(ext_counter)) print("各类别图像数量:", dict(class_counter)) return class_counter # 用法:把 root 换成你的数据集根目录 # inspect_dataset('./steel_defect_dataset')这段脚本做两件事:统计文件扩展名,确认图像格式是否统一(钢铁数据集里常见 jpg 和 bmp 混用,bmp 无损但体积大,jpg 有压缩伪影,混用会影响模型对纹理的敏感度);统计每个类别目录下的图像数量,快速判断类别均衡性。参数上没什么可调的,root指向数据集根目录即可。跑完之后你会得到一张类别分布表,如果发现某个类别少于 100 张,后面训练时就得考虑过采样或类别加权。
2.2 类别分布统计与长尾问题的量化判断
1800 张图像如果分 6 类,平均每类 300 张,听起来还行;但实际工业数据集往往不是均匀分布——正常样本可能占一半,某些罕见缺陷只有几十张。长尾问题不量化就没法决策,所以得算两个指标:最大类与最小类的比值(不平衡比),以及每类样本数是否低于某个阈值。
| 指标 | 计算方式 | 判断标准 | 处理建议 |
|---|---|---|---|
| 不平衡比 | 最大类数量 / 最小类数量 | > 10 视为严重长尾 | 过采样 + 类别加权损失 |
| 最小类样本数 | 直接统计 | < 100 张需警惕 | 数据增强或迁移学习 |
| 类别数 | 标签去重 | 6~10 类较常见 | 过多则考虑合并细类 |
我一般会先跑一遍统计,把不平衡比算出来。如果比值在 3 以内,直接训练问题不大;3 到 10 之间,用加权交叉熵就能压住;超过 10,光靠损失函数不够,得在数据层面做文章——对少数类做旋转、翻转、亮度扰动,或者用切图(crop)把大图里的缺陷区域裁出来扩充样本。钢铁缺陷有个特点:划痕和夹杂在纹理上差异明显,但麻点和氧化皮压入在低分辨率下容易混淆,所以增强时别过度模糊,否则类间边界更糊。
2.3 标签格式转换:从分类目录到检测标注的落地路径
如果标签是按类别分文件夹给的,那它天然适合分类任务;但很多钢铁缺陷检测场景需要的是边界框,这时候就得看数据集是否附带检测标注(如 VOC XML 或 YOLO txt)。如果只有分类标签,你有两条路:一是先做分类基线,验证特征可分性;二是用弱监督或人工补标的方式生成框标注。常见做法是先用分类模型跑一遍,看混淆矩阵,再决定要不要投入标注成本。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, img_w, img_h, class_map): """将 VOC 格式 XML 转为 YOLO txt 格式""" tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in class_map: continue cls_id = class_map[cls_name] bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # YOLO 格式:中心点坐标 + 宽高,均归一化到 0~1 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return lines # class_map 示例:{'scratch': 0, 'inclusion': 1, 'pitted': 2}转换逻辑不复杂,但有两个坑:一是图像尺寸必须和 XML 里记录的一致,如果数据集做过缩放而 XML 没同步更新,归一化坐标会全错;二是类别名到 ID 的映射必须固定,别在不同脚本里各写一套,否则训练时标签错位,模型学出来的东西完全是玄学。参数上,img_w和img_h建议直接从图像文件读取,别硬编码。
3. 用这 1800 张图训练缺陷分类基线:从切分到首轮评估
数据摸清楚了,接下来就是跑一个能出数的基线。1800 张图不算多,别一上来就上大模型,先用轻量网络把流程跑通,拿到混淆矩阵和每类 F1,再决定往哪个方向加投入。这一章按「切分 → 训练 → 评估 → 看错例」的顺序走,每一步都给可复现的配置。
3.1 训练集/验证集/测试集切分的分层策略
图像分类任务最常见的翻车点之一是切分不分层——随机切完之后,验证集里某个类别一张都没有,评估结果完全不可信。钢铁缺陷数据尤其要注意:如果按文件夹随机抽,少数类很容易在验证集里消失。正确做法是按类别分层抽样,保证每个集合里各类别比例接近原始分布。
import os import random import shutil from collections import defaultdict def stratified_split(root, out_root, ratios=(0.7, 0.15, 0.15), seed=42): """按类别分层切分数据集为 train/val/test""" random.seed(seed) class_files = defaultdict(list) for cls in os.listdir(root): cls_dir = os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for f in os.listdir(cls_dir): if f.lower().endswith(('.jpg', '.png', '.bmp')): class_files[cls].append(os.path.join(cls_dir, f)) for cls, files in class_files.items(): random.shuffle(files) n = len(files) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { 'train': files[:n_train], 'val': files[n_train:n_train + n_val], 'test': files[n_train + n_val:] } for split, split_files in splits.items(): dst = os.path.join(out_root, split, cls) os.makedirs(dst, exist_ok=True) for f in split_files: shutil.copy(f, dst) # stratified_split('./steel_defect_dataset', './split_dataset')这段脚本的关键在defaultdict那一步:先按类别把文件归组,再在组内打乱切分,保证每个集合都覆盖所有类别。ratios默认 7:1.5:1.5,1800 张图对应训练集约 1260 张、验证和测试各约 270 张。如果某个类别总数不到 20 张,切完之后测试集可能只剩两三张,这时候建议把比例调成 8:1:1,或者对少数类做留一法评估。seed固定住,保证每次切分结果一致,方便复现。
3.2 轻量卷积网络的首轮训练配置与关键超参
基线模型我一般选 ResNet18 或 MobileNetV3-Small,输入尺寸 224×224,batch size 32,初始学习率 1e-3,优化器用 AdamW,训练 30 个 epoch 起步。钢铁缺陷的纹理特征比较细,输入尺寸别压得太狠,160×160 以下划痕类缺陷容易丢细节。数据增强用随机水平翻转、±15 度旋转、亮度对比度扰动,别用太激进的裁剪,否则缺陷区域可能被裁掉。
import torch import torch.nn as nn from torchvision import models, transforms # 数据增强与归一化 train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 加载预训练 ResNet18,替换分类头 model = models.resnet18(weights='IMAGENET1K_V1') num_classes = 6 # 按实际类别数修改 model.fc = nn.Linear(model.fc.in_features, num_classes) # 类别加权损失,缓解长尾 class_weights = torch.tensor([1.0, 1.5, 2.0, 1.2, 3.0, 1.8]) # 按类别频率反比设置 criterion = nn.CrossEntropyLoss(weight=class_weights) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)weights='IMAGENET1K_V1'表示用 ImageNet 预训练权重初始化,这在 1800 张的小数据集上是关键——从零训练几乎必然过拟合。class_weights那行要按你实际的类别分布来调,频率越低的类权重越高,但别超过 5,否则少数类的梯度会主导更新,把多数类的性能拉垮。学习率 1e-3 配合 AdamW 是比较稳的起点,如果 loss 在前几个 epoch 震荡厉害,降到 5e-4。
3.3 混淆矩阵与每类 F1 的读法:判断数据集能不能用
训练跑完,别只看总体准确率。1800 张图里如果正常样本占一半,模型全猜正常也能有 50% 准确率,这个数没有意义。真正要看的是混淆矩阵和每类 F1。钢铁缺陷里最容易混的是麻点和氧化皮压入——两者在灰度图上都表现为暗斑,区别在边缘锐度和分布密度。如果这两类的混淆矩阵对角线很淡,说明要么标注边界不清,要么分辨率不够,这时候加数据不如先回头查标注一致性。
| 评估指标 | 关注点 | 异常信号 |
|---|---|---|
| 每类召回率 | 少数类是否被漏检 | 某类召回 < 0.5 需查样本量 |
| 每类精确率 | 误检集中在哪两类之间 | 两类互相混淆说明特征重叠 |
| 宏平均 F1 | 类别不均衡下的整体表现 | 与准确率差距大说明长尾严重 |
我一般会把混淆矩阵画出来,盯着非对角线上的大数看。如果发现某两类互相错得厉害,先别急着调模型,把错例图调出来看几张——很多时候是标注本身就有歧义,模型只是忠实地学了噪声。
4. 钢铁缺陷检测的避坑清单:五条血泪经验
这一章单独拎出来讲踩坑,因为钢铁表面缺陷这个场景有几个特有的陷阱,不提前说清楚,后面调参调到头秃也找不到原因。下面五条都是实际做下来反复遇到的,按「现象 → 原因 → 解决」写。
4.1 图像分辨率与现场相机不匹配导致指标虚高
现象:离线训练集上 F1 跑到 0.92,换到现场相机采的图直接掉到 0.6。原因:数据集里的图像可能是经过裁剪或缩放的,分辨率和现场采集的不一致,模型学到的纹理尺度对不上。解决:训练前统一确认图像的实际物理分辨率(每毫米像素数),如果现场相机参数不同,在预处理里加一步重采样,把尺度对齐;另外在验证集里混入少量现场图做域适应评估,别只看公开集的数。
4.2 标签噪声:同一类缺陷在不同图像里标注口径不一致
现象:模型对某一类的预测忽好忽坏,混淆矩阵里该类分散到多个类。原因:标注时对类别边界定义不统一,比如有的标注者把浅划痕归到划痕,有的归到麻点。解决:抽 50 张图人工复核,统计类间歧义比例;如果超过 10%,要么合并细类,要么重新制定标注规范再补标。钢铁缺陷的类别定义最好带图示说明,光靠文字描述不同人理解差异很大。
4.3 过度依赖亮度增强把缺陷特征抹掉
现象:加了 ColorJitter 之后验证集指标反而降了。原因:钢铁缺陷很多是靠灰度对比度区分的,亮度扰动过大把弱缺陷的信号压没了。解决:亮度扰动幅度控制在 ±0.1 以内,对比度扰动可以稍大,但别和亮度同时拉满;更好的做法是用直方图均衡化做预处理,而不是在增强里随机改亮度。
4.4 小样本类别直接过采样导致过拟合
现象:少数类召回上去了,但精确率崩了,误检大量增加。原因:简单复制少数类样本,模型记住了具体图像而不是特征。解决:用过采样时配合强增强(旋转、弹性变形),或者改用 focal loss 让模型聚焦难样本;更稳的做法是先做迁移学习,用预训练特征冻结前几层,只微调分类头。
4.5 训练/测试切分时同一张原图的增强版本跨集泄漏
现象:测试集指标好得不真实,换一批新图就露馅。原因:先做数据增强再切分,同一张原图的不同增强版本同时出现在训练集和测试集。解决:切分必须在增强之前做,按原图 ID 分组切分,保证同一原图的所有变体只落在一个集合里。这个坑在图像分类里特别隐蔽,因为增强后的图看起来不一样,但模型其实见过原图。
5. 从 1800 张到产线可用:小样本下的迁移与验证技巧
1800 张图做原型够用,但离产线还有距离。这一章讲两个进阶方向:怎么用迁移学习把有限样本的效用榨干,以及怎么设计一套能说服自己的验证流程。最后给一个我常用的技巧——用「留出产线批次」做最终检验,而不是只看随机切分的测试集。
5.1 冻结策略与分层学习率的实操配置
小数据集上微调预训练模型,冻结多少层很关键。全冻结只训分类头,收敛快但特征适配不足;全解冻又容易过拟合。我一般分三段:前两个 stage 冻结,第三个 stage 和分类头用较小学习率(1e-4),分类头本身用 1e-3。这样底层通用特征不动,中层纹理特征小幅调整,分类头快速适配新类别。
# 分层学习率:冻结前两层,其余层用小学习率 for name, param in model.named_parameters(): if 'layer1' in name or 'layer2' in name: param.requires_grad = False optimizer = torch.optim.AdamW([ {'params': [p for n, p in model.named_parameters() if 'fc' in n], 'lr': 1e-3}, {'params': [p for n, p in model.named_parameters() if 'fc' not in n and p.requires_grad], 'lr': 1e-4} ], weight_decay=1e-4)这段配置的核心是把分类头和骨干网络分开设学习率。requires_grad = False冻掉前两层,减少可训练参数,1800 张图不至于把整个网络带偏。如果验证集指标在 10 个 epoch 内不涨,可以把 layer3 也解冻试试,但学习率再降一档。
5.2 用留出批次验证代替随机切分:更接近产线的评估方式
随机切分的测试集有个问题:同一批生产条件下拍的图,外观高度相似,随机切分会让训练集和测试集共享大量相似纹理,指标偏乐观。更接近真实的做法是按采集批次留出——比如按时间段或钢卷编号分组,留出整批做测试。这样能暴露模型对光照变化、批次间表面状态差异的敏感度。具体操作是在切分脚本里加一个group_key参数,按组切而不是按文件切。
| 验证方式 | 优点 | 局限 | 适用阶段 |
|---|---|---|---|
| 随机分层切分 | 实现简单,类别均衡 | 指标偏乐观 | 基线快速验证 |
| 按批次留出 | 接近产线分布偏移 | 需要批次元信息 | 上线前评估 |
| 跨域测试 | 检验泛化边界 | 需额外数据源 | 模型选型对比 |
5.3 一个习惯:先看错例再调参
最后说个我自己的习惯。每次指标不达预期,我第一件事不是改学习率或换模型,而是把错例图导出来看二十张。钢铁缺陷的错例往往能直接告诉你问题在哪——如果错例全是低对比度的麻点,那是预处理对比度不够;如果错例集中在图像边缘,那是切图时缺陷被截断;如果错例的标注框明显偏了,那是标注质量问题,调模型纯属白费功夫。这个习惯帮我省下的时间,比任何调参技巧都多。数据量不大的时候,人对错例的直觉比指标更可靠。希望帮到你。
本文还有配套的精品资源,点击获取