NEU-DET钢材表面缺陷数据集详解:从标注格式到YOLO训练实战 简介钢材表面缺陷数据集NEU-DET是一份面向机器学习与计算机视觉研究者的工业质检数据集聚焦真实轧钢场景下的表面缺陷识别适用于目标检测、图像分类及深度学习教学实验。资源共包含2000个文件以1800张jpg图像及对应xml标注为主另配套1800个txt格式的YOLO标签以及转换脚本xml2yolo.py和yaml配置文件便于直接接入常见检测框架压缩包整体约26.68MB结构紧凑适合快速下载实验。目前已有4952人浏览学习。借助这份数据研究者可直接开展CNN等模型的训练、验证与测试省去数据采集和标注的时间配套脚本和配置文件也能帮助将XML标注一键转为YOLO格式降低环境搭建门槛。无论是入门计算机视觉还是研究钢铁制造中的缺陷检测算法这套资源都能提供完整的实测数据支撑与便捷的上手路径。1. 钢材表面缺陷数据集NEU-DET1800张真实热轧带钢图缺陷检测入门的可靠基准做工业质检的开发者最头疼的往往不是模型选型而是找不到带干净标注的缺陷数据。钢材表面缺陷数据集NEU-DET是被引用最多的工业视觉公开数据集之一1800张200×200的热轧带钢表面图像涵盖龟裂、夹杂、斑块、麻点、氧化铁皮压入、划痕六类典型缺陷每类300张且都带有目标框级别的标注。它主要解决两类问题一是给缺陷检测模型提供可直接训练的监督数据二是作为算法对比的统一benchmark让你在不同网络结构、不同增强策略之间有一个标准的度量尺。适合刚切入工业质检的算法工程师、做检测方向课程设计的学生以及需要快速验证数据增强思路的研究者。这份资源不是单纯的图片包还包括整理好的标注说明文档、格式转换脚本和训练配置模板能帮你把从解压到跑通训练的时间压缩到半天以内。2. 六类缺陷与目录结构训练前必须搞清楚的标注对应关系2.1 六类缺陷的视觉特征与易混点NEU-DET的成功率很大程度上取决于你对六类缺陷形态差异的熟悉程度。很多新人直接扔进训练脚本结果发现模型分数上不去回头查才发现是没理解缺陷本身的长相。类别每类数量典型形态视觉特征crazing300龟裂网状细密裂纹像干燥泥地对比度低inclusion300夹杂颗粒状或块状异物边界不规则patches300斑块片状变色区域边缘模糊灰度不均pitted_surface300麻点密集小凹坑点状分布面积小rolled-in_scale300氧化铁皮压入大块暗色压入物常有层状纹理scratches300划痕线条状方向各异长宽比大这里最容易翻车的组合是 rolled-in_scale 和 inclusion两者都表现为“表面上多了一块东西”仅靠单张图常让人工标注都犹豫。处理方式我放在第4章的避坑清单里。另外注意图像原始分辨率就是200×200缺陷目标往往只占画面的一小块尤其是 pitted_surface 和 scratches这对模型输入分辨率的设计有直接影响后面会专门讲。2.2 目录组织Images 与 Annotations 的命名对应解压后先别急着看图片第一步是梳理目录结构。常见发行版通常包含两个主目录NEU-DET/ ├── Images/ │ ├── crazing_1.jpg │ ├── crazing_2.jpg │ ├── ... │ └── scratches_300.jpg ├── Annotations/ │ ├── crazing_1.xml │ ├── crazing_2.xml │ ├── ... │ └── scratches_300.xml ├── label_map.txt └── README.md命名规则很直观类别名加序号。比如 crazing_1.jpg 对应的标注文件就是同目录下的 crazing_1.xml序号一一对应。label_map.txt 通常记录类别 ID 顺序这个文件是后面的雷区——不同转换版本对类别排序不一样有的按字母序有的按官方原始顺序如果训练脚本的类别列表与标注文件里的 class_id 对不上整个模型就等于在乱学。我一般拿到资源第一件事就是写一个校验脚本抽 5 张图对比图片名和 XML 里的 filename 字段是否一致同时打印 label_map.txt 内容。这个动作能避免后面至少一小时的排查时间。2.3 标注格式VOC XML 与归一化坐标的转换脚本NEU-DET 的标注核心是矩形框官方原始版本常见的是 MATLAB 格式的 .mat 文件而社区大量流传的是转换后的 VOC XML 或 YOLO TXT 格式。这套资源里给出的是 VOC XML 为主、附带了 YOLO TXT 生成脚本的版本。下面是一张 crazing_1.xml 中的关键结构annotation folderNEU-DET/folder filenamecrazing_1.jpg/filename size width200/width height200/height depth3/depth /size object namecrazing/name bndbox xmin42/xmin ymin55/ymin xmax158/xmax ymax172/ymax /bndbox /object /annotation注意这个框的单位是像素xmin/ymin 是左上角xmax/ymax 是右下角不是中心点加宽高。很多人在手写转换时容易在这一步把坐标搞错。如果你打算拿这套数据喂给 YOLO 系列需要把像素坐标转成归一化的中心点格式。下面是转换脚本的核心函数import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_width200, img_height200, class_namesNone): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if class_names is None or name in class_names: class_id class_names.index(name) if class_names else 0 box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 像素坐标转归一化中心点坐标 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height # 默认原图是200x200如果新版本尺寸变化这里要同步改 lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines逻辑说明先解析 XML 根节点遍历所有 object 元素从 bndbox 里取出四个像素坐标值再统一除以图像宽高做归一化。这里的关键点是必须明确 XML 中记录的是绝对像素还是已经归一化的值——NEU-DET 常见发行版中是绝对像素因此转换时要除以 200。参数说明img_width 和 img_height 取原图尺寸不是模型输入尺寸class_names 的排序必须与后续训练配置里的 names 完全一致否则类别错位。转换后最好手工验证一下归一化坐标都在 0 到 1 之间任何超出这个范围的值都说明原始标注或换算逻辑有问题。3. 划分数据集并跑通训练从分层抽样到模型配置3.1 分层划分确保六个类别在每个子集中均匀存在很多教程直接 random.shuffle 然后按比例切这对 NEU-DET 来说不算致命但不够严谨。六个类别每类 300 张如果随机切分个别类别的验证集可能只分到十几张AP 波动会非常大。正确做法是按类别做分层抽样保证每类在训练集、验证集、测试集中的比例一致。import os import random from collections import defaultdict random.seed(42) image_dir Images train_ratio, val_ratio 0.7, 0.2 # 测试集占比为 0.1 # 按类别分组 images os.listdir(image_dir) grouped defaultdict(list) for img in images: cls img.split(_)[0] # 假设文件名是 crazing_1.jpg 这种格式 grouped[cls].append(img) train_files, val_files, test_files [], [], [] for cls, imgs in grouped.items(): random.shuffle(imgs) n_train int(len(imgs) * train_ratio) n_val int(len(imgs) * val_ratio) train_files.extend(imgs[:n_train]) val_files.extend(imgs[n_train:n_train n_val]) test_files.extend(imgs[n_train n_val:]) # 校验每个子集的类别分布 for name, files in [(train, train_files), (val, val_files), (test, test_files)]: dist defaultdict(int) for f in files: dist[f.split(_)[0]] 1 print(name, dict(dist))逻辑说明先把 1800 张图按类别前缀分组然后对每一类单独做随机洗牌和切片最后合并三个子集。这样每个子集内部六类数量基本按 7:2:1 分布。参数说明这里用类别前缀做分组要求文件名必须规范如果遇到文件名不是标准格式的情况建议先检查图片名再跑脚本。print 部分用来快速校验训练集每类约 210 张验证集每类约 60 张测试集每类约 30 张如果某个子集里缺了一个类别说明分组逻辑有 bug直接停下来修。3.2 训练配置以 YOLO 系为例的 data.yaml 写法NEU-DET 图像分辨率低目标尺寸偏小训练配置和常规 COCO 数据有明显区别。以 YOLO 系模型为例data.yaml 需要这样定义path: ./NEU-DET train: train.txt val: val.txt nc: 6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratches注意这里的 names 顺序不是随便写的它必须与第 2.3 节转换脚本里 class_names 的顺序保持一致。如果 label_map.txt 给的顺序不同要以标注文件里的 class_id 为准不要自己想当然重排。训练命令建议写成yolo detect train datadata.yaml modelyolov8n.pt epochs150 imgsz256 batch16 device0参数说明imgsz 我推荐 256 而不是 640。原因有两点一是原始图只有 200×200放大到 640 不会增加任何真实细节反而容易让网络去学习插值产生的伪纹理二是 y 这个尺寸下批量能开更大训练收敛更稳定。如果想进一步提高小目标召回可以把 imgsz 设到 384但 batch 要相应减半。epochs 设 150 是因为数据量不大加上 COCO 预训练权重一般 100 轮左右 loss 就平台期了150 轮足够观察过拟合拐点。device0 是单卡训练如果显存不够把 batch 降到 8。3.3 数据增强哪些变换对缺陷检测真正有效NEU-DET 这类小目标密集场景增强策略不是越多越好有些增强反而会破坏缺陷的物理意义。以下是我在这套数据上调过的增强参数增强项推荐值理由mosaic1.0缺陷之间拼接能提升泛化性但对小目标可能产生截断fliplr0.5水平翻转不改变缺陷物理意义安全fliplr_v0.0垂直翻转对划痕方向性有影响不建议开scale0.3轻微缩放模拟距离变化不要超过 0.5hsv_h0.01钢材表面颜色变化小色相扰动太大会失真translate0.1小幅平移增强鲁棒性其中最容易踩坑的是 mosaic 和垂直翻转。mosaic 会把四张图拼在一起如果目标恰好在拼接缝上会被裁掉一半pitted_surface 这种小目标尤其明显所以开了 mosaic 后最好再开一个 close_mosaic 参数让最后 10 个 epoch 切回普通增强。垂直翻转对 scratches 类影响很大如果业务场景里划痕只会水平分布翻转后模型会学到错误的方向先验。参数值没有绝对的正确答案核心逻辑是缺陷检测任务里的增强要尽量保持缺陷的物理外观一致性。4. 常见问题与避坑NEU-DET 训练效果差的排查清单4.1 坐标换算错误导致训练完全发散现象模型 loss 从一开始就反复震荡mAP 始终在 0 附近预测框位置完全不对。 原因最常见的是把 VOC 的 xmin/ymin/xmax/ymax 像素坐标当成了中心点坐标或者忘记除以图像宽高。另外一个隐蔽场景是 XML 里记录的图像尺寸与真实图片尺寸不一致比如标注时宽高是 200但某次预处理把图 resize 成了 640坐标没有跟着缩放。 解决我每次拿到数据先跑一遍转换脚本并加一行断言检查归一化后的值是否都在 0 到 1 之间。如果出现负值或大于 1 的值逐个回查原始 XML。强烈建议写一个可视化脚本把 GT 框画在图上人工看一眼这一步比任何 loss 曲线都直观。4.2 类别顺序不一致导致全部预测错位现象训练正常、loss 正常但打印预测结果时预测类别总是和真实类别错一个位置比如真实是 scratches模型输出总是 inclusion。 原因标注文件里的 class_id 是按 label_map.txt 的顺序编码的但训练脚本的 names 列表用了另一个顺序比如字母序。两者映射错位模型学到的是“第 4 类”而不是“scratches”这个语义。 解决在训练前定义一个唯一的类别映射表贯穿转换脚本、data.yaml、模型推理三个环节。我的习惯是把映射表单独存成一个 JSON 文件训练脚本和推理脚本都从同一个文件读取禁止在多个地方手写类别列表。4.3 图像读取后出现意外通道数现象cv2.imread 出来的图和网上看到的 NEU-DET 示例不一致shape 变成了 4 通道模型预处理直接报错。 原因部分 BMP 版本的 NEU-DET 图像带有 alpha 通道imread 默认行为可能保留 4 通道。另外 OpenCV 读入的是 BGR 顺序如果模型是用 RGB 预训练权重初始化的颜色通道颠倒会造成轻微的性能损失。 解决统一用 cv2.cvtColor(img, cv2.COLOR_BGRA2RGB) 转成 RGB 三通道再保存为 jpg避免后续每个 pipeline 都要处理通道问题。转换后检查一下 shape[2] 3不要只看读图不报错就继续跑。4.4 输入尺寸过小或过大导致小目标漏检现象模型在验证集上 mAP 尚可但 patches 和 pitted_surface 的 recall 极低肉眼观察检测框偏好大目标。 原因输入分辨率设成 128 或 640 是两个极端。128 会把麻点这类小缺陷直接抹平640 则引入了大量插值噪声而且 batch 减小导致梯度不稳定。 解决优先从原始尺寸附近出发imgsz256 是相对平衡的点。如果小目标还是漏配合 3.3 节说的 mosaic 和 scale 增强或者在验证阶段用多尺度测试把测试分辨率也调到 256 以上。4.5 易混淆类别的区分度不足现象rolled-in_scale 和 inclusion、crazing 和 scratches 两两之间混淆严重混淆矩阵里这两对都有大量交叉项。 原因这属于数据本身的标注边界问题不同标注者对“块状异物”和“层状压入物”的理解不同导致 ground truth 本身就有噪声。 解决先看标注挑出 20 张混淆严重的图自己重新审视标注是否合理。如果业务允许可以把这两类合并成一个“表面异物”类别把六分类降成五分类任务难度立刻下降一截。如果必须保留六分类则考虑增加这两类在损失函数里的权重或采用困难样本挖掘策略。5. 进阶用法用混淆矩阵与可视化验证模型是否真的学会了训练结束只看 mAP 远远不够mAP 是一个被平均掉的数字它会掩盖单个类别的失败。我通常的做法是分三步先看混淆矩阵再随机抽 20 张验证集图做预测可视化最后统计每类的召回率。以下是基于验证集预测结果画混淆矩阵的脚本from sklearn.metrics import confusion_matrix import matplotlib.pyplot as plt import numpy as np # y_true 和 y_pred 是验证集上的真实类别ID和预测类别ID cm confusion_matrix(y_true, y_pred, labelslist(range(6))) plt.figure(figsize(7, 6)) plt.imshow(cm, cmapBlues) plt.colorbar() plt.xticks(list(range(6)), class_names, rotation45) plt.yticks(list(range(6)), class_names) plt.xlabel(Predicted) plt.ylabel(True) plt.show()逻辑说明confusion_matrix 的 labels 参数必须显式传入 0 到 5 的完整列表否则当某些类别在验证集中没有被预测到时矩阵会缺行缺列。可视化部分把六类的名称对齐到坐标轴方便直接定位混淆对。参数说明cmap 用 Blues 只是视觉偏好真正要读的是非对角线上的热点区域哪两个类别靠近模型就可能在哪个边界上挣扎。可视化预测框的代码更直接一些加载训练好的权重对单张图做推理用阈值过滤低置信度框from ultralytics import YOLO model YOLO(best.pt) results model.predict(pitted_surface_10.jpg, conf0.25, imgsz256) # results[0].boxes 包含 xyxy 坐标、置信度和类别ID for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() cls_id int(box.cls[0]) conf float(box.conf[0]) print(f{class_names[cls_id]} {conf:.2f} ({x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}))conf0.25 是过滤低置信度预测的阈值imgsz 必须和训练时的输入分辨率保持一致否则模型会对尺度变化敏感。打印出来的每条记录包含类别名、置信度和像素坐标可以和前面 XML 里的 GT 框直接对照。如果发现 pitted_surface 的预测框明显比 GT 大或小说明学习到的目标尺度有偏回到第 3 章调整输入尺寸和增强参数再训一轮。这套流程走完后我习惯把混淆矩阵图片和每类的召回率表格存进验收报告作为模型是否可发布的依据。从那以后每次我跑 NEU-DET 或类似的小目标缺陷数据集都强制先打印类别映射表和一两张预测可视化图再进训练这个习惯帮我挡掉了至少三次“loss 漂亮但业务跑不通”的返工。这套资源里包含了标注格式说明文档、坐标转换脚本、分层划分脚本和一份完整的训练配置模板拿到后按本文顺序执行可以省去大量整理数据的时间。希望帮到你。本文还有配套的精品资源点击获取