简介:这份钢铁表面缺陷检测图像数据集面向从事机器视觉、工业质检与深度学习缺陷识别的研究人员和工程师,提供1800张带标签的钢铁表面图像,覆盖裂纹、锈蚀、凹坑、划痕等典型缺陷类型,可用于训练与验证分类、检测模型,评估精度、召回率与F1分数等指标。压缩包共2000个文件,以1800张jpg图像为主体,配套1400个xml标注文件与1404个txt标签说明,另有data、names等配置文件,整体约26.81MB,目录结构清晰,便于按训练集、验证集与测试集划分使用。目前已有2450人学习下载。数据集标签准确、样本多样,可直接用于监督学习流程,帮助读者快速搭建缺陷检测实验、复现算法对比结果,并针对实际产线场景优化模型,减少人工检测成本与误差。
1. 钢铁表面缺陷检测图像数据集:1800 张带标签图到底能跑出什么结果
产线上刚下来的钢卷,表面一道细如发丝的划痕,人眼在强反光下盯十分钟就花了。很多做工业质检的团队第一反应是买相机、搭光源,却忽略了最卡脖子的一环——带标签的缺陷图像。钢铁表面缺陷检测图像数据集(1800 张图像,含标签)这类资源,解决的正是"模型没见过缺陷长什么样"的问题。它适合三类人:想快速验证检测算法可行性的算法工程师、需要给产线做 PoC 的视觉团队、以及拿它当课程设计或论文基线的高校研究者。1800 张不算多,但胜在类别明确、标注齐全,足够把一条从数据清洗到推理部署的链路完整跑通。下面我按自己踩过的顺序,把这条路讲清楚。
2. 先搞懂钢铁缺陷的六类典型形态与标注格式
拿到数据集别急着喂模型,先花半小时把图像和标签翻一遍。钢铁表面缺陷的形态差异极大,直接决定了你后面选什么检测框架、怎么设 anchor。
2.1 六类高频缺陷的视觉特征
工业场景里最常见的钢铁表面缺陷大致是这几类:裂纹呈细长、走向不规则的暗线,对比度低,容易被氧化皮干扰;夹杂是轧制过程中嵌入的异物,形状块状、边缘模糊;划痕是平行或交叉的亮线,方向性强;麻点是密集的小凹坑,尺度极小;氧化皮压入呈片状暗斑;结疤是凸起的金属瘤,边缘有阴影。这六类的尺度跨度可能从几个像素到几百像素,这就是为什么单尺度检测网络往往翻车。
标注格式上,常见做法是两种:PASCAL VOC 的 XML(存 bbox 的 xmin/ymin/xmax/ymax)和 YOLO 的 txt(存归一化的 cx/cy/w/h)。你拿到的数据集如果是 XML,训练前必须转格式,这一步后面会给脚本。
2.2 标签质量自检:三个必看的统计量
在写任何训练代码前,我一般先跑一段统计脚本,看三件事:每类缺陷的实例数是否均衡、bbox 的宽高比分布、以及有没有越界或零面积的脏框。下面这段代码直接读 VOC 格式的标注目录做体检。
import os import xml.etree.ElementTree as ET from collections import Counter def audit_voc(anno_dir): cls_counter = Counter() bad_boxes = [] ratios = [] for xml_file in os.listdir(anno_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, xml_file)) root = tree.getroot() size = root.find('size') W = int(size.find('width').text) H = int(size.find('height').text) for obj in root.findall('object'): name = obj.find('name').text cls_counter[name] += 1 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) w, h = xmax - xmin, ymax - ymin # 脏框判定:零面积或越界 if w <= 1 or h <= 1 or xmin < 0 or ymin < 0 or xmax > W or ymax > H: bad_boxes.append((xml_file, name)) ratios.append(round(w / h, 2)) print("类别分布:", dict(cls_counter)) print("脏框数量:", len(bad_boxes)) print("宽高比样本:", ratios[:20]) audit_voc('./annotations')逻辑说明:遍历每个 XML,累计类别计数,同时计算每个 bbox 的宽高。参数上,w <= 1 or h <= 1用来抓零面积框,越界判断用图像宽高做边界。跑完你会得到两个关键信号——如果某类实例数只有个位数,训练时几乎必然欠拟合,得靠数据增强补;如果脏框超过 5%,说明标注环节有问题,要么手动修要么直接剔除这些样本,别指望模型自己扛过去。
提示:宽高比分布直接指导 anchor 设计。如果划痕类普遍是 8:1 以上的细长框,而你用默认的 1:1、2:1 anchor,召回率会很难看。
3. 从 VOC 到 YOLO:转换脚本与四个边界坑
格式转换看着简单,实际是翻车高发区。我见过太多人转完格式训练 loss 不降,最后发现是坐标归一化算错了。
3.1 转换脚本:归一化坐标的正确写法
YOLO 格式要求 cx、cy、w、h 全部除以图像宽高归一化到 0~1。注意 cx、cy 是框中心,不是左上角。
import os import xml.etree.ElementTree as ET def voc_to_yolo(anno_dir, out_dir, classes): os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(anno_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, xml_file)) root = tree.getroot() size = root.find('size') W = int(size.find('width').text) H = int(size.find('height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in classes: continue cls_id = classes.index(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) # 中心点 + 宽高,全部归一化 cx = (xmin + xmax) / 2.0 / W cy = (ymin + ymax) / 2.0 / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") out_name = xml_file.replace('.xml', '.txt') with open(os.path.join(out_dir, out_name), 'w') as f: f.write('\n'.join(lines)) classes = ['crack', 'inclusion', 'scratch', 'pitted', 'scale', 'scar'] voc_to_yolo('./annotations', './labels', classes)逻辑说明:classes列表的顺序就是类别 id 的映射,必须和后面训练时的 data.yaml 完全一致,顺序错了模型学到的就是错标签。归一化用.6f保留六位小数,足够精度。参数上唯一要改的是classes内容,按你数据集实际的类别名填。
3.2 四个边界坑:坐标越界、类别错位、空标签、文件名不匹配
坑一,坐标越界。有些标注的 xmax 会等于甚至略大于图像宽度,归一化后 w 可能超过 1,YOLO 训练时会报错或静默裁剪。解决:转换时加cx = min(max(cx, 0), 1)之类的钳制。
坑二,类别错位。VOC 里类别名大小写不一致,比如 "Crack" 和 "crack" 被当成两类。解决:转换前统一转小写并做映射表。
坑三,空标签。有些图确实没有缺陷(负样本),转换后生成空 txt。这本身没错,但如果你用某些框架,空文件会被当成损坏样本跳过。解决:确认框架是否支持背景图,不支持就单独放一个目录。
坑四,文件名不匹配。图像是img_001.jpg,标注是img_001.xml,转换后是img_001.txt,但训练时框架按图像名找标签,一旦有张图没对应标签就会报 "label not found"。解决:转换后写个校验脚本,比对图像目录和标签目录的文件名集合差集。
注意:转换完别直接开训,先随机抽 10 张用可视化脚本把框画回图上,肉眼确认框位置对得上。这一步能省掉你后面几小时的 debug。
4. 训练参数怎么设:小数据集下的防过拟合策略
1800 张图在深度学习里属于小数据集,直接上大模型必然过拟合。核心思路是:小模型 + 强增强 + 早停。
4.1 模型选型与输入尺寸
检测框架上,YOLO 系列对小数据集相对友好,因为它的 anchor 机制和增强策略成熟。模型规模建议选 nano 或 small 级别,参数量控制在几百万,别一上来就上 large。输入尺寸我一般设 640,如果缺陷普遍很小(比如麻点),可以提到 1024,但显存和速度要权衡。下面是一份典型的训练配置。
# data.yaml path: ./dataset train: images/train val: images/val nc: 6 names: ['crack', 'inclusion', 'scratch', 'pitted', 'scale', 'scar']# 训练命令(以常见 YOLO 框架为例) yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ patience=30 \ lr0=0.01 \ lrf=0.01 \ mosaic=1.0 \ mixup=0.1 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4 \ degrees=10.0 \ translate=0.1 \ scale=0.5 \ fliplr=0.5逻辑说明:patience=30是早停,验证集 30 轮不提升就停,防止过拟合。mosaic=1.0开启马赛克增强,把四张图拼一张,对小数据集特别有效。mixup=0.1做图像混合,进一步增加样本多样性。HSV 三个参数是颜色抖动,钢铁图像光照变化大,这个增强很关键。degrees=10做小角度旋转,因为产线相机角度基本固定,旋转太大会引入不真实的样本。
参数怎么调:如果训练 loss 一直降但验证 loss 上升,说明过拟合,加大weight_decay或降低模型规模;如果两个 loss 都不降,说明欠拟合,检查标签格式或提高lr0。
4.2 数据划分与类别均衡
1800 张按 8:1:1 划分训练/验证/测试,即 1440/180/180。划分时要注意同一批次的图不能跨集,否则验证集泄漏,指标虚高。如果某类缺陷样本特别少,用过采样或针对性增强(比如对划痕类多做旋转)。
import os, random, shutil def split_dataset(img_dir, label_dir, out_root, ratios=(0.8, 0.1, 0.1)): imgs = sorted([f for f in os.listdir(img_dir) if f.endswith('.jpg')]) random.seed(42) random.shuffle(imgs) n = len(imgs) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { 'train': imgs[:n_train], 'val': imgs[n_train:n_train + n_val], 'test': imgs[n_train + n_val:] } for split, files in splits.items(): os.makedirs(f"{out_root}/images/{split}", exist_ok=True) os.makedirs(f"{out_root}/labels/{split}", exist_ok=True) for f in files: shutil.copy(os.path.join(img_dir, f), f"{out_root}/images/{split}/{f}") lbl = f.replace('.jpg', '.txt') src_lbl = os.path.join(label_dir, lbl) if os.path.exists(src_lbl): shutil.copy(src_lbl, f"{out_root}/labels/{split}/{lbl}") split_dataset('./images', './labels', './dataset')逻辑说明:random.seed(42)固定随机种子,保证划分可复现。参数ratios控制三集比例。注意这里按图像名找标签,如果标签缺失就跳过,所以前面说的文件名校验很重要。
5. 避坑与排查:训练不收敛、指标虚高、推理漏检
这一章是我踩过的血泪经验,按「现象 → 原因 → 解决」写,你对照着排查。
现象一:训练 loss 从第一轮就不降,一直在高位震荡。原因通常是标签格式错误,比如坐标没归一化、类别 id 从 1 开始(YOLO 要求从 0 开始)、或者 txt 里用了逗号分隔。解决:打开一个标签文件肉眼核对,确认是cls cx cy w h空格分隔、值在 0~1 之间、类别从 0 起。
现象二:验证集 mAP 高得离谱,测试集一塌糊涂。原因是数据泄漏,同一张图或同一批次的图同时出现在训练和验证集。解决:划分前按图像的文件名前缀或采集批次分组,确保同组只进一个集。
现象三:模型在验证集上表现好,实际推理时大量漏检小目标。原因是输入尺寸太小,小缺陷被下采样没了。解决:提高imgsz到 1024,或者在网络里加 P2 小目标检测层。代价是速度下降,要权衡。
现象四:训练中途 loss 突然变 NaN。原因多半是学习率太大或某个 batch 里有脏框(宽高为 0)。解决:降低lr0到 0.001,同时用前面的体检脚本清掉脏框。
现象五:推理时框重叠严重,同一个缺陷出好几个框。原因是 NMS 的 IoU 阈值设太高。解决:把推理时的iou参数从默认 0.7 降到 0.45 左右,具体值在验证集上试。
提示:排查顺序永远是先看数据、再看标签、最后看模型。90% 的"模型不收敛"其实是数据或标签问题。
6. 进阶技巧:用 TTA 和切片推理把 mAP 再抬几个点
基础训练跑通后,想再榨出几个点,有两个技巧性价比最高:测试时增强(TTA)和切片推理。
TTA 的思路是推理时对同一张图做多种变换(水平翻转、多尺度),把结果融合。对钢铁缺陷这种方向性强的场景,水平翻转 TTA 通常能涨 1~2 个点。切片推理(SAHI 那类思路)是把大图切成重叠的小块分别检测再合并,专门治小目标漏检。钢铁图像如果是高分辨率相机拍的,缺陷在原图里可能只占几十像素,整图缩放到 640 后直接消失,切片推理能救回来。
验证方法上,别只看 mAP 一个数。我一般会分类别看 AP,如果某一类特别低,单独把它的验证样本抽出来可视化,看是漏检还是误检。漏检多就查 anchor 和输入尺寸,误检多就查负样本够不够、NMS 阈值合不合理。
一个具体技巧:把验证集里所有预测框和真值框画在同一张图上,用不同颜色区分,肉眼扫一遍。这比看数字直观得多,往往一眼就能发现系统性问题,比如所有框都偏右下几个像素——那可能是标注时的坐标偏移。
最后说个我自己的习惯:每次改完参数或增强策略,先跑 20 个 epoch 看趋势,别一上来就 150 轮。小数据集上,前 20 轮的 loss 曲线基本能告诉你这个方向对不对。省下来的时间够你多试三组配置。希望帮到你。
本文还有配套的精品资源,点击获取