YOLOv5钢材缺陷检测实战:从NEU-DET数据集训练到ONNX部署全流程 简介面向工业视觉质检与YOLOv5初学者的一套完整钢材表面缺陷检测资源包含已训练好的检测模型权重、训练过程可视化结果PR曲线、loss曲线等以及使用LabelImg人工标注好的缺陷数据集。模型可识别多种钢材缺陷类型适合用于复现实验、对比训练效果或作为目标检测课程设计、毕业设计的基线方案。资源共164个文件约106.77MB主要由43个yaml配置参数、35个Python训练/推理脚本、4个pt权重文件以及17张jpg样例图片组成同时附有xml与txt两种格式的标注标签分别存放便于按需取用数据集与检测结果的详细说明可参考作者提供的配套博文。目前已有2275人学习下载适合希望快速上手钢材缺陷检测或需要现成数据集开展迁移学习与模型调优的工程师、学生。1. 为什么要用YOLOv5做钢材缺陷检测从NEU-DET到自训权重的完整链路钢材表面的裂纹、麻点、氧化皮压入在高速带钢产线上稍一疏忽就是整卷降级。传统人工盯屏容易疲劳漏检所以越来越多线边视觉改造把YOLOv5钢材缺陷检测作为首选框架——推理快、迁移成本低、社区资料多。这篇文章以公开数据集NEU-DET为素材把“数据集转YOLO格式、训练出自有缺陷检测权重、验证并导出部署”整条链路走一遍参数和踩坑一处不落。适合刚接触工业视觉的算法工程师也适合想快速评估这条路线值不值得投入的产线负责人。2. NEU-DET数据集与YOLOv5检测原理先看清6类缺陷再谈训练2.1 NEU-DET数据集的组成6类缺陷的视觉特征与标注格式做钢材缺陷检测第一个要回答的问题不是“选什么网络”而是“我手上的数据长什么样”。NEU-DET是一个公开的热轧带钢表面缺陷数据集图像尺寸统一为200×200像素每张图里的缺陷区域都有对应的矩形标注框。它包含6个缺陷类别crazing网状裂纹、inclusion夹杂、patches麻点、pitted_surface氧化铁皮压入形成的凹坑、rolled-in_scale轧制氧化皮、scratches划痕。每个类别各180张总计1800张标注格式是VOC风格的XML文件。这套数据集原本就是为了统一评测表面缺陷检测算法而整理的后来也成了YOLO系列在这个方向上的默认benchmark之一标题里的“数据集”三个字最常见的指代就是它。缺陷类别视觉特征检测难度crazing 裂纹表面分布成网状细线高目标细长且不连续inclusion 夹杂颗粒状深色小块中对比度偏低patches 麻点大片散布的点状区域中高单点尺寸小pitted_surface 凹坑压入形成的坑状区域低边缘相对清晰rolled-in_scale 氧化皮片状暗色覆盖物中scratches 划痕细长线状缺陷高宽度只有几个像素别看图像只有200×2006类缺陷的形态差异非常大。crazing和scratches是细长结构一类像网、一类像线宽度可能只占几个像素patches和inclusion则是小块的、边缘不锐利的区域。这些形态差异直接决定了YOLOv5的anchor要怎么配也决定了训练时数据增强参数的取舍。拿到数据后先做一件事用工具把每张图的标注框可视化一遍挑五六十张图扫一眼看有没有坐标越界、框明显框错位置、漏标的情况。这个检查花不了十分钟却是后面十小时训练最稳的保障。2.2 YOLOv5的检测机制栅格、Anchor与多尺度特征为什么适配表面缺陷YOLOv5在推理时把输入图像下采样成三档特征图分别对应stride 8、16、32负责小、中、大目标。每个特征图被划分成栅格每个栅格预设若干不同宽高比的anchor box网络负责回归“这个anchor离真实目标差多远”同时预测目标属于哪个缺陷类别。和两阶段检测器Faster R-CNN这类相比YOLOv5少了一次独立候选框生成少了这个串行步骤工业线边场景下的速度优势非常明显同样的输入分辨率单位时间内能过的帧数高一个量级。为什么这套机制适合钢材缺陷第一表面缺陷绝大多数是几十像素的小目标YOLOv5的head保留stride 8的低层特征图细节信息比只看高层语义的模型更完整第二缺陷形状高度不规则anchor回归机制允许模型自己学习框的宽高比比固定滑窗灵活第三训练链路成熟数据格式转换、可视化、导出都有现成工具做方案验证周期短。这三点叠加让YOLOv5成为这类质检项目的常见起点。还有一步可以在正式训练前做掉用极短训练观察autoanchor的输出。YOLOv5启动训练时默认会重新统计数据集的anchor日志里会出现类似“AutoAnchor: 6.8 anchors/t, 0.96 Best Possible Recall (BPR)”的信息。如果anchors/t低于4或者BPR低于0.9说明默认anchor跟缺陷数据的长宽比分布差得远后续收敛会非常慢。这也是判断“要不要手工调anchor”的最直接依据。钢材表面两类细长缺陷对anchor的挑剔程度比自然图像里的行人、车辆高得多这一步不能省。3. 数据集转格式与环境搭建喂给YOLOv5前必须做对的准备3.1 环境安装克隆仓库、建独立环境、装依赖YOLOv5的官方仓库从克隆到跑通train.py其实很快但“快”的前提是环境干净提前把依赖隔离好。常见做法是先建独立的conda环境再按仓库里的requirements.txt装依赖避免把机子上其他项目的PyTorch版本顶掉。git clone https://github.com/ultralytics/yolov5.git cd yolov5 conda create -n steel_yolo python3.9 -y conda activate steel_yolo conda install cudatoolkit11.8 -c conda-forge -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt命令的逻辑不复杂前三行把仓库和环境建好第四行为GPU训练准备CUDA运行时第五行装的是与CUDA 11.8配套的PyTorch最后一行把YOLOv5用到的opencv、pandas、tensorboard等依赖一次性装齐。装完后可以用python train.py --data coco128.yaml --epochs 1先跑一个极小任务验证环境比直接上钢材数据集省排查时间。如果你手里的机器显存不大建议第一版就选yolov5n而不是yolov5s后续换模型只改--weights参数数据准备流程完全不用动。3.2 XML转YOLO TXT一个可以直接改的转换脚本YOLOv5不认XML它要求每张图对应一个同名txt文件每行格式是“类别ID 中心点x 中心点y 宽度 高度”后四个值全部归一化到0到1。用归一化而不是像素坐标是因为YOLOv5在训练时会把输入图做letterbox目标框的坐标必须跟着输入尺寸缩放。import os import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches] def convert_xml_to_txt(xml_path, img_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() img Image.open(img_path) w, h img.size # NEU-DET 是 200x200但这里取真实值 lines [] for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{CLASSES.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))脚本里最值得说的就是坐标换算cx和cy是把矩形两个角点取平均得到中心点bw和bh是右下角减左上角得到宽高四个值都要除以图像宽高。一个容易翻车的细节XML里的坐标是像素值txt里要的是比例值忘记除以w/h训练出来的框会溢出到图外。NEU-DET一张图里可能有多个object脚本循环里每读到一个框就追加一行不能只保留第一个。转换脚本跑完之后别急着训练。随机挑几十张图用OpenCV把txt坐标还原成像素框画回原图上肉眼确认一遍框是否贴合缺陷边缘。这一步是血泪经验坐标画上去明显偏移说明不是网络的问题是数据准备阶段出了错这时候排查成本最低。3.3 数据集划分与steel.yaml让train.py认识你的数据集数据要按训练集和验证集分开放我给的比例是8:21800张图里取1440张训练、360张验证。划分时固定随机种子保证每次划分结果一致否则多次实验之间验证集不同mAP对比就失去了意义。import random, shutil from pathlib import Path random.seed(42) # 固定种子保证实验可复现 imgs sorted(Path(NEU-DET/images).glob(*.jpg)) random.shuffle(imgs) val_imgs imgs[:360] # 20% 做验证集 train_imgs imgs[360:] for mode, files in [(train, train_imgs), (val, val_imgs)]: img_out Path(fdatasets/steel/images/{mode}) lbl_out Path(fdatasets/steel/labels/{mode}) img_out.mkdir(parentsTrue, exist_okTrue) lbl_out.mkdir(parentsTrue, exist_okTrue) for img in files: shutil.copy(img, img_out / img.name) xml_path Path(NEU-DET/annotations) / img.with_suffix(.xml).name convert_xml_to_txt(xml_path, img, lbl_out / img.with_suffix(.txt).name)这段脚本只是把图片和转换后的txt按目录结构复制一份不修改原始数据集。之后在yolov5目录下建一个data配置文件告诉训练脚本数据在哪、有几类、类别顺序是什么。# datasets/steel.yaml path: datasets/steel train: images/train val: images/val nc: 6 names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled-in_scale 5: scratches这里有一个非常隐蔽的坑names的顺序必须和转换脚本里CLASSES的顺序一一对应错一位loss照常下降但推理出来的类别全乱了。训练跑到一半再发现这个问题前面的时间基本白费。另外path字段建议写相对路径这样项目整个目录挪到别的机器上不用改配置。4. 训练自己的钢材缺陷权重train.py 参数怎么定才不折腾4.1 预训练权重还是从零训练先看数据规模原始NEU-DET只有1800张图每类180张这个规模对深度学习模型来说偏小。直接用随机初始化训练yolov5s通常会在验证集上反复震荡甚至出现过拟合到背景纹理的现象。常见做法是拿COCO上预训练好的yolov5s.pt做起点做微调让模型继承底层通用特征边缘、纹理、对比度再在钢材缺陷上重新学习类别语义。这个迁移策略在大多数工业质检项目里都能明显缓解前期loss下降慢的问题省下不少训练轮次。那什么时候需要从零训练如果现场缺陷形态和公开数据集差得很远比如表面有大面积周期性纹理COCO预训练特征反而会成为干扰或者目标设备是低算力边缘盒子要换yolov5n小模型时从头训也不见得差。我的判断标准是先让yolov5s微调跑50个epoch如果验证集loss还在往上走再开一版空权重训练做对比。注意这里的“从零训练”是指把--weights参数留空或直接指定为而不是随意用一个别的权重文件这点新手常搞混。4.2 train.py的核心命令与参数batch、img、epochs怎么定第一版训练建议保持仓库默认参数只动下面这几个。命令是这样的python train.py \ --data datasets/steel.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 32 \ --epochs 100 \ --patience 30 \ --cache ram \ --name steel_det逐个说参数--img 640是输入边长原始图虽然只有200×200YOLOv5会先letterbox再缩放640能保留更多细节但显存占用和时间都会涨显存只有6G的机器建议降到480。--batch 32决定一次进多少张图batch越小BatchNorm统计越不稳mAP曲线容易抖动调大batch对收敛有好处但OOM是第一个拦路虎显存不够就降到16。--epochs 100在NEU-DET这种小数据集上够用50轮能看到趋势100轮能拿到相对稳定的结果没必要一上来就跑300。--patience 30表示连续30个epoch验证集没有提升就提前停省时间也能防止过拟合后把好权重覆盖掉。--cache ram把图片预加载进内存小数据集下显著加速训练内存不够就换--cache disk。--name steel_det是给这次训练的输出目录起名方便后面同时对比多组实验。还有几个常被忽略的开关--workers 4控制数据加载线程数Windows下建议设为0或2否则会一直报DataLoader worker崩溃--device 0指定GPU编号--project runs/steel可以自定义输出目录位置。第一版跑的时候其他参数保持默认等确认链路通了再慢慢加戏。4.3 超参数与输出文件hyp文件里调什么、results里看什么YOLOv5把学习率和增强强度放在hyp配置文件里不同版本路径略有不同常见的是data/hyps/hyp.scratch.yaml。对钢材缺陷而言最值得动的是下面这几个lr0: 0.01 # 初始学习率小数据集建议降到 0.005 mosaic: 1.0 # 四图拼接增强 mixup: 0.0 # 混合增强 copy_paste: 0.0 # 实例粘贴增强为什么这样调mosaic把4张图拼在一起训练对通用目标检测效果好但钢材缺陷图本身只有200×200拼图后一条划痕可能横跨两个子图标注完整性受影响所以遇到细长缺陷多的数据集我会把mosaic降到0.5。mixup会生成半透明的混合图而表面缺陷的对比度本来就不高混着混着模型就分不清了我一般直接关掉。copy_paste在新版本里默认也是关的金属表面不适合做实例粘贴贴出来的样本太假。训练跑完后注意力集中在runs/train/steel_det/目录weights/best.pt是验证集mAP最高的权重最终要用的就是它weights/last.pt是最后一轮权重用来断点续训results.png是loss和mAP曲线合图快速看趋势confusion_matrix.png是六类缺陷的混淆矩阵细长缺陷互相串类的线索大多在这里训练日志打印的每类P、R比单个mAP更有用它能告诉你哪一类在拖后腿。判断权重能不能用不能只看loss曲线重点是mAP0.5有没有超过0.7以及混淆矩阵里有没有某两类一直在混淆。5. 钢材缺陷检测训练避坑mAP上不去的四个真实排查路径5.1 现象loss正常下降mAP0.5却一直卡在0.1附近原因大多是数据格式错了尤其转换脚本里坐标归一化写错。YOLO的txt要求“类别ID中心点x中心点y宽度高度”四个坐标全部除以图像宽高如果少除了坐标值就溢出到几倍图宽模型学到的全是畸形框loss却照样能降。解决方法是先把训练集可视化。取一两张训练图像和对应的txt文件用代码把坐标还原成像素框画回原图肉眼对比。几乎所有“loss能降但mAP不动”的案例在这一步都能发现坐标偏移、宽高写成整数而不是比例、类别ID对不上数据yaml之类的问题。画出框没问题再回头查data.yaml的names顺序和转换脚本CLASSES顺序是否一致。顺序错位是最隐蔽的坑因为loss正常、训练也稳定只有推理时类别张冠李戴。5.2 现象六类缺陷里crazing和scratches永远学不好原因要落到anchor上。这两类细长缺陷的标注框非常窄长宽比可能到1:10以上YOLOv5默认anchor里没有这种极端形态训练初期大量正样本匹配不上模型拿到的梯度信号很弱。分类也许勉强能学但回归框质量差precision和recall都上不去。解决分两步。第一步训练时不要关autoanchor它默认会在启动时根据你的数据集重新统计anchor如果日志里BPR还是低于0.9第二步就要手动干预在模型yaml里加一组长条形anchor比如把某个尺度的anchor改成(5, 60)这种比例。另外一个容易被忽略的因素是输入分辨率划痕只有几个像素宽--img 640会把细节放大同时把背景噪声也放大我会先用--img 480跑通找到稳定的参数再去推更高分辨率。5.3 现象误检一堆模型把背景纹理当成缺陷原因集中在“训练和推理的预处理不一致”和“数据分布漂移”这两件事上。YOLOv5训练时letterbox会补灰边推理时如果直接resize把整幅卷面图拉伸到640长宽比变了纹理尺度也变了框的坐标整体偏移误检自然爆发。解决时把推理预处理做成和训练完全一致先计算缩放比例再补灰边最后才是归一化。还有一个更贴近产线的做法对大幅面整卷图做tiling切块每个小块过网络再合并结果不要整图resize。切块尺寸要能整除stride 32区块之间建议留10%重叠否则一条跨切块的划痕会被拦腰切断。这个细节直接影响现场能不能用权重训得再好部署这一步错了也是白搭。5.4 现象训练到一半OOM或者机器直接卡死原因不只是batch大还有--cache ram把整份图片预加载进内存、mosaic增强的缓存占用、以及--img设置过高这几个因素叠加。遇到OOM先别急着换显卡按顺序排查。先把--batch从32降到16再不行把--img从640降到480还不行就关掉--cache ram换--cache disk最后一步才换yolov5n小模型。这里有个度要拿捏batch低于16时BatchNorm统计会变抖mAP曲线会有明显噪声所以小显存机器别一味追小batch可以配合yolov5n和480的分辨率先把链路验证通过等换到有显存的机器再跑正式版本。6. 从权重到产线推理验证与导出ONNX的具体技巧6.1 用best.pt过一遍现场图比验证集mAP值更可信的验证验证集mAP只是实验室指标真正要信的是“模型在没见过的产线实拍图上的表现”。跑推理python detect.py \ --weights runs/train/steel_det/weights/best.pt \ --source sample_images \ --conf-thres 0.25 \ --img 640 \ --save-txt \ --save-conf--conf-thres默认0.25是误检与漏检的折中点现场图上如果出现大量低置信框不要急着降阈值先回头检查预处理是否一致、数据分布是否漂移。--save-txt会把每个框的类别、坐标、置信度输出成txt文件这个文件就是后续接PLC或者上位机的原始数据。6.2 导出ONNX与部署时的预处理一致性要跨平台落地ONNX是绕不开的中间格式python export.py \ --weights runs/train/steel_det/weights/best.pt \ --img 640 \ --include onnx \ --simplify导出的best.onnx可以交给TensorRT、OpenVINO或边缘平台的RKNN工具链做进一步转换。这里要提醒的是onnx固化的是输入张量的shape训练用640导出和部署必须全程保持640不要部署时换成别的尺寸否则现场误检率会高得莫名其妙。我做钢材缺陷检测有个习惯权重文件一定和对应的data.yaml、转换脚本放在同一个目录里备份因为过了两个月你很可能忘了这个best.pt是在什么分辨率、什么anchor设置下训出来的。这个习惯帮我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取