ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

钢材表面缺陷检测实战:YOLOv5结合NEU-DET数据训练与部署全指南

钢材表面缺陷检测实战:YOLOv5结合NEU-DET数据训练与部署全指南 简介面向钢铁质检场景的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.9PyTorch 的版本根据显卡 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_okTrue) 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 lossYOLOv5 的官方仓库自带几组超参数建议文件训练时可以通过--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 曲线要重点观察验证集上的mAP0.5和mAP0.5:0.95两条曲线。mAP0.5反映的是框粗定位是否准mAP0.5:0.95反映的是定位精度钢材缺陷检测中细长裂纹的定位对后者非常敏感。如果mAP0.5不错但mAP0.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_locationcpu, weights_onlyFalse) print(ckpt.keys()) # 常见字段model, ema, epoch, optimizer, train_args print(ckpt[epoch])weights_onlyFalse是 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_locationcpu, weights_onlyFalse) 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_onlyTrue的加载策略而 YOLOv5 的权重文件里保存了自定义类、优化器状态等复杂对象旧版torch.load的默认解包方式被禁了。解决方法有两个。第一升级到 YOLOv5 最新版本官方已经适配了新的加载方式。第二如果你在独立环境中做推理不想升级就在加载时显式加参数ckpt torch.load(best.pt, map_locationcpu, weights_onlyFalse)这条我在第 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-txtconf-thres降到 0.20、iou-thres升到 0.50 是细长缺陷场景的常用组合能让重叠框更好合并漏检减少。但代价是低置信度的误报会变多所以部署时建议配一个后置规则比如框面积小于 20 像素的检测结果直接丢弃。最后一步是数据增强的针对性优化。钢材原始图对比度偏低我试过在训练前用 CLAHE 做对比度增强但有副作用直接把增强图喂进模型会丢失原始纹理信息正确做法是把对比度增强作为一种弱增强随机加到训练管线里让模型学会兼容两种光照条件。我一般把hsv_v扰动调低到 0.02把translate和scale稍微加大因为钢材缺陷的位置随机性很强位移和多尺度增强对泛化帮助最明显。做过的项目里最有价值的一次提升不是换模型而是重新规范化了标注。原先标注团队把细长缺陷的框只框住最明显的三分之一导致模型回归目标不稳定。重新全部框完整后mAP 直接涨了 4 个点。模型调优永远排在建好数据之后这个顺序颠倒了会一直在原地打转。希望这段经验能帮你在钢材缺陷检测这条路上少走几次弯路。本文还有配套的精品资源点击获取
返回列表