简介这套YOLOv5与钢材表面缺陷检测相结合的实战资源面向目标检测入门者、工业视觉工程师及竞赛选手解决钢材表面小目标缺陷定位与分类难题。包内基于YOLOv5网络结构CSPNet、SPP、FPN/PANet等提供完整数据集与训练代码共2000个文件以1971张JPG图像、1954个TXT标签、1800个XML标注文件为主体附Python训练脚本、YAML配置、PyTorch权重及onnx模型整包约501MB解压即可进入训练流程。已有2503人学习下载被用于钢铁产线质检等场景的算法验证。资源价值在于带来可直接运行的工程化方案既包含标注好的钢材缺陷数据集裂纹、凹坑、氧化皮、划痕等也给出多尺度训练、数据增强和实时推理的完整实现便于快速迁移到其他表面检测项目或作为毕设、比赛的技术底座。1. 钢铁表面缺陷检测项目从数据集到可落地的质检方案一条钢材产线每分钟走几十米表面划痕、麻点、氧化皮混在金属纹理里人工肉眼盯久了很容易漏检。这个项目把某高校公开的钢材表面缺陷图像集和 YOLOv5 组合起来做成了一个开箱即用的目标检测方案你给它一张钢材表面的显微图它能框出缺陷类别和位置裂纹、夹杂、划痕、麻点这几类都能认。对刚入坑目标检测的开发者来说它最大的价值不是创新而是完整——从数据集整理、标注格式转换、模型训练到推理部署每一步都有现成脚本比你自己从零凑一套省很多时间。本文会按数据准备、训练、推理、避坑、二次开发五条线把这套资源拆开讲清楚不绕概念直接落到能跑通、能改动的代码层面。2. 项目定位与技术选型为什么是 YOLOv5 处理这批钢材图像2.1 这套资源的组成结构这个项目拿到手后解压出来的结构基本是围绕 数据 模型 工具脚本 组织的。数据侧是某高校采集的钢材表面缺陷图像集每张图是 200x200 像素的灰度显微图涵盖六类常见轧制缺陷裂纹crazing、夹杂inclusion、麻点patches、氧化皮pitted_surface、轧制氧化皮rolled-in_scale、划痕scratches。图像经过人工标注成 VOC 格式的 XML 文件目标是用边界框把缺陷位置框出来。模型侧是 YOLOv5 官方仓库的定制配置入口脚本、训练参数、预训练权重都按照这批数据的类别数调整过数据加载路径也已经改成本地目录结构。我不推荐你把整个压缩包原样扔进训练脚本就跑——先花十分钟把目录结构和标签格式摸清楚后面能少踩一半的坑。拿最常见的部署方式来说项目本身准备了一个预处理脚本负责把 VOC 的 XML 标注转成 YOLOv5 需要的 TXT 格式转换逻辑里还包含按类别统计数量的功能。这个脚本我会在 3.2 节贴出来逐行讲因为它直接影响 mAP 曲线的可信度Class 数量漏写一个训练器压根起不来。2.2 技术栈组件灰度图、六类缺陷与 YOLOv5 的适配点先澄清一个容易踩的坑钢材表面缺陷数据集是单通道灰度图像不是三通道 RGB。YOLOv5 的默认输入是 RGB 三通道所以多数教程里的模型配置不能直接套。常见做法是在数据加载器里做灰度转三通道复制也就是把灰度值复制到三个通道上保留纹理结构的同时让网络结构不需要改动。这个项目在数据集加载部分做了同样的处理你在dataset.py或者配置文件的channels字段会看到channels: 3而实际读入图像时会先走一次灰度转换。这个细节后面训练时如果不注意会出现训练集 loss 正常但验证集 mAP 极低的现象特征分布根本没对齐。另一个适配点是类别数。默认 YOLOv5 的 COCO 预训练模型是 80 类这批钢材缺陷只有 6 类。项目的模型配置文件里nc: 6就是改这个数字同时最后一层卷积的输出通道会由代码自动推导不需要你手动改卷积参数。如果你拿到的是老版本代码可能会看到一个手工计算的fc层输出维度那个是早期版本的遗留写法配合新版本引擎使用时反而会报错这种版本差异问题我放在第 5 章的避坑清单里单独说。2.3 数据集的目录规范和标注格式数据部分完整解压后典型布局如下NEU-DET/ ├── IMAGES/ │ ├── 1.jpg │ ├── 2.jpg │ └── ... ├── ANNOTATIONS/ │ ├── 1.xml │ ├── 2.xml │ └── ... └── labels_6class/ ├── 1.txt └── ...IMAGES放原始图ANNOTATIONS放 VOC 格式 XML另有一个已经转换好的labels_6class目录放 YOLO 训练格式的 TXT。每个 TXT 文件对应一张图每行代表一个目标框格式为class x_center y_center width height四个坐标值都是相对于图片宽高的比例取值在 0 到 1 之间。这个归一化坐标是 YOLO 系列的约定转换脚本的核心工作就是读 XML 里的xmin ymin xmax ymax绝对坐标除以图片宽高得到相对坐标再把类别名映射成数字 id。这套格式早些年还劝退过不少人现在数据集作者已经直接附上了转换结果省了中间步骤。注意labels_6class的名字暗示类别数已经按六类处理好了。如果你打算新增一类缺陷比如把重叠的多个缺陷合并成一类需要同步改三处数据 TXT 里的类别编号、data.yaml的names列表、模型配置里的nc。少改任何一处训练时要么报错要么类别错乱这类问题定位起来极容易翻车后文专门有排查小节。3. 数据准备与预处理链路从 XML 到 YOLO 格式的干净转换3.1 为什么要做格式转换YOLOv5 训练时读取的是 TXT 标注不是 XML。每个 TXT 长这样0 0.512500 0.481250 0.225000 0.125000 2 0.312500 0.562500 0.200000 0.125000第一列是类别 id后面四列是归一化边界框。而数据集标注者给的原始文件是 XML里面是九个字段包括文件名、图像尺寸、每个目标的边界框绝对坐标和类别名。不转换的话要么训练直接报路径找不到标注要么读进来一堆 0 值导致 loss 不下降。转换脚本要做的不只是格式翻译还要解决图片尺寸不一致的问题——虽然这批图都是 200x200但你二次开发换数据源时会遇到各种尺寸按像素绝对坐标算出来的归一化值会越界。3.2 核心转换脚本逐行拆解项目里通常存在一个xml2txt.py核心逻辑如下import os import xml.etree.ElementTree as ET # 类别名映射顺序对应 data.yaml 中的 names CLASS_MAPPING { crazing: 0, inclusion: 1, patches: 2, pitted_surface: 3, rolled-in_scale: 4, scratches: 5, } def convert_xml_to_yolo(xml_path, txt_path, img_width200, img_height200): tree ET.parse(xml_path) root tree.getroot() # 优先从 XML 里读真实尺寸避免硬编码失效 size root.find(size) img_width int(size.find(width).text) img_height int(size.find(height).text) with open(txt_path, w, encodingutf-8) as f: for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAPPING: print(f[skip] unknown class: {name}) continue cls_id CLASS_MAPPING[name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n)逐行看关键点首先我建议优先从 XML 的size节点读取图像宽高而不是把 200x200 写死在函数参数里。原因很简单你后续换数据集时图像尺寸一变化写死的参数会让所有标注框全部偏移这种 bug 肉眼极难发现。其次CLASS_MAPPING字典的顺序必须和data.yaml里names的顺序保持一致不然类别语义错位比如把划痕当成裂纹训练时模型不会报错但检测结果完全不可信。转换后建议用脚本抽样打印每个类别的框数量确认没有异常。喂给训练器之前先跑一次快速验证比事后再排查数据问题省力得多。3.3 数据集划分训练集、验证集与测试集的比例把握转换完标注只是第一步还需要把整个数据集划分成三份训练集、验证集、测试集。项目一般提供一个split.py按 8:1:1 做随机划分常见处理是固定随机种子保证多次运行划分结果一致import random import os from glob import glob random.seed(42) images sorted(glob(NEU-DET/IMAGES/*.jpg)) random.shuffle(images) n_train int(len(images) * 0.8) n_val int(len(images) * 0.1) # 剩余 10% 作为测试集 train_list images[:n_train] val_list images[n_train:n_train n_val] test_list images[n_train n_val:] def write_list(path, data): with open(path, w) as f: for img in data: # 只写相对路径训练脚本配合 data.yaml 的路径前缀使用 f.write(img.split(NEU-DET/)[-1] \n) write_list(train.txt, train_list) write_list(val.txt, val_list) write_list(test.txt, test_list)注意这里只列了图像路径没有列标注路径因为 YOLOv5 的加载逻辑默认标注和图像同名同目录只是扩展名不同。如果你把标注放在独立目录训练脚本运行时会出现标注找不到的报错这个我在第 5 章会细说。划分比例为什么是 8:1:1 而不是 9:1?这批钢材缺陷数据总量不算大每一类大概几百张9:1 会让验证集太少mAP 值波动很大多跑一次结果可能差三四个点不稳定得让人怀疑模型没收敛。8:1:1 是这类中小数据集的稳妥区间如果你后续自己采集数据量过万可以回归 9:1。3.4 数据增强策略小数据集怎么防过拟合钢材缺陷图像有个显著特点同类别之间纹理高度相似背景几乎全是灰色金属面。这种数据分布一旦训练轮次过长模型很容易过拟合——训练集 mAP 刷到 0.9验证集只有 0.6。这个项目在 YOLOv5 自带增强的基础上比较常用的是增加轻度马赛克增强、旋转角度限制和饱和度调整但注意灰度图本身没有颜色饱和度增强基本无效所以我一般建议把hsv_s这类参数调低把增强预算留给平移和缩放。说到过拟合就得提cache参数。YOLOv5 训练时可以加--cache把整批图片提前加载进内存训练速度快很多但要注意内存占用。这批 200x200 的灰度图单张很小缓存全部也就几百 MB放心开。如果你是二次开发换了一批 4K 大图这个参数会直接让内存爆掉到时候不要慌去掉--cache或者开--cache-ram的比例控制即可。4. 模型训练实操config 调参、训练命令与监控4.1 数据配置与模型配置双文件解析训练前有两类配置文件需要检查第一是数据配置data.yaml第二是模型配置比如yolov5s.yaml。data.yaml内容类似# 训练、验证数据列表路径 train: ./train.txt val: ./val.txt test: ./test.txt # 类别数 nc: 6 # 类别名顺序必须与 XML 转换脚本中的编号一致 names: [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches]这里的train和val指向的是步骤 3.3 生成的train.txt、val.txt文件路径YOLOv5 会从这两个文件里逐行读取图片路径。如果报错找不到文件多半是相对路径前缀不对需要在train.py里传入--data时同时注意工作目录位置。yolov5s.yaml则是模型结构定义只需要关注两处nc改成 6其他网络深度、宽度系数按需调整。项目默认给的是 small 版本也就是 yolov5s参数量最小、训练最快但精度也最低。你如果机器显卡显存大于 8G可以换成yolov5m或yolov5l效果会更好训练时间也会拉长不少。我个人的习惯是先用 s 版本做通配流程验证确认数据、代码链路没问题再上 m 版本跑正式训练。4.2 训练命令与关键参数说明确认好配置文件后训练命令长这样python train.py \ --data data.yaml \ --cfg yolov5s.yaml \ --weights yolov5s.pt \ --batch-size 16 \ --epochs 50 \ --imgsz 640 \ --device 0 \ --name steel_defect_run1参数含义逐一说下。--data指定数据配置--cfg指定网络结构配置--weights是指预训练权重路径。这里有个易错点--weights传yolov5s.pt时模型会加载 COCO 预训练权重作为初始值在这个小数据集上可以显著加快收敛。但要是把--weights和--cfg搭配错了比如yolov5s.yaml配yolov5m.pt会直接报维度不匹配原因是网络的宽度系数跟权重里的通道数对不上。YOLOv5 的权重文件内部结构跟模型配置是一一绑定的不能混用。--batch-size 16在 200x200 小图上训练没有压力但 YOLOv5 会把输入缩放到--imgsz指定的 640实际显存占用按 640 计算16 张 640x640 的图一张 8G 显存的卡够用。--epochs 50对中小数据集是合理区间跑完看results.png里的val_mAP0.5曲线如果还在明显上升就继续加轮次。实际经验是这批钢材数据在 40 轮左右基本收敛再往后训练集 loss 继续降验证集 mAP 反而可能小幅回落出现过拟合征兆。GPU 资源有限的人可以把--device cpu跑但速度慢十倍不止一片 50 轮可能要跑四五个小时不太建议。如果只有 CPU可以试试把--imgsz降到 416速度会快一些精度稍微牺牲。4.3 训练过程监控看哪些指标判断收敛训练输出会在runs/steel_defect_run1/目录下生成一系列文件重点看四个指标train/box_loss、val/box_loss、metrics/mAP_0.5、metrics/mAP_0.5:0.95。先看 box_loss训练集和验证集的损失曲线应该同步下降如果训练 loss 降了但验证 loss 不动甚至反弹过拟合了。mAP_0.5 表示 IoU 阈值 0.5 下的平均精度一般钢材这类目标不算小的场景mAP_0.5 能到 0.85 以上算合格。mAP_0.5:0.95 是更严格的指标对框的位置精度要求高达到 0.6 以上就算不错这个小数据集上强求 0.8 不现实。另外强烈建议看一眼confusion_matrix.png,这个图能直观反映哪个类别之间容易混淆。钢材缺陷里patches和pitted_surface视觉特征非常接近一个是麻点、一个是氧化皮坑点很多模型会把两者搞混。如果混淆矩阵显示这两类互相错认比例很高说明特征区分度不够常见解法是增加数据增强的缩放范围让模型看到更多局部纹理放大后的差异或者干脆合并成一个大类牺牲粒度换准确率。4.4 预训练权重与迁移学习的组合逻辑这里补一个细节钢材缺陷图是灰度图用 COCO 预训练权重做迁移学习第一层卷积假设输入 RGB 三通道特征但实际喂的是灰度图复制成的三通道图虽然形状一致但通道间的语义相关性跟真实 RGB 图不一样。大多数情况下模型依然能收敛因为第一层学到的是边缘、纹理这类低层特征跟颜色相关性不大。不过有个隐患如果数据加载器偷懒直接把灰度图单通道做 pseudo-RGB 复制网络的channels配置可能需要临时改成 1连第一层卷积的输入通道也要手动改。这个项目里我建议保持channels: 3 复制成三通道因为这样兼容性最好换数据集或者换预训练权重都不用再动网络结构。5. 推理部署与常见坑排查检测效果验证三板斧5.1 推理脚本加载 best.pt 对单张图和视频做检测训练完成后runs/steel_defect_run1/weights/下会生成best.pt和last.pt推理阶段用best.pt。推理命令python detect.py \ --weights runs/steel_defect_run1/weights/best.pt \ --source ../NEU-DET/IMAGES/123.jpg \ --conf 0.25 \ --iou 0.45 \ --save-txt \ --project runs/detect--source可以跟单张图、文件夹也可以跟视频路径。--conf 0.25是置信度阈值低于这个值的预测框会被过滤掉。钢材缺陷里有些类别的目标边界不清晰置信度天然偏低如果你发现大批漏检把--conf降到 0.1 再试但代价是误检会增多。--iou 0.45是 NMS 的 IoU 阈值值越小对重叠框的抑制越强默认 0.45 够用。检测完的结果会输出到runs/detect/目录每张图会画上预测框、类别名和置信度同时--save-txt会把每张图的预测框坐标保存为 TXT 文件方便后续批量后处理。5.2 我踩过的一个坑图像尺寸与 label 坐标不同步某次我把数据集的某个子集单独拎出来做验证只复制了 IMAGES 目录没有复制 ANNOTATIONS 目录结果推理时模型输出的框全部偏到图像边缘看起来像位置错乱。排查半天发现是预处理的缩放逻辑不一致训练时数据加载器默认把 200x200 图像 letterbox 到 640x640推理时如果输入尺寸不是 640 的倍数letterbox 补边的比例不一样框坐标换算就会整体偏移。这个项目里图像尺寸统一是 200x200只要训练和推理都用--imgsz 640letterbox 的行为一致不会出问题。但如果你换了一批尺寸不规整的图推理时一定要自己实现 letterbox 的反变换把 640 坐标系下的框坐标映射回原始图像像素坐标系这个细节最容易让人翻车。5.3 避坑清单六条具体排查记录现象1训练启动即报错 AssertionError: train: No labels found 原因train.txt 里的图片路径对应的标注 TXT 不存在。 解决检查标注目录是否和图片同名同目录YOLOv5 默认把图片路径后缀 .jpg 换成 .txt 在同一个目录寻找标注。 现象2loss 不降一直维持在初始大值 原因标注坐标越界归一化后出现负数或大于 1 的值。 解决用脚本扫描所有 TXT 标注检测 x_center w/2 1 的情况同时检查 XML 转 TXT 时是否读取了正确图片尺寸。 现象3训练到后期 val mAP 反而掉点 原因过拟合训练轮次过多。 解决增加早停patience 参数调大或增强数据增强强度、降低模型容量换 yolov5s。 现象4检测结果把 patches 和 pitted_surface 混淆 原因两类图像纹理高度相似网络难以区分。 解决看看混淆矩阵确认问题比例增加缩放类数据增强或者考虑合并类别。 现象5推理时提示 CUDA out of memory 原因batch-size 太大或输入分辨率太高。 解决把 detect.py 的 --batch-size 调成 1--imgsz 调成 416。 现象6换用 yolov5m.yaml 训练报参数不匹配 原因--cfg 和 --weights 对应版本不一致。 解决只改 --cfg 为 yolov5m.yaml同时 --weights 也要换成对应的 yolov5m.pt不能沿用 s 的权重文件。每条都来自真实高频问题尤其是 1 和 2几乎每个用这套数据的人都会遇到。如果你看到自己的报错在上面列表里先按对应方案排查通常十分钟内能定位。6. 二次开发换数据集、换模型结构、落到产线的改造路径6.1 把钢材六类换成你自己的缺陷类型这套资源最有价值的地方在于流程可迁移。把钢材缺陷替换成其他工业质检图比如电池表面划痕、瓷砖裂纹、焊缝气孔只需要做三件事。第一整理图片和标注标注格式按前面讲过的 YOLO 格式组织类别编号从 0 开始连续编号不要跳号。第二修改data.yaml的nc和names保证名字列表顺序和标注编号一一对应。第三把数据集按 8:1:1 划分重新生成train.txt、val.txt。这三个步骤做完直接拿第 4 章的训练命令跑就行其余代码都不用改。6.2 从 yolov5s 升级到 yolov5m 的注意点前期流程验证用 s 版本没问题正式上线追求精度时换 m 版本你需要做两个动作把--cfg改成yolov5m.yaml把--weights改成yolov5m.pt。这里有一个我从某次训练里总结的血泪经验只改--cfg不改--weights报错信息会直接点名shape mismatch但新手往往会去检查数据格式绕一大圈才发现是模型结构参数不匹配。原因是 YOLOv5 的权重文件里包含了每一层的 shape 信息s和m的深度宽度不一样shape 自然对不上。正确做法是--cfg和--weights配套更换不要混搭。6.3 批量预测与结果落库有点工业味的输出格式产线场景一般不会对着单张图调用一次 detect.py而是需要批量处理并输出结构化结果。常见改造是把推理封装成一个函数批量读图、批量预测、把结果写进 CSV 或落在本地数据库。核心逻辑如下import torch import cv2 import pandas as pd model torch.hub.load(ultralytics/yolov5, custom, pathruns/steel_defect_run1/weights/best.pt) records [] for img_path in image_list: img cv2.imread(img_path) results model(img, size640) for *box, conf, cls_id in results.xyxy[0].tolist(): records.append({ image: img_path, class_id: int(cls_id), xmin: box[0], ymin: box[1], xmax: box[2], ymax: box[3], confidence: round(conf, 4), }) df pd.DataFrame(records) df.to_csv(predictions.csv, indexFalse)model(img, size640)里的size和训练时的--imgsz必须一致不然框坐标会有系统性偏移。results.xyxy[0]返回的是这张图所有的预测框每一行是[x1, y1, x2, y2, confidence, class_id]。这个 CSV 落到生产线侧的看板系统后可以按缺陷类型统计当天不良率、按缺陷位置分析轧辊磨损趋势比单纯画一张检测图实用很多。6.4 关于部署形态和模型转换的最后一句话如果你要部署到嵌入式设备或者用 TensorRT 加速YOLOv5 官方仓库提供了export.py可以把 PyTorch 权重转成 ONNX 或者 TensorRT engine。钢材缺陷检测在生产环境常见的做法是用 ONNX Runtime 跑在工控机上不依赖 PyTorch 环境显存占用也更低。我自己的习惯是每训完一版就顺手导出一次 ONNX反正命令就一行python export.py --weights runs/steel_defect_run1/weights/best.pt --include onnx导出的best.onnx可以直接用 ONNX Runtime 加载推理延迟通常比 PyTorch 低一截。从那以后我每次换数据训练都会先跑通这条导出链路确认 ONNX 模型输出的框和 PyTorch 输出一致再往产线脚本里接。这套流程看起来多一步但能省掉上线前的大半联调时间。希望这篇拆解能帮你少走点弯路。本文还有配套的精品资源点击获取