简介本资源为NEU-DET钢材表面缺陷检测数据集面向从事工业质检、缺陷识别与深度学习目标检测的开发者及研究人员可用于训练与验证钢材表面六类缺陷的检测模型。数据集同时提供Pascal VOC与YOLO两种标注格式包含jpg图片及对应的xml、txt标注文件方便直接接入主流检测框架。压缩包共2000个文件以1799个xml标注文件和201个txt文件为主整体约63.1MB采用7z格式打包。标注类别涵盖crazing、inclusion、patches、pitted_surface、rolled-in_scale、scratches六类总标注框数达4186个各类别框数分布清晰便于分析类别均衡性。目前已有1156人学习下载适合需要快速开展钢材缺陷检测实验、复现基线模型或进行数据增强与模型对比的读者使用。1. NEU-DET 钢材表面缺陷数据集1799 张图、6 类缺陷VOC 与 YOLO 双格式到底怎么用拿到「NEU-DET钢材表面缺陷数据集VOCYOLO格式1799张6类别」这个标题很多人第一反应是直接解压丢进 YOLOv8 训练脚本结果跑完一看 mAP 惨不忍睹回头才发现标注格式没对齐、类别名对不上、图片和标签数量差了几张。NEU-DET 是东北大学发布的钢材表面缺陷检测基准数据集6 类缺陷分别是裂纹crazing、夹杂inclusion、斑块patches、点蚀pitted_surface、氧化铁皮压入rolled-in_scale、划痕scratches共 1799 张灰度图。这个规模在缺陷检测里属于「小而精」——够你跑通全流程、验证改进思路但绝不够你从零训一个工业级模型。它同时提供 VOC 和 YOLO 两种标注格式本质是同一批图的两套标注映射VOC 用 XML 存绝对坐标YOLO 用 txt 存归一化中心点坐标。这篇文章面向的是想用这份数据集做钢材缺陷检测落地验证的工程师从格式差异、目录结构、转换脚本到 YOLOv8 训练参数、置信度门限调整、混淆矩阵排查一步步讲清楚。如果你正在找一份能快速上手、又不会在格式上翻车的缺陷检测数据集NEU-DET 是个合适的起点但前提是你得先把下面这些坑填了。2. VOC 与 YOLO 双格式拆解目录结构、标注差异与选型理由2.1 两种格式的标注逻辑差异VOC 格式的核心是每张图对应一个 XML 文件里面用object标签记录每个缺陷的类别名和边界框的绝对像素坐标xmin, ymin, xmax, ymax。YOLO 格式则是每张图对应一个 txt 文件每行一个缺陷格式为class_id x_center y_center width height全部是相对于图像宽高的归一化值0~1 之间。这两种格式没有优劣之分只有适用场景不同VOC 更适合用 OpenCV、PIL 做可视化验证和传统图像处理流程YOLO 格式则是 Ultralytics 系列训练脚本直接吃的输入。NEU-DET 同时给两套省去了你自己写转换的麻烦但也带来一个隐患——两套标注如果不同步你根本不知道哪套是对的。常见做法是先用 VOC 的 XML 做一次可视化抽检确认框的位置和类别没问题再拿 YOLO 格式去训练。2.2 解压后的目录应该长什么样一份规范的 NEU-DET 双格式数据集解压后通常是这样组织的NEU-DET/ ├── images/ # 1799 张 bmp 或 jpg 灰度图 │ ├── crazing_1.jpg │ ├── inclusion_1.jpg │ └── ... ├── annotations_voc/ # VOC 格式 XML │ ├── crazing_1.xml │ └── ... ├── labels_yolo/ # YOLO 格式 txt │ ├── crazing_1.txt │ └── ... └── classes.txt # 类别名与 id 映射classes.txt里一般是 6 行顺序对应 YOLO 的 class_id 0~5。这个顺序必须和你的data.yaml里names列表完全一致否则训练出来的模型会把裂纹识别成划痕而且 loss 曲线看起来还挺正常这就是最典型的「玄学翻车」。我一般会先跑一段脚本核对三件事图片数量、XML 数量、txt 数量是否都是 1799每个 XML 里的类别名是否都在 classes.txt 里每个 txt 的 class_id 是否在 0~5 范围内。2.3 用 Python 快速校验双格式一致性下面这段脚本做三件事统计文件数量、检查类别名合法性、抽查一个样本的 VOC 与 YOLO 框是否指向同一区域。import os import xml.etree.ElementTree as ET IMG_DIR NEU-DET/images VOC_DIR NEU-DET/annotations_voc YOLO_DIR NEU-DET/labels_yolo CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches] # 1. 数量核对 imgs {os.path.splitext(f)[0] for f in os.listdir(IMG_DIR)} xmls {os.path.splitext(f)[0] for f in os.listdir(VOC_DIR)} txts {os.path.splitext(f)[0] for f in os.listdir(YOLO_DIR)} print(图片:, len(imgs), XML:, len(xmls), TXT:, len(txts)) print(缺失XML:, imgs - xmls) print(缺失TXT:, imgs - txts) # 2. 类别名合法性 bad_cls set() for f in os.listdir(VOC_DIR): tree ET.parse(os.path.join(VOC_DIR, f)) for obj in tree.findall(object): name obj.find(name).text if name not in CLASSES: bad_cls.add(name) print(非法类别名:, bad_cls) # 3. 抽查一个样本的框是否一致VOC绝对坐标 vs YOLO归一化 sample crazing_1 tree ET.parse(os.path.join(VOC_DIR, sample .xml)) size tree.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in tree.findall(object): 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 归一化中心点格式 cx (xmin xmax) / 2 / w cy (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h print(fVOC-YOLO: {cx:.4f} {cy:.4f} {bw:.4f} {bh:.4f}) with open(os.path.join(YOLO_DIR, sample .txt)) as f: print(YOLO原始:, f.read().strip())这段脚本的关键参数说明CLASSES列表必须和classes.txt以及后续data.yaml里的names三处完全一致顺序都不能换。size节点里的宽高是 VOC 标注的基准如果 XML 里缺size节点归一化就会算错这种情况在老数据集里偶尔出现需要手动补或从图片读取。抽查样本时如果 VOC 转出来的归一化值和 YOLO 原始值对不上误差超过 0.01说明两套标注不是同一批生成的必须以其中一套为准重新转换。2.4 选型理由什么时候用 VOC什么时候用 YOLO如果你的流程是「先用传统图像处理做预处理再送进深度学习模型」VOC 格式更方便因为 OpenCV 读 XML 做 ROI 裁剪很直接。如果你直接用 Ultralytics YOLOv8/v5 训练那 YOLO 格式是唯一选择省去转换步骤。我一般会保留 VOC 作为「标注真值」用于可视化抽检YOLO 作为「训练输入」用于迭代模型两边定期用上面的脚本对一次防止某次手动改标注只改了一边。NEU-DET 的 1799 张图里6 类缺陷的分布并不均匀划痕和裂纹样本相对多点蚀和氧化铁皮压入偏少这一点在划分训练集和验证集时要特别注意——不能简单随机切否则验证集里可能某一类只有个位数样本mAP 波动会非常大。3. 从零跑通 YOLOv8 训练环境配置、data.yaml 与关键参数3.1 环境配置Anaconda Ultralytics 的最小依赖热词里「yolo环境配置」「anaconda环境配置要求」是高频问题这里给一个我实测能跑通的最小配置。Python 3.9 或 3.10 都可以PyTorch 选和 CUDA 匹配的版本如果只用 CPU 训练1799 张图跑 100 轮大概要几个小时建议至少有一张 8GB 显存的卡。conda create -n neudet python3.10 -y conda activate neudet # 根据你的 CUDA 版本选下面以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python lxml安装完后用yolo checks确认环境重点看 PyTorch 版本和 CUDA 是否可用。如果显示 CPU only训练会慢到让你怀疑人生。ultralytics包自带 YOLOv8 的预训练权重下载逻辑第一次训练会自动拉yolov8n.pt不需要手动去下。如果你要用 YOLOv5那就得单独 clone 仓库但 YOLOv8 的 API 更简洁本文以 v8 为主。3.2 划分训练集与验证集别用随机切分NEU-DET 只有 1799 张验证集一般留 10%~20%。但直接random.shuffle有个问题同一类缺陷的图片可能高度相似比如同一卷钢材连续拍摄随机切会导致训练集和验证集里出现近乎重复的样本验证 mAP 虚高。我一般按类别分层抽样每类按 8:2 切并且尽量让同一批次的图落在同一侧。下面脚本按类别分层划分并生成 YOLO 需要的目录结构。import os, shutil, random from collections import defaultdict random.seed(42) IMG_DIR NEU-DET/images LBL_DIR NEU-DET/labels_yolo OUT neudet_yolo VAL_RATIO 0.2 # 按文件名前缀归类假设文件名以类别名开头 cls_imgs defaultdict(list) for f in os.listdir(IMG_DIR): if not f.lower().endswith((.jpg, .bmp, .png)): continue name os.path.splitext(f)[0] cls name.rsplit(_, 1)[0] # 去掉末尾编号 cls_imgs[cls].append(name) for split in [train, val]: os.makedirs(f{OUT}/images/{split}, exist_okTrue) os.makedirs(f{OUT}/labels/{split}, exist_okTrue) for cls, names in cls_imgs.items(): random.shuffle(names) n_val max(1, int(len(names) * VAL_RATIO)) val, train names[:n_val], names[n_val:] for split, items in [(train, train), (val, val)]: for n in items: src_img None for ext in [.jpg, .bmp, .png]: p os.path.join(IMG_DIR, n ext) if os.path.exists(p): src_img p break shutil.copy(src_img, f{OUT}/images/{split}/{os.path.basename(src_img)}) shutil.copy(os.path.join(LBL_DIR, n .txt), f{OUT}/labels/{split}/{n}.txt) print(划分完成)参数说明VAL_RATIO设 0.2 是缺陷检测的常见比例样本少于 200 的类别可以降到 0.15 保证训练量。random.seed(42)固定随机种子方便复现。文件名前缀归类依赖命名规范如果 NEU-DET 的文件名不是以类别名开头就得改成读 XML 里的类别来归类。划分完一定要再统计一次每类在 train 和 val 里的数量确认没有某一类在 val 里为 0。3.3 data.yaml 的写法与类别顺序陷阱data.yaml是 YOLOv8 训练的核心配置文件路径和类别名写错是最常见的翻车点。path: /abs/path/to/neudet_yolo train: images/train val: images/val nc: 6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratchespath必须是绝对路径用相对路径在部分版本上会找不到文件。names的顺序必须和classes.txt、标注里的 class_id 完全一致这是硬约束。我见过有人把names写成字典但顺序打乱训练不报错但推理时类别全错位这种坑只能靠可视化抽检发现。nc是类别数NEU-DET 是 6写错会直接报维度不匹配。3.4 启动训练命令行与 Python API 两种方式命令行方式适合快速试跑yolo detect train \ dataneudet_yolo/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/neudet \ nameexp1Python API 方式适合嵌入到自己的流程里from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( dataneudet_yolo/data.yaml, epochs100, imgsz640, batch16, lr00.01, patience20, device0, projectruns/neudet, nameexp1 )关键参数逐个说imgsz640是 YOLOv8 的默认输入尺寸NEU-DET 原图分辨率不一训练时会自动 resize如果缺陷特别小比如点蚀可以提到 800 或 1024但显存占用会明显上升。batch16在 8GB 显存上跑 640 尺寸基本安全显存不够就降到 8。lr00.01是初始学习率小数据集上可以降到 0.005 减少震荡。patience20表示 20 轮验证指标不提升就早停防止过拟合。modelyolov8n.pt是最小的预训练权重如果你追求精度可以换yolov8s.pt或yolov8m.pt但 1799 张图用 n 或 s 就够了大模型反而容易过拟合。3.5 训练过程看什么loss 曲线与 mAP 的合理预期训练启动后runs/neudet/exp1/下会生成results.csv和一堆曲线图。重点看三个指标train/box_loss是否稳定下降、val/box_loss是否跟着降如果 train 降 val 不降就是过拟合、metrics/mAP50是否在 30 轮后趋于稳定。NEU-DET 6 类缺陷在 YOLOv8n 上mAP50 跑到 0.75~0.85 是正常范围如果只有 0.3 左右八成是类别顺序错了或者标注格式没对齐。metrics/mAP50-95会低不少0.5 左右就算不错因为缺陷框的定位精度本身就难做高。如果某一类的 AP 明显低于其他类先去验证集里把这一类的预测结果可视化出来看大概率是样本太少或者标注框画得太松。4. 推理、评估与置信度门限把模型真正用起来4.1 单张图与批量推理的命令训练完拿best.pt做推理yolo detect predict \ modelruns/neudet/exp1/weights/best.pt \ sourceNEU-DET/images/crazing_1.jpg \ conf0.25 \ saveTrue批量推理把source换成目录即可。conf0.25是置信度门限低于这个值的框不输出。热词里「yolo 检测 调整置信度门限」是高频需求这个参数直接决定漏检和误检的平衡。缺陷检测场景下漏检的代价通常比误检高所以门限可以适当调低到 0.15~0.2让更多疑似缺陷进入人工复核。但调太低会出一堆假框需要配合后面的评估来定。4.2 用验证集跑评估并生成混淆矩阵yolo detect val \ modelruns/neudet/exp1/weights/best.pt \ dataneudet_yolo/data.yaml \ conf0.25 \ iou0.6 \ plotsTrueiou0.6是 NMS 的 IoU 阈值缺陷框重叠多的时候可以调到 0.5 减少框合并。plotsTrue会生成混淆矩阵和 PR 曲线。混淆矩阵是排查类别错位的利器如果crazing大量被预测成scratches说明这两类在特征上太像要么加数据要么在损失函数上做文章。热词里「yolo混淆矩阵总合不唯一」说的就是归一化方式不同导致行和列总和不一致Ultralytics 生成的混淆矩阵默认按预测归一化看的时候注意看对角线占比而不是绝对值。4.3 置信度门限的实操调法门限不是拍脑袋定的我一般这样做先用conf0.25跑一遍验证集导出每张图的预测框和置信度然后画一条「置信度-准确率」曲线找准确率开始明显下降的拐点。NEU-DET 上这个拐点通常在 0.2~0.3 之间。如果业务允许人工复核就取拐点偏左的值如果要全自动就取偏右。下面脚本统计不同门限下的预测框数量和平均置信度。from ultralytics import YOLO import numpy as np model YOLO(runs/neudet/exp1/weights/best.pt) results model.predict(neudet_yolo/images/val, conf0.05, saveFalse) confs [] for r in results: if r.boxes is not None: confs.extend(r.boxes.conf.cpu().numpy().tolist()) confs np.array(confs) for t in [0.1, 0.15, 0.2, 0.25, 0.3, 0.4]: print(fconf{t}: {np.sum(confs t)} 个框)conf0.05是为了拿到尽可能多的低置信度框用于分析实际部署时再按统计结果定门限。如果 0.1 到 0.2 之间框数量骤降说明模型对大部分缺陷的置信度集中在 0.2 以上门限设 0.2 比较稳。5. 避坑与排查NEU-DET 训练中最容易翻车的 5 个点5.1 现象训练 loss 正常下降但 mAP 始终为 0原因data.yaml里的names顺序和标注里的 class_id 不一致或者nc写成了别的数字。模型在学但学到的类别映射和评估时的映射对不上导致所有预测都被判为错误。解决用第 2.3 节的脚本核对classes.txt、data.yaml、标注文件三处的类别顺序确保完全一致。改完重新训练不要接着旧权重继续。5.2 现象验证集 mAP 很高但拿新图推理全是假框原因训练集和验证集划分时没有按类别分层验证集里混入了和训练集近乎重复的样本导致评估虚高。NEU-DET 里同一类缺陷的图片可能来自同一批拍摄相似度极高。解决按第 3.2 节的分层抽样重新划分并且人工检查验证集里是否有和训练集视觉上几乎一样的图。如果有把它们移到训练集或直接剔除。5.3 现象某一类缺陷的 AP 始终为 0原因这一类在验证集里样本数为 0或者标注框的宽高算出来是 0XML 里 xmin 等于 xmax。解决先统计每类在 train 和 val 里的数量确保 val 里每类至少有几个样本。再检查这一类所有 XML 的 bbox把宽或高为 0 的标注修掉或剔除。NEU-DET 里pitted_surface和rolled-in_scale样本偏少尤其要注意。5.4 现象训练到一半显存溢出CUDA out of memory原因imgsz或batch设太大或者workers太多导致数据加载占用显存。解决先把batch减半再把imgsz从 640 降到 512 试。如果还不行把workers设为 0 用主进程加载数据慢但稳。8GB 显存跑 640 尺寸batch16是上限batch8更保险。5.5 现象推理时框的位置整体偏移原因VOC 转 YOLO 时归一化用错了宽高基准比如用了 XML 里的size但实际图片被 resize 过或者 YOLO 标注本身就是错的。解决用第 2.3 节的抽查脚本随机抽 10 张图把 VOC 转出来的归一化值和 YOLO 原始值逐行对比误差超过 0.01 就说明两套标注不同步。以 VOC 为准重新生成 YOLO 标注或者反过来但只能选一套作为真值。6. 小数据集上的进阶技巧从 1799 张里榨出更多信息NEU-DET 只有 1799 张想进一步提升模型表现光调参不够得在数据和训练策略上做文章。我常用的三个手段按性价比排序第一是数据增强YOLOv8 默认开了 mosaic、HSV 抖动和随机翻转但缺陷检测里灰度图的 HSV 抖动意义不大可以关掉hsv_h/s/v把mosaic保留它能把 4 张图拼成一张等效增加样本多样性再加degrees10做小角度旋转因为钢材缺陷的方向不应该影响判定。第二是迁移学习的层次选择yolov8n.pt是在 COCO 上预训练的COCO 里没有钢材缺陷但底层边缘和纹理特征仍然有用所以冻结 backbone 前几轮再解冻比从头训收敛快很多。第三是类别不平衡处理pitted_surface和rolled-in_scale样本少可以在data.yaml同级目录放一个hyp.yaml把cls损失权重调高或者用copy_paste增强把少样本类复制粘贴到其他图上。验证改进是否有效不能只看最终 mAP我习惯固定一个baseline实验比如yolov8n 默认增强 100 epochs每次只改一个变量跑 3 个不同随机种子取平均避免单次结果的偶然性。下面这个表格是我在 NEU-DET 上跑过的几组对照供参考数值是 3 次平均硬件不同会有浮动实验模型增强策略mAP50mAP50-95baselineyolov8n默认0.790.48旋转yolov8ndegrees100.810.50关HSVyolov8nhsv00.800.49copy_pasteyolov8n少样本类增强0.830.52换模型yolov8s默认0.840.54从表里能看出换更大模型带来的提升和做数据增强差不多但推理成本高不少。如果部署在边缘设备上我倾向于用yolov8n加增强而不是硬上yolov8s。另外copy_paste对少样本类的提升在混淆矩阵上体现得很明显——pitted_surface的召回率能涨 5~8 个百分点但要注意粘贴的位置不能和已有缺陷重叠否则会引入错误标注。最后说一个我踩过的坑有次为了冲 mAP把epochs设到 300结果 150 轮后验证 loss 开始上升mAP 反而掉了早停没开白跑了一百多轮。从那以后我固定patience20并且每 10 轮存一次 checkpoint宁可多存几个权重也不赌最后一轮是最好的。NEU-DET 这个规模的数据集100 轮左右基本就到头了再训就是过拟合。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?