☰
钢材表面缺陷检测实战:YOLOv5结合NEU-DET数据训练与部署全指南
2026/10/2 2:42:32 网站建设 项目流程

简介:面向钢铁质检场景的YOLOv5钢材缺陷检测方案,覆盖数据标注、模型训练、评估与推理的关键环节,尤其适合需要在短期内完成工业表面缺陷识别验证的研究人员与工程师。压缩包内共164个文件,大小约106.77MB,核心内容包括yaml/yml配置、Python脚本、pt模型权重、jpg图像以及xml/txt标注文件,sh脚本和md文档用于辅助环境部署与结果解读;文件结构沿用YOLOv5工程组织方式,便于替换数据集后开展迁移训练。资源已包含训练好的检测模型,能区分多种钢材缺陷类型,并附有PR曲线、loss曲线等评估结果,便于分析模型性能;数据集由LabelImg标注,jpg图片对应xml和txt两种标签格式,既可直接训练,也方便二次整理。此外还包含容器化部署配置、训练与推理记录、示例脚本等文件,覆盖环境搭建到推理验证的完整链路。已有2275人学习下载,适合作为工业质检项目或YOLOv5目标检测实战的参考基线,可显著缩短项目前期准备时间。

1. 钢材表面缺陷检测为什么都用 YOLOv5:先看任务再看模型

热轧带钢产线上的裂纹、麻点、氧化铁皮压入、划痕,靠人工目检不仅费眼,而且疲劳后漏检率直线上升。YOLOv5 钢材缺陷检测是目前落地最顺的路线之一:用公开的 NEU-DET 钢带表面缺陷数据集整理成 YOLO 格式,拿 COCO 预训练权重做迁移学习,训练出自己的缺陷检测权重,再部署到工位相机或边缘设备上做实时推理。这篇文章按这条链路讲清楚——数据集怎么处理、权重文件怎么选、训练参数怎么调、哪些坑必须避开。适合准备用 YOLOv5 做工业质检、又不想从零开始踩坑的开发者。

2. 选型依据与数据形态:从传统算法到 YOLOv5 的迁移

2.1 钢材缺陷的“难”不是目标大,而是纹理太像

钢材表面缺陷在图像上并不是孤立的大块异物,它们经常和背景纹理混在一起。比如裂纹(crazing)和划痕(scratches)在灰度图里都是细长条,方向、宽度、灰度都很接近;氧化铁皮压入(rolled-in_scale)和麻点(patches)又都是片状的灰度突变。传统 CV 的做法是用 OpenCV 做阈值分割、边缘检测、形态学滤波,这类方法在单一光照、单一钢种的工况下能跑,一旦换产线、换钢种,阈值就失效,维护成本很高。

深度学习目标检测的好处是特征不是人手工设计的,模型自己会把“什么是缺陷”学出来。但钢材缺陷检测还有两个特性必须注意:第一,缺陷面积占整幅图的比例通常很小,属于典型小目标;第二,类间相似度高,模型容易把裂纹判成划痕、把麻点判成氧化皮。所以选模型时不能只看 mAP 排行榜,要看小目标召回率、类间区分度、以及部署时的算力成本。

2.2 为什么是 YOLOv5 而不是 YOLOv8 或传统视觉

我在实际项目里选 YOLOv5 的理由很现实:生态成熟、资料多、踩坑经验容易搜到。YOLOv8 在精度和训练便利性上确实有提升,但很多工业项目的推理环境还是老旧的 CUDA 版本,YOLOv5 对 PyTorch 版本的要求宽松得多,TensorRT、OpenVINO、瑞芯微 RKNN 这些边缘部署链路的适配也最全。更重要的是,YOLOv5 的官方仓库本身就是一个实验平台,数据增强、超参数、anchor 策略全是可配置的,对于钢材这种“数据量不大但有特点”的任务,可调性比开箱即用更重要。

传统 OpenCV 缺陷检测不是不能用,我一般拿它做两件事:一是产线初期的快速摸底,二是作为深度模型的辅助输入,比如用边缘密度图给灰度图叠一个通道。但完全靠阈值分割做多类别缺陷分类,几乎不可行。至于最近常被提到的 dinomaly 这类无监督异常检测方法,它对“有没有异常”很敏感,但对“是哪种缺陷”的定位和分类能力弱,更适合作为漏检兜底,不适合单独作为质检模型。

2.3 NEU-DET 数据集:钢带缺陷检测的事实标准

NEU-DET 是东北大学发布的热轧带钢表面缺陷数据集,也是行业里最常被引用的钢材缺陷检测基准。它包含 6 类缺陷:crazing(裂纹)、inclusion(夹杂物)、patches(麻点)、pitted_surface(氧化铁皮压入)、rolled-in_scale(轧制氧化皮)、scratches(划痕)。图片是 200×200 的灰度图,每类 300 张,共 1800 张,常见划分是 1440 张训练、360 张验证。

拿到数据集后要注意一个细节:网上流传的版本标注格式不统一。有的是 PASCAL VOC 格式的 XML,有的是已经转好的 YOLO 格式 TXT,还有的图片是 8 位灰度 PNG 而不是三通道 JPG。所以第一步永远是先看标签文件长什么样,而不是直接开训。另外 NEU-DET 的标注框普遍很紧密,一张图里可能出现多个缺陷框重叠,这类样本在转换和统计类别分布时要特别注意。

2.4 环境安装:先跑通最小命令再碰数据

环境配置我按官方仓库的推荐路子来,Python 3.8 或 3.9,PyTorch 的版本根据显卡 CUDA 决定,不需要追最新。拉代码后直接用 requirements.txt 装依赖。

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

装完先做一次环境自检,用官方预训练权重跑一张图片的推理,确认 CUDA、模型前向、后处理全部正常,再进入数据集准备。这一步能省掉后面至少一个小时。常见问题集中在 opencv-python 版本冲突和 Pillow 版本过新导致的读取异常,如果推理时报图像加载相关错误,先去检查这两个包。

3. 把 NEU-DET 整理成 YOLO 格式:转换脚本与训练实操

3.1 目录组织:训练集、验证集分开,别让数据切片串台

我习惯把数据集放在 YOLOv5 仓库外的独立目录,再用软链接指进来,这样训练代码更新时不会被旧数据覆盖。目录结构统一为:

datasets/steel/ ├── images/ │ ├── train/ # 1440 张 │ └── val/ # 360 张 ├── labels/ │ ├── train/ # 对应同名 .txt │ └── val/ ├── steel.yaml └── class_names.txt

划分数据时严格按“同一张图片只能出现在一个集合”的原则。如果一个来源版本里一张图对应多个 XML(部分公开版本存在这种情况),要把这些 XML 合并到同一个 TXT 后再去划分,否则同一张图的不同标签会散落在训练集和验证集,相当于数据泄露,后面 mAP 会虚高到不真实。

3.2 标注转换脚本:XML 转 YOLO 格式的完整实现

YOLOv5 需要的标签格式是每行一个目标:类别id x_center y_center width height,坐标全部归一化到 0~1。下面这个脚本处理 VOC 格式的 XML 标注,输出可直接用于训练的 TXT 文件。

import os import xml.etree.ElementTree as ET # 类别顺序和训练配置中的 names 必须完全一致 CLASSES = ['crazing', 'inclusion', 'patches', 'pitted_surface', 'rolled-in_scale', 'scratches'] def convert_xml_to_yolo(xml_path, txt_path): 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') if name not in CLASSES: print(f"跳过未知类别: {name}, 文件: {xml_path}") continue cls_id = CLASSES.index(name) box = obj.find('bndbox') x1 = float(box.findtext('xmin')) y1 = float(box.findtext('ymin')) x2 = float(box.findtext('xmax')) y2 = float(box.findtext('ymax')) # 防御性检查:过滤无效框,YOLO训练遇到负数框会直接崩溃 if x2 <= x1 or y2 <= y1: continue x_center = (x1 + x2) / 2 / width y_center = (y1 + y2) / 2 / height w = (x2 - x1) / width h = (y2 - y1) / height # 坐标超过1会被裁掉,这里做一次clip兜底 x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) w = max(0.0, min(1.0, w)) h = max(0.0, min(1.0, h)) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 批量转换:假设 XML 在同名目录下 xml_dir = 'datasets/steel/annotations' out_dir = 'datasets/steel/labels' os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue name = os.path.splitext(xml_file)[0] convert_xml_to_yolo( os.path.join(xml_dir, xml_file), os.path.join(out_dir, name + '.txt') )

这段脚本有两个容易忽视的点。findtext('size/width')用的是相对路径查找,比逐层root.find('size').find('width')健壮,不会因为 XML 节点层级多一个少一个就报错。另一个是归一化后的宽高做了 0~1 的 clip,虽然大多数标签不会越界,但部分公开数据集里确实存在手标出界的框,不处理的话训练时会在损失计算阶段产出 NaN,排查起来非常痛苦。转换完成后抽一张图做可视化校验,把框画在原图上确认类别对应关系。

3.3 写数据配置与训练命令:关键参数一次说清

在datasets/steel/steel.yaml里写清数据路径和类别名,注意 YOLOv5 6.x 版本支持path字段作为根目录,路径写相对根目录的子路径即可。

# datasets/steel/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

训练命令我通常这样起:

python train.py \ --data datasets/steel/steel.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 200 \ --cache \ --device 0

--weights yolov5s.pt是拿 COCO 预训练权重做初始化,模型会随机去掉最后一层的分类头,前面的骨干特征保留。--img 640表示将 200×200 的原始图片缩放到宽高 640 的输入尺寸后再训练,YOLOv5 会在保持宽高比的前提下做 letterbox 填充,不会粗暴拉伸变形。--batch 16在 12G 显存上足够,24G 显存可以开到 32。--cache会把图片一次性加载进内存,NEU-DET 只有 1800 张小图,内存开销不大,但对追求最快迭代速度的场景非常值得。第一次训练时建议把--epochs设到 300,因为小数据集收敛慢,200 轮通常够用但不够稳定。

3.4 超参数与训练进度观察:不要只看 total loss

YOLOv5 的官方仓库自带几组超参数建议文件,训练时可以通过--hyp指定。默认使用的是hyp.scratch-low.yaml,对工业缺陷检测来说,我一般会改几个关键值:mosaic在最后 10 个 epoch 关闭,避免小目标被马赛克增强里的裁剪操作截断;hsv_h、hsv_s、hsv_v的扰动幅度要调低,因为钢材表面色彩变化小,过度的颜色抖动会引入虚假特征。

python train.py \ --data datasets/steel/steel.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 300 \ --close_mosaic 10 \ --hyp hyp.scratch-low.yaml \ --cache

训练过程中不要只盯总的 loss 曲线,要重点观察验证集上的mAP@0.5和mAP@0.5:0.95两条曲线。mAP@0.5反映的是框粗定位是否准,mAP@0.5:0.95反映的是定位精度,钢材缺陷检测中细长裂纹的定位对后者非常敏感。如果mAP@0.5不错但mAP@0.5:0.95拉胯,通常是回归损失没收敛,此时优先检查学习率和 anchor 设置,而不是盲目增大模型。

4. 缺陷检测权重怎么选、怎么用:预训练与自训练权重的正确姿势

4.1 COCO 预训练权重只是起点,不是终点

很多人问“心情不好”的问题是:YOLOv5 的官方权重是拿 COCO 数据集训练的,COCO 里没有钢带缺陷,拿它初始化有用吗?有用,而且很有用。深度学习模型的前几层学的是边缘、纹理、角点这类底层视觉特征,COCO 数据集规模大、场景多样,这些底层特征学得非常扎实。钢材缺陷的几何纹理虽然特殊,但它仍然由边缘和纹理组合而成,迁移这部分特征比随机初始化训练收敛快得多,最终精度也更高。

所以--weights参数传yolov5s.pt是最省事的选择。如果你不使用预训练权重,需要把参数改成--weights ''。我测试过在 1800 张 NEU-DET 上,随机初始化大概要多训练 100 个 epoch 才能追平预训练权重训练 100 个 epoch 的效果。对小数据集而言,预训练权重就是你的第一道后悔药。

4.2 best.pt 和 last.pt 的区别:部署和续训别用错了

训练结束后,runs/train/exp/weights/目录下会生成两个权重文件。last.pt是最后一个 epoch 完整保存的文件,包含模型参数、EMA(指数移动平均)参数、优化器状态、学习率调度器状态、epoch 序号以及训练配置的完整快照,它的用途只有一个:断点续训。best.pt是验证集 mAP 最高点的权重,也是部署推理使用的文件。

这里有一个很多新手踩过的坑:直接把last.pt拿去部署。last.pt里带着优化器状态和训练配置,文件体积比纯模型大一倍多,推理时还要额外解析这些无用字段,完全没有必要。部署时应该从best.pt中提取纯推理模型,或者直接用export.py把它导出成 ONNX/TensorRT 格式。

查看权重文件内部结构可以用一个小命令:

import torch ckpt = torch.load('runs/train/exp/weights/best.pt', map_location='cpu', weights_only=False) print(ckpt.keys()) # 常见字段:model, ema, epoch, optimizer, train_args print(ckpt['epoch'])

weights_only=False是 PyTorch 2.6 之后必须显式指定的参数,旧版代码直接load在新环境可能报WeightsUnpickler error。这个细节在当前各种环境兼容问题里出现频率很高,提前知道能省一次深夜 debug。

4.3 用训练好的权重做推理:四个参数各管一件事

推理用官方detect.py即可,基本命令长这样:

python detect.py \ --weights runs/train/exp/weights/best.pt \ --source datasets/steel/images/val \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --save-conf

--conf-thres是置信度阈值,低于这个值的检测结果会被过滤。钢材缺陷检测场景里我默认从 0.25 起调,如果漏检严重就下调到 0.15,但要接受误报率上升。--iou-thres是 NMS 阶段使用的 IoU 阈值,值越大越不容易合并重叠框,对密集小目标要适当调高。--save-txt会输出标签文件,方便做结果分析;--save-conf会把置信度一并写进 TXT,这样后续对比混淆矩阵时能知道每个框的分数分布。

4.4 导出推理权重和部署文件:按目标平台选择格式

如果要在树莓派或嵌入式设备上部署,直接拿 PyTorch 权重跑不现实。我通常先做一步瘦身,再导出对应平台的目标格式。

import torch # 从 best.pt 中提取纯推理模型结构 ckpt = torch.load('runs/train/exp/weights/best.pt', map_location='cpu', weights_only=False) model = ckpt['ema' if 'ema' in ckpt else 'model'].float() model.eval() # 保存为纯推理权重,体积大约缩小一半 torch.save({'model': model}, 'best_infer.pt')

导出 ONNX 用官方export.py就够了,注意设置--opset 12以上以保证算子兼容:

python export.py \ --weights best_infer.pt \ --include onnx \ --opset 12 \ --img 640

导出后的 ONNX 文件可以直接在树莓派上用 ONNX Runtime 跑,也可以转成 RKNN 等边缘平台格式。导出之前固定的输入尺寸要和训练时--img保持一致,否则部署后遇到宽高比差异大的图片会出现大量漏检。

5. 钢材缺陷检测训练避坑:显存、过拟合与漏检的常见问题

5.1 训练 loss 掉到很低但 mAP 不涨:先查数据泄露

现象是训练集的 loss 曲线非常漂亮,box_loss 和 cls_loss 都稳步下降,但验证集的 mAP 始终在低位徘徊,甚至训练集 mAP 接近 1.0、验证集 mAP 只有 0.5 上下。

原因是数据划分阶段出了问题,最常见的是同一张图片的多个标注版本、增强副本被拆到了训练集和验证集。NEU-DET 公开数据里有少量图片在 XML 里出现重复目标,也有整理版本把灰度图转存成 JPG 后原图路径发生变化,这些都会造成不同集合间数据串扰。

解决方法是重做数据划分。划分前对图片文件做 MD5 去重,然后按“图片名维度”去重而不是按标注文件维度去重。同时建议划分后随机挑 10 张验证集图片去训练集里做图片相似度检索,肉眼确认没有重复样本。这一步虽然繁琐,但能排除最大的 mAP 虚高陷阱。

5.2 细长裂纹大量漏检:anchor 与输入尺寸不匹配

现象是训练正常,crater 类、inclusion 类的召回率有 85% 以上,但 crazing 和 scratches 这两类细长形缺陷的召回率只有 50% 左右。

原因是这两类缺陷的宽高比非常极端,长宽比经常在 5:1 甚至 10:1 以上。YOLOv5 默认基于 COCO 数据分布的 anchor 尺寸比例大约是 1:1、1:2、2:1 这类常见形状,对 10:1 的细长条匹配度很低,模型很难回归出精确的细长框。

解决方法是利用 YOLOv5 自带的自动 anchor 学习机制。训练时保持默认开启 AutoAnchor 即可,它会根据你数据集中的真实框统计分布自动调整 anchor。关键是你不能用--noautoanchor把它关掉,也不要手工设置一组吓人的 anchor 尺寸。训练日志里会输出AutoAnchor相关的提示,看到类似 “anchors/target = 4.10, Best Possible Recall = 0.98” 这样的内容就可以放心,说明 anchor 已经适配到数据集了。如果 BPR 低于 0.95,要考虑是否数据里有大量极端长宽比框,必要时可以给细长缺陷单独调整输入尺寸到 640 以上。

5.3 灰度图训练报错或推理过炸:通道数不一致

现象是训练和验证阶段正常,部署后部分图片推理结果异常,或者自己在本地用cv2.imread读图喂进模型时报维度错误。

原因是 NEU-DET 原始图是灰度图,cv2.imread默认读出来只有 H×W 两个维度,而 YOLOv5 的模型输入是三通道的 RGB 张量。部分人在数据准备阶段把灰度图直接存成 JPG 并假想它自带三通道,但读取时实际还是单通道,处理代码也没做扩展,就会在推理端炸掉。

解决方法是统一在读取后转为三通道,不要指望不同环境自动帮你转换:

import cv2 import numpy as np img = cv2.imread('test.jpg') if img.ndim == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # 在推理端对每个输入图片做同样的通道扩展

把这段逻辑封装进数据加载层,而不是在每个脚本里重复写。另一个容易忽略的地方是导出 ONNX 时如果输入图像本来就是灰度图,尽量在部署代码里转换,不要依赖 ONNX 前处理去猜通道数。

5.4 开了--cache显存直接爆掉

现象是训练刚开始几十秒,GPU 报CUDA out of memory,而关掉--cache后同样的 batch size 能跑。

原因是--cache参数在 YOLOv5 中默认将图片以 RAM 形式缓存到内存,这不直接占显存,但会增加数据加载吞吐,配合较大的--workers时,GPU 在等待数据队列中被大量预取数据占住,显存碎片加剧。尤其在数据增强同时开启 mosaic、mixup 的情况下,中间张量翻倍,显存就炸了。

解决方法是先看卡还剩多少显存,把--workers降到 0 或 2,把 batch size 减半。如果还不够,可以改用--cache disk,这种方式只在第一次加载时把图像缓存成磁盘格式,速度和内存占用居中,对 NEU-DET 这种小数据集几乎无感,但非常稳定。

5.5 PyTorch 新版加载旧权重报错

现象是代码迁移到 PyTorch 2.6+ 后,原有的torch.load('best.pt')直接爆出WeightsUnpickler error: Unsupported global或者unsafe legacy global lookup。

原因是 PyTorch 从 2.6 版本开始默认启用weights_only=True的加载策略,而 YOLOv5 的权重文件里保存了自定义类、优化器状态等复杂对象,旧版torch.load的默认解包方式被禁了。

解决方法有两个。第一,升级到 YOLOv5 最新版本,官方已经适配了新的加载方式。第二,如果你在独立环境中做推理不想升级,就在加载时显式加参数:

ckpt = torch.load('best.pt', map_location='cpu', weights_only=False)

这条我在第 4 章已经提过,这里是它最常见的出处。以后任何人的权重文件在新环境加载报错,第一反应就查这个参数,不要先去重装 PyTorch。

6. 把准确率从 85 推到 92 的检查顺序:置信度、NMS 与细节增强的配合

模型跑通只是开始,钢材缺陷检测的调优阶段有一套固定的检查顺序,按这个顺序做能少走弯路。

第一步是分析混淆矩阵,训练结束后在runs/train/exp下会生成 confusion_matrix.png,直接看哪个类互相混淆最严重。NEU-DET 上最常见的混淆对是crazing和scratches,如果这两类互相串,说明输入分辨率不够或者增强参数太激进。第二步是看 PR 曲线,找到召回率拖后腿的类别,再单独对那一类调置信度阈值。第三步才是调后处理。

调置信度时我习惯按类别设置阈值而不是全局一刀切:

python detect.py \ --weights runs/train/exp/weights/best.pt \ --conf-thres 0.20 \ --iou-thres 0.50 \ --save-txt

conf-thres降到 0.20、iou-thres升到 0.50 是细长缺陷场景的常用组合,能让重叠框更好合并,漏检减少。但代价是低置信度的误报会变多,所以部署时建议配一个后置规则,比如框面积小于 20 像素的检测结果直接丢弃。

最后一步是数据增强的针对性优化。钢材原始图对比度偏低,我试过在训练前用 CLAHE 做对比度增强但有副作用,直接把增强图喂进模型会丢失原始纹理信息,正确做法是把对比度增强作为一种弱增强随机加到训练管线里,让模型学会兼容两种光照条件。我一般把hsv_v扰动调低到 0.02,把translate和scale稍微加大,因为钢材缺陷的位置随机性很强,位移和多尺度增强对泛化帮助最明显。

做过的项目里,最有价值的一次提升不是换模型,而是重新规范化了标注。原先标注团队把细长缺陷的框只框住最明显的三分之一,导致模型回归目标不稳定。重新全部框完整后,mAP 直接涨了 4 个点。模型调优永远排在建好数据之后,这个顺序颠倒了会一直在原地打转。希望这段经验能帮你在钢材缺陷检测这条路上少走几次弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询