
简介NEU-DET钢材表面缺陷数据集面向深度学习目标检测与工业视觉研究者覆盖crazing、inclusion、patches、pitted_surface、rolled-in_scale、scratches六大典型缺陷可用于训练缺陷检测模型、验证算法鲁棒性及对比实验。资源共2000个文件以txt标签和jpg图像为主另有少量json与cache缓存文件压缩包整体约23.51MB其中txt为yolov8格式标注jpg为缺陷样本图像并明确划分为训练集1260张、验证集361张、测试集180张可直接接入yolov8等主流框架。目前已有1455人学习下载数据划分与格式统一便于复现实验结果。此数据集包含完整的标注信息和目录结构既适合工业质检场景下的缺陷识别研究也可作为目标检测教学与算法评测的基础数据有助于降低数据准备成本并提升模型迭代效率。1. NEU-DET 不是拿来就能训先说清这 1800 张图能干什么、不能干什么NEU-DET 这组数据最近被问得很多钢材表面缺陷一共六大类训练集 1260 张、验证集 361 张、测试集 180 张标签已经整理成 yolov8 的 txt 格式。听起来拿到就能训但真正落地时最容易出问题的恰恰不是训练命令而是你如何看待这 1800 张图。先说结论1260 张训练图对 yolov8 来说偏少但 NEU-DET 每张图缺陷区域集中、背景干净所以跑出一个能用基线并不难。真正的难点集中在两类相似缺陷的区分上——氧化铁皮压入与夹杂、夹杂与斑块这些在人眼看来都接近模型更容易混。想快速验证检测算法、交工业质检方向作业这套数据值得投入。这篇按我拿到数据集后的处理顺序来写先认全缺陷类型和标签格式再按 7:2:1 组织目录跑通基线最后给一份避坑清单和三个低成本涨点操作。新手照着能跑通熟手可以直接跳到避坑和进阶章节。2. 认全六大类缺陷NEU-DET 的标注格式与坐标转换2.1 六类缺陷长什么样别把类别名和视觉特征记错NEU-DET 每张样本都是 200×200 的 8bit 灰度图一个缺陷框通常占据图像的 1/4 到 1/2并不算典型的小目标场景。六类缺陷分别是 crazing龟裂、inclusion夹杂、patches斑块、pitted_surface麻点、rolled-in_scale氧化铁皮压入、scratches划痕。这个顺序也是社区版本里最常见的类别索引顺序建议从一开始就固定下来否则后面转格式、写 data.yaml、训练三条链路全都会错位。类别名中文通俗叫法典型视觉特征识别难点crazing龟裂表面细密网状裂纹像干裂的泥地裂纹宽窄不定浅灰度下对比度低inclusion夹杂深色点状或短线状嵌入物与麻点/斑块视觉接近patches斑块大片灰暗区域边界模糊灰度渐变缺少锐利边缘pitted_surface麻点密集小凹坑呈深色颗粒感纹理密集容易和夹杂混淆rolled-in_scale氧化铁皮压入不规则块状深色压痕常有方向性形状多变明暗不均scratches划痕细长直线或弧线灰度低于背景与轧制方向纹理容易混拿到数据后我做的第一件事不是训练而是把六类缺陷的图片各抽几十张拼成一张大图人工过一遍。这一步看起来原始但对后续判断混淆矩阵非常有帮助。很多同学直接跳进训练等看到 patches 和 inclusion 被模型混成一片时才回头补课。这里还有一个常见坑不同渠道流出的 NEU-DET 版本里类别命名有差异有的叫 Patches有的叫 Plaque还有的写作 Rolled-in Scale 带空格。建议统一成小写加下划线的风格即pitted_surface、rolled-in_scale这种避免和 yolov8 的 names 解析逻辑打架。2.2 标签到底长什么样VOC/绝对坐标到 YOLO 归一化坐标的换算原始 NEU-DET 标注经常以绝对像素坐标形式存放例如框的 xmin、ymin、xmax、ymax或者直接存在 mat 文件里。yolov8 要求的 txt 格式是每行class x_center y_center width height并且全部归一化到 [0,1]。如果直接把绝对坐标丢给 yolov8坐标范围会超出图像尺寸轻则训练不稳定重则 loss 直接 NaN。下面这段脚本是把 VOC 风格的 XML 标注转成 yolov8 的 txt核心是按图像宽高做归一化import os import xml.etree.ElementTree as ET from glob import glob # 原始VOC标注目录与输出目录 voc_dir annotations out_dir labels os.makedirs(out_dir, exist_okTrue) # 类别名必须与 data.yaml 中的 names 顺序完全一致 CLASS_MAP { crazing: 0, inclusion: 1, patches: 2, pitted_surface: 3, rolled-in_scale: 4, scratches: 5, } for xml_path in glob(os.path.join(voc_dir, *.xml)): tree ET.parse(xml_path) root tree.getroot() width int(root.findtext(size/width)) height int(root.findtext(size/height)) lines [] for obj in root.iter(object): name obj.findtext(name).strip().lower().replace( , _) if name not in CLASS_MAP: print(f[skip] unknown class: {name} in {xml_path}) continue box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) # 绝对坐标 - 归一化中心点坐标 归一化宽高 x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height w (xmax - xmin) / width h (ymax - ymin) / height lines.append(f{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))逻辑说明先通过size/width和size/height拿到图的真实尺寸再把对角坐标换算成中心点加宽高最后统一除以宽高做归一化。CLASS_MAP的顺序必须和你之后 data.yaml 里的 names 完全一致否则类别索引错位训练时模型会把 inclusion 当成 crazing 去学mAP 再高也是自欺欺人。参数说明w和h除以的是各自维度即宽除以 width、高除以 heightx_center是 xmin 和 xmax 的平均值再除以 width不是除以 height。这个细节写错会导致框整体偏移训练过程表现为 val loss 降不下去。如果源标注是 mat 或者其他自定义格式把读取部分替换成对应解析逻辑即可后面换算部分保持不变。2.3 1260 张训练图是否够用先理解小数据集下的性能边界六类缺陷、平均每类只有 210 张训练图这个量级对数据增强比较敏感。yolov8 默认开启的马赛克增强在小数据集上能明显扩充样本多样性但也可能引入错误上下文比如两张不同缺陷图拼接后生成假缺陷。一个务实的预期是用 yolov8n 跑 100 epochmAP50 能做到 0.9 左右mAP50-95 在 0.7 上下再往上每涨一个点投入的调参成本会成倍增加。这个项目里最值得关注的是相似类别的区分度。inclusion 和 pitted_surface 在灰度纹理上高度相似检测模型通常会在两者之间反复横跳。不要指望单纯增加 epoch 能解决更有效的方向是放大分辨率、优化标注一致性以及最后一章会讲到的两阶段细分类。认清这个边界后面所有调参才有针对性。3. 1260训练/361验证/180测试这套划分目录和 data.yaml 应该这么配3.1 什么样的划分是合理的数据泄漏比数据少更致命标题给出的 1260/361/180 大约就是 7:2:1这是检测任务里比较标准的比例。但比例正确不等于划分正确最常见的问题是按文件名的字母顺序直接切前 70%这会导致某些类别的样本被整体分进训练集或验证集。例如 rolled-in_scale 的文件名如果集中在某个字母段按顺序切分后验证集里可能只见得到一两类缺陷。我一般会先做一次按类别分层抽样保证每个 split 中六类的占比都接近 1/6。具体做法是先读每个 txt 标签首列统计每张图属于哪个类然后对每个类分别按 7:2:1 切分最后合并。这一步虽然要写几行代码但能避免训练过程里验证集 mAP 很高、测试集一塌糊涂的典型翻车现象。另一个容易忽略的是图片和标签必须同名同扩展名。NEU-DET 流传版本里偶尔会出现图片是.jpg、标签是.txt但文件名不一致的情况yolov8 在训练时会对缺失标签的图片自动跳过并打印 warning如果没注意数据集索引条数可能实际参与训练的有效图片远小于 1260 张。3.2 按 yolov8 的目录组织数据images 和 labels 必须对齐yolov8 的目录约定是 images 和 labels 两个根目录下各自放 train、val、test 子目录文件一一对应。这里给出一个把已有文件按划分列表移动的脚本划分列表里每行是一个不带后缀的文件名mkdir -p datasets/NEU-DET/images/{train,val,test} mkdir -p datasets/NEU-DET/labels/{train,val,test} for split in train val test; do while read name; do # 两个目录同时移动避免图片或标签丢失 mv images/${name}.jpg datasets/NEU-DET/images/${split}/ mv labels/${name}.txt datasets/NEU-DET/labels/${split}/ done ${split}.txt done逻辑说明脚本按 train.txt、val.txt、test.txt 三个清单移动文件images和labels同步移动保证两边路径一致。注意清单里不要带.jpg后缀否则移动时会找不到文件也不要包含空行空行会让 bash 尝试移动一个不存在的名为空串的文件虽然不会报错中断但会留下隐患。参数说明如果图片扩展名不统一例如混有.png和.jpg建议先统一转换成.jpg再做移动。移动完成后执行一遍ls datasets/NEU-DET/images/train | wc -l和ls datasets/NEU-DET/labels/train | wc -l两边数量必须相等这是目录组织正确的最直接验证。3.3 写对 data.yaml类别索引是训练里最容易错的一环data.yaml 是 yolov8 和数据集之间的唯一桥梁。大部分训练看似正常但指标稀烂的问题根源不是网络结构而是这个文件里的 names 顺序和 txt 文件首列索引对不上。下面是一份可直接改用的配置path: datasets/NEU-DET # 相对路径基于你执行命令的目录 train: images/train val: images/val test: images/test # 训练阶段不会用到 test仅预测时读取 nc: 6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratches逻辑说明path指定数据集根目录train、val都是相对于path的子路径。nc必须和names的长度一致yolov8 在启动时会校验这两处不一致会在终端直接报错。names的下标就是标签 txt 里每行的第一个数字如果转换脚本里把 patches 定义为 0这里就必须让 0 对应 patches。参数说明test字段在训练阶段不会被读取但建议保留因为训练完成后用yolo predict或yolo val指定模型时可以直接复用同一个 yaml。注意 yaml 文件里不能有中文路径yolov8 对非 ASCII 路径的支持一直不太稳定。调试阶段可以先用yolo train dataNEU-DET.yaml modelyolov8n.pt epochs1跑一个 epoch启动日志里会打印类别数量和数据索引数确认无误后再正式训练。4. 跑起第一个 yolov8 基线训练命令、关键参数与日志判断4.1 最小训练命令先跑通再谈优化数据目录和 yaml 就绪后第一个基线用下面的命令跑。选择 yolov8n 是因为 1260 张训练图撑不起大模型的容量n 规模在此类数据上通常已经够用且训练速度快、显存占用低方便快速试错yolo detect train \ dataNEU-DET.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ cacheTrue \ cos_lrTrue \ projectruns/neu_det \ namev8n_imgsz640逻辑说明modelyolov8n.pt会加载 COCO 预训练权重这个小数据集微调场景下比从零训练收敛快得多前 20 个 epoch 就能看到明显下降。imgsz640对 200×200 原图是 3.2 倍放大放大后缺陷纹理更清晰有利于相似类别的区分代价是训练速度略慢。cacheTrue把 1260 张图全部缓存进内存小数据集场景下能省掉大量磁盘 IO训练明显更快。参数说明batch16对 yolov8n 来说足够支撑 BN 层统计稳定如果再小到 4 或 8建议同步降低初始学习率。cos_lrTrue使用余弦退火学习率数据量小时收敛比阶梯下降更平滑能减少最后几十个 epoch 的震荡。project和name指定输出目录方便多个实验并行比较。第一次跑建议把epochs降到 30 做冒烟测试确认日志、权重、验证流程都正常再拉满。4.2 影响结果最明显的三个参数imgsz、batch、epochs 的取舍在 NEU-DET 这个小数据集上imgsz 对结果的影响通常排在第一位。用 200 或 256 会让深层特征图分辨率过低yolov8n 的骨干网络有 5 次下采样200×200 输入到最后一层只有约 6×6 的特征图小缺陷的响应几乎被抹平。升到 640 后特征图变成 20×20缺陷细节保留更充分。如果你的显卡允许可以试 800但对这个数据规模来说边际收益递减。batch 的影响主要体现在 BN 层。batch16 是稳妥起点低于 8 时 BN 统计噪声变大可能出现 val loss 曲线抖动。epochs 方面120 对这个数据量偏多实践中常在 80 到 100 之间收敛超过 120 后模型开始记住训练集噪声val mAP 不再上升。判断标准要看验证集指标不是看训练集 loss。参数小数据集2000张推荐说明imgsz640放大原图保留缺陷细节对相似类别区分帮助最大batch16BN 统计更稳定显存不够时降到 8 并降低学习率epochs80-100太长会过拟合建议配合早停 patience20cos_lrTrue收敛平滑小幅提升 val 稳定性4.3 训练完看什么从 results.csv 和混淆矩阵里读信号训练结束后yolov8 会在输出目录生成results.csv、confusion_matrix.png、F1_curve.png等文件。results.csv是最值得逐行看的东西每一行对应一个 epoch包含 train/box_loss、train/cls_loss、val 指标等列。我习惯先看 val/box_loss 是否还在下降如果 80 epoch 前后已经走平说明模型容量到顶了再加 epoch 只会加重过拟合。confusion_matrix.png用于定位具体是哪两类缺陷在互混。如果右下角的对角线亮块之间出现明显的对称暗块比如 inclusion 和 patches 互相误判就去检查这两类的标注是否有标错的情况。NEU-DET 某些版本里个别样本的框边缘明显偏离缺陷本体这类标注噪声会直接影响混淆矩阵的观感必要时手动重标几张。验证模型在测试集上的表现用下面这条命令yolo detect val \ modelruns/neu_det/v8n_imgsz640/weights/best.pt \ dataNEU-DET.yaml \ splittestsplittest告诉 val 流程去读 yaml 里的 test 路径而不是 val 路径。这里会输出测试集的 precision、recall、mAP50、mAP50-95。记住一个原则以测试集指标为准验证集指标只用来判断模型选择两者差距如果超过 0.05优先怀疑划分的数据泄漏问题。5. 避坑清单NEU-DET 与 yolov8 组合最常见的五个翻车现场5.1 训练 loss 出现 NaN罪魁祸首是坐标越界或空标签现象启动训练后前几个 epoch 正常某个 epoch 的 cls_loss 突然变成 nan之后所有指标全部失效只能中断重来。原因最常见的两类坐标问题。一是某个 txt 里出现归一化后仍大于 1 或小于 0 的坐标值二是标签文件本身是空的anchor 匹配阶段无法为这些样本分配正样本产生异常梯度。解决写一个几十行的检查脚本批量扫描所有标签文件把空文件和越界文件找出来再回到转换阶段修复。修复后重新检查一遍再训练。5.2 训练很顺mAP50 却只有 0.3 左右先查类别索引现象loss 曲线正常收敛训练集和验证集指标都算平滑但 mAP50 一直停留在 0.3 附近完全达不到可用的 0.85 以上。原因这个现象 90% 是 data.yaml 的 names 顺序和 txt 标签首列不一致。例如转换脚本里 patches 排在索引 2但 data.yaml 里索引 2 写的是 inclusion模型把 patches 的框当成 inclusion 去学类别标签完全错位mAP 自然上不去。解决打开验证集任意一个 txt 和对应的原始图对照确认首列数字在 data.yaml 里指向正确的类名。这一步花不了两分钟但能避免后续所有实验全部跑在错误标签上。5.3 验证集 mAP 0.9测试集掉到 0.7划分泄漏了现象best.pt 在 val 上表现不错换到测试集评估后指标明显下降差距超过 0.05甚至更多。原因最常出现在按文件名顺序切分的划分方式。NEU-DET 文件名本身携带类别和样本编号信息顺序切分会把某几类缺陷整体放进训练集验证集只覆盖个别类别模型在验证集上虚高。解决换成分层采样切分按类别分别做 7:2:1。另外如果数据来源包含批量采集的连续图片还要确认同一卷钢上的相邻样本没有同时出现在训练集和验证集里否则样本高度相似同样会造成指标虚高。5.4 patches 和 inclusion 互相混淆放大分辨率解决不了全部现象混淆矩阵显示 patches 和 inclusion 的互相误判率明显高于其他类别对无论怎么调 imgsz 和 epoch这对类别的区分度始终上不去。原因这两类在灰度图上视觉特征确实接近单靠检测头的分类分支很难彻底分开。更隐蔽的原因是原始标注里这些样本的框边界本身就不一致有的框得紧有的框得松导致模型学到的是不一致的上下文。解决先单独抽出这两类的样本做一次人工排查确认标注质量。如果框本身没问题就在检测框输出的基础上加一个两阶段的细分类网络先检测后分类通常能把这对相似类别的准确率再提几个点。具体做法在最后一章展开。5.5 导出 onnx 后指标掉点imgsz 和 NMS 参数没对齐现象训练时 mAP50 正常导出 onnx 加载到推理端后同一个测试集评估结果明显变差掉 3 到 5 个点。原因导出时指定的 imgsz 如果和训练不一致特征图尺度变化直接导致检测头输出偏移。另一个原因是没有显式指定 conf、iou 参数推理时采用默认值与训练时的阈值设置不一致。解决导出命令里带上训练时的 imgsz并在导出后先跑一次yolo predict看单张结果确认框的位置和类别都正常再上批量评估。模型格式转换从来不是无损的nap 到 onnx 再到 engine 每多一层转换最好都过一遍验证集。6. 从基线往上顶分辨率矩阵、伪标签回灌与两阶段细分类6.1 先花半天跑一个分辨率矩阵别迷信默认 640默认 imgsz640 是一个好起点但不一定是 NEU-DET 上的最优解。我通常会在 480、640、800 三档各跑一轮短训练epochs 固定在 60只改 imgsz其余参数保持一致用测试集 mAP50 和 val/box_loss 的变化趋势做对比。放大分辨率对 inclusion 和 pitted_surface 这类纹理缺陷的帮助最直观但超过某个临界点后训练时间翻倍、指标不再上涨这个临界点因数据而异。for imgsz in 480 640 800; do yolo detect train \ dataNEU-DET.yaml modelyolov8n.pt \ epochs60 imgsz$imgsz batch16 \ projectruns/neu_det nameexp_${imgsz} done跑完后把三组实验的测试集指标记在一张表里对比重点看 mAP50 的差异是否超过 2 个百分点。如果 640 到 800 的涨幅不明显就用 640 作为最终配置把时间省下来留给后续优化。6.2 用 180 张测试图做伪标签回灌测试集没有标签但它和训练集同源分布一致是一份现成的数据资产。常见做法是用训练好的 best.pt 对测试集做预测筛选置信度高的结果作为伪标签加入第二次训练。这个操作在工业缺陷场景里很实用因为采集真实标注的成本远高于算力成本。yolo detect predict \ modelruns/neu_det/exp_640/weights/best.pt \ sourcedatasets/NEU-DET/images/test \ conf0.95 iou0.5 save_txtTrue save_confTrue筛选逻辑是只取 confidence 大于 0.95 的检测框生成新的标签文件放到独立的pseudo_labels/test目录绝不能覆盖原测试集目录。第二次训练时把 1260 张真实标签和筛选出的伪标签合并成一个训练集epochs 减少到 40 左右防止模型对伪标签中的噪声过度拟合。伪标签的数量通常只有真实标签的一两成但对小数据集来说已经是可观的增益。6.3 两阶段细分类检测框裁剪加分类网络当混淆矩阵里某对类别长期互相误判时与其反复调检测参数不如在检测头后面加一个分类网络。做法是先用 best.pt 检测出所有框按框裁剪出缺陷区域再训练一个分类模型对裁剪图做细分分类用于最终裁决 patches 和 inclusion 这类难分对。检测负责找位置分类负责认类别两条链路解耦后各自压力都小很多。我习惯把这一步留到最后再做因为大部分场景在分辨率矩阵和伪标签之后已经能达到目标。如果还差几个点再上分类网络也不迟。这类表面缺陷数据集最深的体会是先确认数据没问题再调模型先认准目标和边界再谈涨点。拿到新数据先跑一版 640 基线不调参宁可多花半天把混淆矩阵和标签质量看清楚也不要急着换模型结构。希望帮到你。本文还有配套的精品资源点击获取