☰
YOLOv5钢材表面缺陷检测实战:数据处理、调参优化与模型部署全复盘
2026/10/22 7:51:09 网站建设 项目流程

做了两年工业视觉项目,其中接触最多的就是表面缺陷检测这类需求。钢材表面缺陷检测更是典型的"看着简单、做起来一堆坑"的方向,数据少、类别不平衡、瑕疵形态多变,还要考虑产线实际推理速度。这次把我的完整项目复盘整理出来,项目编号DL00618,从数据集处理到YOLOv5训练再到部署验证,全部摊开来讲,希望能给正在做类似方向的读者一些参考。

这个项目适合这几类人:刚开始接触工业缺陷检测的学生或开发者,想了解YOLOv5在钢材表面数据上怎么调参的工程师,以及准备把检测模型落地到产线上的朋友。我会把核心代码、配置、踩坑记录都写出来,尽量让内容能直接复现。读完你至少能搞明白:这份公开的钢材缺陷数据该怎么预处理,YOLOv5哪些地方需要针对小目标和细长纹理做调整,训练过程中哪些指标才是真正值得盯的。

1. 钢材缺陷检测的难点和这个项目要解决的三个问题

先说说为什么这类任务值得专门写一篇。钢材表面缺陷的自动化检测,在冶金行业里属于质检环节的核心需求。传统的机器视觉方案也有不少公司在做,但效果往往受限于光照条件、钢材表面纹理干扰、缺陷形态多样性。深度学习方案这几年逐步替代传统方案,一个重要原因是泛化能力更强——不需要针对每一条产线单独设计几十个滤波器和规则阈值。

我在项目启动前,先把这个任务拆成了三个核心问题:

第一,数据从哪来、质量如何。标注框准不准、缺陷类别分布均不均匀,直接决定模型上限。这个项目用的是一份公开的钢材表面缺陷数据集,图像是灰度图,每张尺寸不大,共包含六类典型缺陷:裂纹、夹杂、斑块、麻点、氧化皮、划痕。每类300张左右,总量不到2000张,说实话对深度学习训练来说是偏少的,必须靠数据增强和合理的训练策略补足。

第二,模型怎么选、怎么改。YOLOv5在通用目标检测上很成熟,工程生态完善,但直接拿默认配置跑钢材缺陷数据,效果不会好。原因在于这些缺陷很多是小目标或者细长形纹理,比如划痕可能只有几个像素宽、横跨整张图,裂纹呈网状分布,跟常规的COCO目标差异很大。anchors、输入分辨率、网络深度都要针对性地调。

第三,怎么验证效果、怎么部署。离线测试mAP高不等于产线能用,还要看推理速度、单类漏检率、误检率。尤其钢材表面本身就有氧化层、水渍、光照不均这些天然干扰,模型很容易把正常表面误报成缺陷,这类问题在实验室指标里看不出来,部署时才会暴露。

这三个问题梳理清楚后,整个项目的技术路线就非常明确了:数据集清洗与格式转换,YOLOv5配置调整与训练,模型导出与推理验证。下面按这个顺序展开。

2. 数据集处理:从标注格式混乱到直接能喂给模型的干净数据

2.1 原始数据的格式摸底

拿到数据的第一件事不是急着训练,而是把数据从头到尾看一遍。这份公开数据集的标注是VOC格式的XML文件,一张图对应一个XML,里面记录的是物体类别和bounding box坐标。图像本身是灰度图,尺寸比较小,但YOLOv5要求的三通道输入,所以灰度图需要复制成三通道。

我习惯先把数据摸个底,统计几个关键指标:每类缺陷的样本数、标注框的数量分布、标注框的尺寸分布、是否存在空标注文件。这一步千万别跳过,工业数据的坑比想象中多,比如某些图片的标注框坐标超出了图像边界、某些类别名字拼写不一致、某些XML里没有object节点。用一段Python脚本可以完成基本的摸底检查:

import xml.etree.ElementTree as ET import os, cv2 counts = {} box_sizes = [] for xml_file in os.listdir('annotations'): tree = ET.parse(os.path.join('annotations', xml_file)) root = tree.getroot() objects = root.findall('object') for obj in objects: name = obj.find('name').text counts[name] = counts.get(name, 0) + 1 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) w = x2 - x1 h = y2 - y1 box_sizes.append((w, h)) print(counts)

我这次跑下来发现,六类缺陷的样本量基本均衡,每类都在300张上下,但标注框大小差异很大。划痕和裂纹的框通常特别窄长,甚至出现宽高比超过10的框,这在通用数据集里很少见。这个发现直接影响后面的anchors设置。

2.2 从VOC格式转成YOLO格式

YOLOv5训练需要的是TXT标注文件,每行一个目标,格式是:类别id 中心点x 中心点y 宽 高,都归一化到0到1之间。转换时最容易出错的是坐标归一化没做好,尤其是边界值刚好等于图片宽高时,除以宽高后会得到1.0,这在某些增强操作里会出问题,建议稍微做一下边界截断。

转换脚本的关键部分如下:

def voc_to_yolo(xml_path, out_dir, class_names): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) with open(os.path.join(out_dir, xml_path.replace('.xml', '.txt')), 'w') as f: for obj in root.findall('object'): name = obj.find('name').text cls_id = class_names.index(name) 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) dw = 1.0 / img_w dh = 1.0 / img_h cx = (x1 + x2) / 2.0 * dw cy = (y1 + y2) / 2.0 * dh w = (x2 - x1) * dw h = (y2 - y1) * dh # 边界保护 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) f.write(f'{cls_id} {cx} {cy} {w} {h}\n')

转换完成后,我会随手找几张图把标注框画出来检查一下,防止格式对但坐标错位。这一步是性价比最高的检查手段,比看日志可靠得多。

2.3 数据增强策略怎么定

原始数据只有不到2000张,直接训练很容易过拟合。YOLOv5自带了丰富的在线增强,包括马赛克(Mosaic)、随机仿射、HSV扰动、水平翻转等。但对钢材缺陷数据,并不是所有增强都适用。比如HSV扰动,钢材表面是灰度图,色相扰动没有意义,饱和度和明度扰动反而要克制,太大会让钢材的纹理失真。我的做法是关闭色相扰动,明度扰动保持适度。

另一个需要特别注意的增强是马赛克。马赛克把四张图拼接成一张训练样本,对小目标检测很有帮助,因为相当于把目标缩放了。但钢材缺陷的边界本来就模糊,拼接处的裂缝会产生大量伪缺陷信息。我的经验是马赛克可以用,但不要全程开启,建议前大半段训练开启,最后几十个epoch关闭,让模型回归到原始分布上微调。

针对缺陷样本不平衡的问题,还应该做类别层面的分析。六类缺陷虽然每类样本量接近,但检测难度差异大,像裂纹和麻点这类纹理型缺陷会被频繁漏检。此时可以在损失函数层面加大难例权重,YOLOv5里可以通过调整类别损失系数来实现。不过这个操作要谨慎,权重过大可能导致简单类别准确率下降。

2.4 训练集/验证集的划分细节

数据集划分看着简单,工业场景下也有讲究。我建议按目录或按数据采集批次划分,而不是随机打乱。公开数据的图像采集顺序往往隐含了不同的样本状态,随机划分会把同一状态的样本同时分到训练集和验证集,导致验证指标虚高。更稳妥的做法是先给所有样本按缺陷类型的组合做分层抽样,保证训练集和验证集的类别分布接近,再按比例划分。

我最后用了大约87%的样本做训练,剩下的做验证,没有单独分测试集,因为样本量有限。如果你的数据量足够,最好还是留出一部分完全没参与过训练的测试集,用于最终效果评估。

3. YOLOv5配置里的关键决策:选哪个模型、anchors和损失函数怎么调

3.1 模型规模:不是越大越好

YOLOv5系列有n/s/m/l/x五个尺寸,很多人上来就喜欢用x,觉得效果一定最好。但工业缺陷检测跟通用检测不一样,数据量小、目标形态特殊,大模型极易过拟合,而且产线推理对帧率有硬要求。从我跑的结果看,在钢材缺陷这个任务上,m的效果和x差距很小,训练速度却快很多;s在部分类别上漏检率偏高,尤其是小目标裂纹和划痕。

我最终选了YOLOv5m作为主力模型。如果部署端的算力比较吃紧,可以用s配合更大的输入分辨率来弥补精度损失。核心思路是:优先把输入分辨率提上去,再考虑模型复杂度。因为钢材缺陷多是小目标,分辨率对召回率的影响非常直接。

3.2 输入分辨率和anchors的调整

YOLOv5默认训练尺寸是640x640,anchors是按COCO数据集聚类出来的。COCO里的目标普遍比较大,而钢材缺陷里大量是几个像素宽的长条纹理,直接沿用默认anchors会很难匹配。Z这个问题我分两步处理:

第一步,提高输入分辨率。我在训练时用了896x896,代价是batch size必须降下来,显存占用明显上升,但换来的是小目标召回率提升显著。分辨率也不是越高越好,超过某个值后收益递减,而且推理速度急剧下降。896这个值是我在对比了640、768、896三档之后确定的,再高就超出部署端的实时要求了。

第二步,重置anchors。YOLOv5支持两种方式,一种是用--noautoanchor关闭自动学习anchors,另一种是不加这个参数,让训练开始时基于自己的数据重新聚类anchors。我的做法是让YOLOv5自动聚类,训练日志里会输出新的anchors值。聚类结果也确实跟默认值差别很大,多数anchors变成了细长的形状,这正是钢材缺陷的特点。

3.3 灰度图和数据格式的处理细节

钢材表面缺陷数据是灰度图,YOLOv5的数据加载器默认按三通道读取。我们的解决办法是读取灰度图后复制成三通道,或者干脆在读取时用cv2.IMREAD_COLOR强制转成三通道。后者更简单,但要注意别用cv2.IMREAD_GRAYSCALE之后再拼接,因为灰度图转三通道的正确方式是三个通道的值都等于原灰度值,而不是只有一个通道有值。

我在数据加载环节加了一个检查函数,随机打印一批增强后的图像和标注框,确认数据喂进去的形状是(batch_size, 3, height, width),标注框坐标都在合理范围内。这个习惯帮我避免了很多训练过程中"loss看起来正常但效果很怪"的问题。

3.4 损失函数与类别不平衡的平衡策略

YOLOv5的损失由三部分组成:分类损失、置信度损失、边框回归损失。默认设置下,边框回归用的是CIoU Loss,分类用的BCE With Logits Loss。钢材缺陷任务里,难检测的类别(裂纹、划痕)主要难在召回率,也就是置信度阈值之下被过滤掉了。提高召回率有两个方向:一个是调低最终的confidence阈值,但会带来误检;另一个是训练时让模型更关注这些难例。

我在训练的后期阶段,将分类损失的难例挖掘权重提高了,同时关闭了马赛克增强,这两个操作配合起来,对难类别的召回率提升比较明显。需要提醒的是,这类改动一定要在验证集上分别统计每个类别的精确率和召回率,不要只看mAP一个数字。mAP提升0.01,有时候恰恰是某个类别变得更差了,只看平均值会掩盖这个问题。

4. 训练实测:命令、参数、和那些翻过车的坑

4.1 环境配置与基础参数

训练环境用的是常见的深度学习容器配置,GPU是24GB显存档位的卡,如果显存只有6GB,建议用yolov5s模型或者把输入分辨率降到640。依赖清单很简单:PyTorch、OpenCV、tensorboard等,YOLOv5自带的requirements文件可以直接装。我建议从官方仓库拉取代码,而不是用某个修改版,因为修改版可能带有别人的配置习惯,出了问题很难排查。

数据集配置文件steel.yaml大概是这样的:

train: datasets/steel/images/train val: datasets/steel/images/val nc: 6 names: ['crazing', 'inclusion', 'patches', 'pitted_surface', 'rolled-in_scale', 'scratches']

对应的训练命令是:

python train.py --data steel.yaml --cfg yolov5m.yaml --weights yolov5m.pt --batch-size 8 --img 896 --epochs 200 --hyp hyp.scratch-low.yaml --project runs/steel

其中--weights yolov5m.pt表示使用官方预训练权重作为初始权重,这在数据量少时非常有用,可以显著加速收敛并提升最终精度。--hyp hyp.scratch-low.yaml选的是适合迁移学习的基础超参数,增强强度较低,对工业数据更稳。

4.2 损失曲线到底该怎么看

训练过程中我主要盯三类日志:训练损失、验证损失、验证集的mAP曲线。YOLOv5的训练日志里,每一轮都会输出类似box_loss、obj_loss、cls_loss的数值。很多初学者看到box_loss从0.08降到0.06就觉得训练不错,其实更重要的参考是验证集上的mAP曲线有没有稳定上升、最后是否出现明显过拟合迹象。

我这次训练实际跑了大概180轮,前半程mAP曲线稳定上升,90轮之后速度放缓。从150轮开始,我注意到验证集的mAP提升已经非常微弱,但训练损失还在继续下降,这是典型的过拟合前兆。此时不需要急着停,可以让它继续跑,等150轮以后看是否有一个验证集精度峰值,然后通过最优权重路径取那个峰值模型。

YOLOv5会自动保存每个训练阶段的权重,默认保留best.pt和last.pt。建议训练结束后用best.pt测试,不要用last.pt——last.pt在过拟合阶段可能已经偏离了最优位置。

4.3 训练中的三大翻车现场

第一个常见的坑是显存不足导致训练中断。我第一次用896分辨率跑yolov5m时,batch size设为16,直接OOM。后来把batch size降到8,并用梯度累加的方式来等效放大batch size,效果就正常了。YOLOv5的--batch-size参数配合--accumulate参数可以做梯度累加,等于用时间换空间。

第二个坑是数据增强过度导致训练集和真实分布偏离。前文提到我关闭了色相扰动、克制了明度扰动,这里再补一句:对灰度图数据,很多人在模型代码里直接读RGB图,结果模型学到的其实是"光的颜色"这种虚假特征,而钢材表面的纹理细节反而是次要的。这话听起来好像无所谓,但遇到实际产线光照变化时,模型的泛化能力会明显下降。

第三个坑是类别标签混乱。这份公开数据集的类别名称在不同版本里可能不一样,有的叫PittedSurface,有的叫Pitted_surface,如果你直接拿去写names列表,训练全程都不报错,但类别匹配就是错的。因此建议配对完类别名称后,把每一类缺陷的名称跟标注原图对照一遍,别嫌麻烦。

4.4 超参数调整经验

训练到120轮之后,我对比了不同超参数的表现,有几个经验值得分享。首先,学习率的初始值不要太高。YOLOv5默认的学习率设计已经比较合理,但如果你发现训练初期损失震荡剧烈,可以把hyp里的lr0从0.01降到0.005。其次,warmup阶段要保留足够长的预热轮数,前5~10个epoch让模型先适应数据分布,能有效防止开局发散。第三,针对难分类别,可以在hyp里调整cls_pw和cls权重,但每次只动一个参数并且记录对比结果,不要贪多。

我最终把cls_pw从默认值略微提高,让模型在分类损失上更关注少数难例。这类改动要记录在训练配置里,方便复现对比。

5. 推理验证与偏业务侧的部署经验

5.1 检测指标:mAP之外还要看什么

模型训练完后,我在验证集上跑了完整评估,生成了混淆矩阵和PR曲线。整体mAP@0.5在0.78左右,mAP@0.5:0.95大约0.52,从数字上看中规中矩。但mAP掩盖了类别差异:划痕和氧化皮这两类的AP明显高出整体水平,裂纹和麻点则偏低,尤其是裂纹类别在低置信度下大量漏检。

产线部署不能只看mAP。漏检和误检的成本完全不一样——钢材表面缺陷漏检意味着缺陷板流入下游,误检意味着好板被错误降级,两者都会造成经济损失。因此部署前,我把每个类别的精确率/召回率曲线单独打印出来,和现场质检人员确认哪类缺陷更致命、允许的误检率是多少,然后据此调整置信度阈值。这个环节一定要跟工艺人员聊清楚,闭门调参会脱离实际需求。

5.2 导出ONNX和TensorRT推理

验证效果满意后,我开始做推理优化。第一步是把PyTorch模型导出为ONNX格式,YOLOv5官方脚本支持一键导出:

python export.py --weights runs/steel/exp/weights/best.pt --img 896 --batch 1 --include onnx

导出后的ONNX模型可以用ONNX Runtime或者转成TensorRT引擎推理。钢板检测通常部署在工控机的GPU上,TensorRT的加速效果非常明显。转换时注意输入尺寸要和训练时一致,动态batch可以打开,但现场一般固定batch为1,没必要额外优化。

我在部署后的实测中,模型在推理端的单张耗时从原始PyTorch的约28ms降到TensorRT的约9ms(这里指常见消费级GPU的数值),完全满足产线30帧/秒的要求。这个收益主要来自FP16精度和模型算子融合,精度损失几乎可以忽略。

5.3 现场部署中遇到的几个实际问题

第一个是光照环境。实验室验证用的都是公开数据集图片,成像条件比较理想。现场相机装在生产线上后,环境光照的波动、钢材表面的反光、氧化层的颜色差异,都会影响检测效果。试运行时模型误检率会上升,原因是钢材表面出现了训练数据里没有见过的"看起来像缺陷"的正常纹理。解决办法是收集现场真实图片,加入训练集做增量训练。这个闭环一定要跑起来,没有哪个模型能一次到位。

第二个是通信接口。检测结果需要实时回传给产线控制系统,我用了简单的TCP协议,把每个缺陷框的类别、置信度、中心点坐标发出去,同时打印一条日志记录图片路径和检测结果,方便事后回溯。工业现场对系统稳定性的要求远高于实验室,建议考虑异常重连、超时保护等问题。

第三个是类别置信度的阈值。前面说mAP高不等于能用,部署后我会根据现场反馈,按类别粒度分别设置置信度阈值。比如划痕类误检率偏高,我就把划痕的阈值提高,宁可漏掉一部分弱划痕,也不能让好板被频繁判废——这个取舍需要跟现场人员确认。

6. 把灯光调暗前的最后提醒

整套流程跑下来,有几个总结性的经验我想单独列出来。先说说如果重做一遍,我会优先做什么:拿到数据后先花时间做可视化分析和类别分布统计,而不是急着套模型训练。这次项目里,很多训练阶段的问题其实源于前期对数据形态的认知不足,比如细长框的普遍性、灰度图的通道处理、类别名称的混乱。

再说说这个项目后续的扩展方向。钢材缺陷检测这个任务可以做得更细,比如从检测升级到分割,把缺陷的具体轮廓像素级标出来,方便后续的缺陷等级判定和工艺追溯。分割的标注成本高很多,但工业场景里对缺陷面积的精确定量需求是真实存在的。另一个方向是用更轻量的检测网络替换YOLOv5,比如某些嵌入式平台专用框架,但我个人建议先用YOLOv5把流程跑通,再考虑极致性能优化,毕竟工程项目的核心是把问题解决掉,模型选型是手段而不是目的。

如果你自己也在做类似的数据集处理工作,建议把源数据和标注文件先做一次完整备份,然后把处理后的训练文件、配置文件、训练日志都按项目编号归档。这个项目我整理出了复现用的完整目录,后续换机器、换人接手都能快速恢复环境,而不是靠记忆重新搭一遍。做工业项目的经验就一条:把每一步可复现的东西都落到文件和脚本里,将来你会感谢现在的自己。

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

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

立即咨询