做钢材表面缺陷检测这个项目之前,我一直觉得目标检测在通用场景里已经够成熟了,无非是标注、训练、调优三步走。真把“基于YOLOv5的钢材表面缺陷检测”完整落地一遍之后,才发现核心难点根本不在算法本身,而在数据质量、标注规范、缺陷尺度差异这些看起来不起眼却决定成败的环节上。这个项目的完整链路,是用YOLOv5模型配合一套完整的数据集处理流水线,对热轧、冷轧钢板表面的裂纹、夹杂、麻点、氧化皮、划痕等常见缺陷做自动识别和定位。整套方案覆盖了数据准备、格式转换、划分、增强、环境搭建、模型训练、参数调优,一直到评估与推理部署。想快速复刻一个可用的钢材质检小系统、或者正在做工业视觉方向的入门同学,这份内容可以直接当作业来抄。
1. 项目核心思路:为什么选定YOLOv5
1.1 钢材表面缺陷检测到底在解决什么问题
钢铁产线的质检,属于典型的工业视觉场景。过去靠老师傅肉眼盯,一条中厚板产线一小时能跑几百米的钢板,人眼根本盯不过来。人工质检的问题是一本算不清的账:长时间盯屏,眼睛疲劳导致漏检率升高;缺陷种类多且外观相近,新人跟老师傅的判断标准很难完全一致;一旦产线提速,人员配备就只能跟着涨,成本居高不下。更关键的是,人工抽检覆盖率往往不足三成,漏掉的缺陷一旦流到下游客户手里,就是索赔和品牌信任度的双重损失。
所以这类项目的目标非常直接:用深度学习模型代替人眼,完成“有没有缺陷、缺陷在哪儿、属于哪一类”这三个任务。钢材表面缺陷类别在行业内比较固定,热轧表面常见的裂纹、夹杂、斑块、麻点、氧化铁皮压入、划痕这六类,基本覆盖了轧制过程中绝大多数表面质量问题。
这个任务跟通用目标检测的最大差别在于缺陷本身的视觉特性。第一,很多缺陷对比度很低,比如麻点和氧化皮在灰度纹理背景下并不明显,人眼都要凑近才能分辨,模型同样容易被背景干扰。第二,缺陷尺度跨度极大,既有占满整板的大面积氧化皮压入,也有只有几十个像素的细裂纹和麻点。第三,实际生产中负样本远远多于正样本,合格板材占绝大多数,模型很容易“偷懒”把所有图像都预测成背景。这些约束决定了方案选型时必须重点考虑模型对细粒度纹理特征的提取能力。
1.2 目标检测方案选型:YOLOv5为什么能打
选定YOLOv5之前,我也认真评估过传统视觉方案。用阈值分割、灰度共生矩阵加纹理特征统计,再配分类器做判断,这种老路子在对比度高的缺陷上确实有效,但一旦光照变化、氧化皮颜色深浅不一,特征漂移就会非常严重,现场要不停调参数,维护成本极高。工业现场的稳定性和泛化能力,传统方法很难满足。
深度学习目标检测方案里,两阶段的Faster R-CNN精度确实高,但推理速度在在线检测场景下是硬伤,特别是多路相机同时跑的时候,算力开销扛不住。单阶段的SSD速度不错,但对小目标的召回能力偏弱,钢材缺陷里又有大量小目标。YOLO系列中,v5相对v3、v4在工程易用性上有巨大进步:仓库自带标注格式转换工具、数据增强、超参数进化、多尺度训练这些模块全部内置,我可以把主要精力放在数据处理而不是重复造轮子。
实际操作时我选的是YOLOv5s作为起点,而不是直接上YOLOv5x。原因很简单:工业部署场景算力受限,目标帧率必须保证实时性,s模型参数量小,单张GPU上能以毫秒级完成推理。新手容易陷入“模型越大越好”的误区,但检测效果不仅取决于模型容量,也取决于数据质量和训练策略。先用小模型跑通全流程,再根据精度瓶颈决定是否升级到m或l,整个训练接口一致,切换成本极低。这种渐进路线帮我省了大量返工时间。
| 方案 | 优势 | 劣势 | 结论 |
|---|---|---|---|
| 传统视觉 | 无需训练、解释性强 | 特征漂移严重、泛化差 | 不适合工业多变场景 |
| Faster R-CNN | 精度高、小目标友好 | 推理慢、部署成本高 | 离线分析可以考虑 |
| YOLOv5s | 速度快、精度均衡、生态好 | 小目标需要额外调优 | 本项目最终选择 |
1.3 完整流程长什么样
项目推进整体分为五个阶段。数据阶段,获取钢材表面缺陷图像,完成标注检查、格式转换、训练验证集划分,确定数据增强策略;环境阶段,搭建深度学习训练环境,准备YOLOv5开源仓库和依赖库;建模阶段,设计数据配置文件和模型配置,选定预训练权重策略;训练阶段,执行训练脚本,监控指标曲线,视情况调参和早停;评估阶段,在验证集上计算mAP等指标,对漏报误报做错误分析。后面所有内容都围绕这五件事展开,我会把每一阶段实际会踩的坑和操作细节都说明白。
2. 数据集处理与工程化细节
2.1 标注格式转换:VOC到YOLO
数据集使用某公开钢材表面缺陷数据集,包含大约1800张热轧带钢表面图像,覆盖六类缺陷,每类约300张。原始标注是Pascal VOC格式的XML文件,每个目标以xmin、ymin、xmax、ymax形式保存。而YOLOv5训练需要的是与图片同名的txt文件,每行一个目标,内容格式为:class_id center_x center_y width height,所有坐标都归一化到0到1区间。
转换代码是绕不开的第一步。我写了一个脚本完成全部转换:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_names): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_names: continue cls_id = class_names.index(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_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") name_no_ext = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, name_no_ext + ".txt"), "w") as f: f.write("\n".join(lines))几个转换坑值得单独讲。第一,不同标注工具导出的XML中,边界框节点名可能不一致,有的文件写xmin,有的旧工具导出的是Xmin,解析时最好对tag名统一转小写。第二,坐标是整数还是浮点要提前确认,计算目标宽和高时最好按像素差加1再归一化,否则框会小一圈。第三,转换完要抽查txt中归一化坐标有没有超过1.0的,如果标注框本身越界,模型训练时会看到非法目标框,轻则影响收敛,重则训练直接报错。这类问题在数据量几百上千时不容易一眼发现,但一旦发生了,排查成本远高于提前检查。
2.2 目录结构与数据集划分
YOLOv5约定数据目录最好设计为images和labels两个平行目录,图片放在images/train,标签放在labels/train,这样在配置文件中写路径最省心。最终目录结构如下:
datasets/steel/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml训练集、验证集、测试集划分比例我用了8:1:1。在1800张的规模下,这个比例能基本保证各类别在训练集中均出现。这里有一个重要的经验:不要只按文件名随机划分,而要按缺陷类别做分层抽样。我见过有人随机划分后,某个小类别在验证集里只分到几张图,验证指标波动特别大,看起来像模型不稳定,其实是数据划分不均匀导致的假象。正确的做法是在划分脚本里按类别分布做分层,保证每个缺陷类别在训练集、验证集里的占比接近原始分布。这样得到的mAP曲线才稳定,才有横向对比的价值。
2.3 数据增强和类别不均衡处理
原始数据量只有1800张,对深度学习模型来说明显偏少。好在YOLOv5内置了丰富的增强手段。Mosaic增强在一张训练图里拼接四张图,相当于每批次能看到的上下文扩大了4倍,对目标尺度变化和背景干扰多的工业缺陷非常有效。我另外开启了随机翻转、轻微旋转、HSV色域扰动、平移和缩放。
这里要特别强调,增强参数不是越大越好。工业缺陷检测场景里,旋转角度我控制在5度以内,因为钢板缺陷的朝向在物理上有固定倾向,比如划痕多沿轧制方向延伸。如果做90度旋转或大幅度旋转,会造出大量跟真实生产分布不一致的伪样本,模型学到的方向先验就错了,部署时遇到正常方向的缺陷反而可能丢检。颜色扰动同样要克制,原始图像有固定的灰度分布和打光条件,颜色调过头会让模型对光照变化过度敏感,真实缺陷和干扰纹理更难区分。
针对类别不均衡,除了用Mosaic增强让每个batch都能撞见少数类,我还对样本最少的两个类别做了简单的过采样,在训练集里额外复制几份对应图片。训练时随机shuffle后,这些类别实际参与训练的次数变多,对少数类AP的提升非常直接。这种朴素手段在几千张规模的数据集上效果明显,相比之下,引入复杂的focal loss等方案收益反而不大。
2.4 数据质量检查
数据集处理过程中最耗时的是清洗,不是格式转换。我做两个动作。第一个是可视化检查,写脚本把每张训练图的真实框画出来拼图查看,重点看有没有框错位、类别标错、重复框。第二个是统计每个txt文件里的目标数量,如果有大量图片对应空txt,要确认是正常负样本还是漏标了。
检查时遇到一个典型问题:某张图里既有大面积氧化皮又有细小裂纹,原始标注只标了氧化皮,裂纹漏标了。对检测模型来说,同一张图上相似区域一个标了一个没标,训练信号会产生矛盾,直接拖低裂纹这类缺陷的召回率。解决方式是建立“标注复核清单”,把容易漏标的缺陷组合和特征列出,逐图复核。这一步虽然枯燥,但对最终mAP的影响不亚于调参,是数据工程里最值得投入时间的部分。
3. 训练环境搭建与YOLOv5配置
3.1 环境与硬件要求
整个训练基于PyTorch框架,CUDA版本必须和PyTorch版本严格匹配。环境管理方面,强烈建议使用Python虚拟环境,避免不同项目之间依赖库冲突。我自己搭环境的顺序是:先安装对应CUDA驱动和CUDA Toolkit,再创建新的虚拟环境并激活,然后安装PyTorch,最后安装YOLOv5仓库依赖。不要嫌麻烦,环境问题在项目进行到一半时爆发才是最恶心的。
硬件方面,一块至少8GB显存的GPU是起步要求。选batch size有个简单方法:先用默认参数试跑一个step,观察单张图的显存占用,然后按显存大小 / 单张图占用取整得到初始值。以输入分辨率640x640、模型YOLOv5s为例,单张图大约占用3到4GB显存,8GB卡可以把batch设到8。如果显存不够,还可以借助梯度累积达到等效大batch效果,但梯度累积本质是拿时间换显存,不能盲目调大。没有独立GPU的情况下,用CPU训练这个规模的模型会非常痛苦,单epoch可能超过二十分钟,不建议尝试。
3.2 数据配置文件与模型配置
YOLOv5用yaml文件描述数据集信息。我把data.yaml写到数据目录下:
train: datasets/steel/images/train val: datasets/steel/images/val test: datasets/steel/images/test nc: 6 names: ["crazing", "inclusion", "patches", "pitted_surface", "rolled_in_scale", "scratches"]这个文件里最容易踩坑的是names顺序。训练时的names顺序必须和标签txt中的class_id一一对应,一旦改过数据集或重新生成标签,顺序错位会让模型所有预测的类别对不上号。更隐蔽的是,这种错位在训练指标上往往看不出来,因为损失照样下降,直到你可视化预测框时才发现类别全乱了,这种问题排查起来极其痛苦。所以改完数据后,一定要前先跑一次推理可视化,确认类别名称和框对应正确,再继续后续实验。
模型配置方面,YOLOv5提供了s、m、l、x几档结构,区别主要在网络的深度和宽度倍数。第一次训练直接使用默认的YOLOv5s即可。如果后续想微调结构,可以在模型yaml里改动depth_multiple和width_multiple,但新手不建议动这两个参数,默认配置已经过大量场景验证,稳定性和效果都有保障。
3.3 预训练权重:用还是不用
我强烈建议用COCO预训练权重做迁移学习初始化,而不是随机初始化。效果差异非常明显:迁移学习能让收敛速度快三分之一以上,早期mAP提升曲线也更平滑。原因是COCO上有大量通用目标特征,底层卷积已经学会了边缘、纹理、颜色等基础模式,钢材缺陷虽然和自然图像分布差异很大,但底层特征是可以复用的。
这也带来一个注意事项:迁移学习时初始学习率不要开太大。首次训练就用默认学习率0.01,先观察前几个epoch的损失曲线,再酌情调整。有些项目一上来就把学习率调到0.02以上,预训练权重很快被破坏,后面再怎么训练都回不到理想效果。如果你发现加载预训练权重后前几个epoch损失反而升高,大概率是学习率偏大,需要降一个数量级重试。
4. 训练实操与参数调优
4.1 训练命令和关键参数
进入YOLOv5仓库目录后,训练命令大致如下:
python train.py \ --data datasets/steel/data.yaml \ --weights yolov5s.pt \ --batch-size 8 \ --img 640 \ --epochs 100 \ --patience 20 \ --device 0几个参数的实际含义和使用逻辑要搞清楚。--img是训练输入分辨率,不是原始图像尺寸。--batch-size在显存允许范围内尽量大,太小会让Batch Normalization统计不准。--patience是早停耐心值,连续20个epoch验证集mAP没有提升就自动停止,防止无效长时间挂机。训练时终端会实时打印P、R、mAP50、mAP50-95等指标,同时runs/exp目录下会生成标签分布热力图、混淆矩阵和PR曲线图,这些图比终端数字更直观,要养成定期查看的习惯。
4.2 小目标与低对比缺陷的定向调整
钢材缺陷的难点集中在细裂纹和麻点这类小目标上。YOLOv5默认会在训练时根据数据集自动重新聚类anchor,训练日志里会显示autoanchor相关输出,如果提示锚框数量需要调整,建议相信它。虽然手动把anchor设得更小往往能骗过自己,但autoanchor在多数情况下已经足够,徒增变量反而影响复现。
输入分辨率是另一个杠杆。我把--img从640调到960后,小目标的mAP有明显上升,代价是训练时间和显存占用上涨。如果部署端对推理速度没有严格限制,工业缺陷检测可以优先保精度,适当提高输入分辨率。关键细节是:训练时用960,推理时也最好保持960,不要训练用高分辨率、部署用低分辨率,否则尺度不匹配会损失大量性能。
下表是我在640和960分辨率下的实测对比感受,具体数值会随随机种子波动,但趋势稳定:
| 输入分辨率 | 小目标召回 | 训练耗时 | 显存占用 | 适用场景 |
|---|---|---|---|---|
| 640 | 中等 | 基准 | 中 | 追求实时性 |
| 960 | 明显提升 | 增加约50% | 高 | 更看重精度 |
4.3 训练曲线怎么看
训练过程中最常看三张曲线:损失曲线、PR曲线、混淆矩阵。损失曲线重点关注训练损失和验证损失是否同步下降。如果训练损失一直降、验证损失开始反弹,就是过拟合信号。钢材缺陷数据集规模小、模型容量大,过拟合来得比通用场景更早,我一般从第40个epoch左右开始盯紧验证损失,一旦出现反弹就及时早停。
PR曲线反映各类别精确率和召回率的权衡关系,曲线下面积越大,模型越可靠。混淆矩阵则直接暴露模型把哪两类缺陷搞混。氧化皮和斑块外观相似,混淆矩阵里这两类交叉值通常偏高,这是训练层面解决不了的信息不足问题,应该从数据层面补充边界样本,而不是一味调参。看懂这几张图,比盯着终端里刷新的数字有用得多。
4.4 断点续训与结果保存
训练中断是常态,YOLOv5会在每个epoch结束时保存last.pt和best.pt。续训时把--weights指向last.pt,再加--resume参数,就能从断点继续训练。best.pt是基于验证集mAP最优的权重,最终评估和部署都应优先选best.pt,不要用last.pt,后者只是训练过程快照,不代表效果最优。
保存策略有个经验:默认每个epoch都保存权重,磁盘占用其实很小,不用改配置。唯一要注意的是别把不同实验的结果混在同一个runs目录下。我吃过这个亏:两个实验共用输出目录,比较模型时找错权重,白白浪费一整天。每次训练前把输出目录重定向到新目录,或者定期清理旧结果,养成习惯后能避开很多低级错误。
5. 常见问题排查与实战避坑
5.1 训练Loss不降或者直接出NaN
这类问题我遇到过两次,基本锁定在两个原因。第一是标签文件里出现越界坐标,归一化后某个w或h异常,导致损失计算出现无穷值。排查顺序是:先写脚本统计所有txt中归一化坐标的最小值和最大值,如果发现小于0或大于1的值,定位到具体文件重新标注。第二是学习率过高导致模型发散,把学习率降到0.001试跑20个epoch,损失曲线通常会恢复下降趋势。这两个原因分别排查,很快能定位。
5.2 个别缺陷类别AP总是很低
如果其他类别都正常,只有某一个类别的AP特别低,先查数据量。样本数少于50张的类别,即使做了增强,也很难学到稳定特征。然后再检查这类缺陷是否容易和其他类别混淆,观察混淆矩阵确认。如果两个都排除,可以考虑把该类别的样本专门挑出来做一次更强的数据增强,再混合进训练集。注意控制增强幅度,否则会生成大量重复伪样本,反而过拟合。
数据量问题的另一个解法是收集更多现场数据,但这在项目初期往往不现实。退而求其次,可以先用现有数据把模型框架跑通,后续再逐步补充现场样本做增量训练,这本来就是工业视觉项目的常态。
5.3 验证集指标很高但检测现场效果差
这类问题本质上还是数据分布不一致。训练集里的图像是固定机位、固定打光条件拍的,实际产线会有换角度、光照强度变化,甚至镜头上有水雾。验证集mAP高只能说明模型在特定分布上表现好,不能代表现场泛化能力。解决思路是采集更多现场图像并入训练集,而不是只调模型参数。另外,训练时适度加入模糊、噪声、阴影模拟增强,能提高模型对现场干扰的鲁棒性。这条经验来自实际教训:有一次验证集指标已经很好,一道现场就把精度打回原形,后来补了现场图像再训练,差距才真正缩小。
5.4 推理部署时的细节
模型训练完导出ONNX格式做CPU或边缘设备推理时,有一个容易忽略的点:YOLOv5导出时默认包含NMS模块,但很多推理框架对内置NMS支持不一致。我的做法是导出时去掉NMS,在业务代码里自己实现或调用对应框架的NMS,可控性更高。同时要配置置信度阈值和IoU阈值,一般置信度阈值设0.25、NMS的IoU阈值设0.45作为起点,再根据现场误报和漏报情况调整。输入图像预处理必须与训练保持一致,包括归一化除255、letterbox填充灰边等,任何一处不一致都会让精度打折扣。
最后聊点项目之外的体会。一个YOLOv5检测模型从训练完成到真正稳定用于产线质检,中间还有很长的路。模型指标并不等于现场效果,现场要反复试跑,收集误报漏报样本,再把这些样本加入训练集做增量迭代。这个项目最让我受益的不是模型代码本身,而是意识到工业视觉项目的重心其实在数据工程:六成时间在整理数据、清洗标注,三成时间在测试评估,真正敲定模型结构和调参可能只占一成。如果你也准备做类似项目,我的建议是别让模型调参占据过多精力,先把数据和评估闭环做扎实,后面自然水到渠成。