☰
钢材表面缺陷检测实战:基于YOLOv8的目标检测方案从数据到部署
2026/10/19 16:29:15 网站建设 项目流程

产线上一块钢板过去,表面有没有麻点、划伤、氧化皮,以前靠老师傅拿眼睛盯。人眼盯久了会疲劳,而且同一个缺陷换个时段、换个检验员来判,结果可能就不一样。我最近用YOLOv8做了一套钢材表面缺陷检测方案,从数据集处理、模型训练一路推到部署推理,完整跑通了一遍。这篇文章把整个过程、关键参数和踩过的坑都整理出来,适合刚接触目标检测、想拿工业缺陷场景练手的开发者,也适合已经在做检测、想对比一下方案选址的朋友。

我当时接到这个需求时,第一反应不是急着训练模型,而是先把数据从头到尾捋了一遍。原因很简单:钢材表面缺陷检测任务里,模型反而是最不难的部分,真正难的是把数据洗干净、把评价指标定对、把部署环境里的各种意外处理好。下面我就按我实际执行的顺序来写。

1. 项目初衷与技术选型

1.1 钢材表面缺陷检测难在哪

先说说这个任务本身。钢材表面缺陷检测,本质上是目标检测里的“小目标+外观差异大+类别不平衡”三重难题叠在一起。常见缺陷包括裂纹、夹杂、麻点、氧化皮、轧制氧化铁皮、划伤这六类,形态完全不同。裂纹是细长线状,稍不留意就被当成背景纹理;麻点是密集小坑,单个尺寸可能只有几十个像素;氧化皮是成片的不规则暗色区域,边缘模糊,跟正常的锈蚀还有阴影非常像。

这种任务跟我平时做行人检测、车辆检测完全不是一个感受。行人和车辆在图像里占的比例大、外观稳定,即使有遮挡也不至于找不着。钢材缺陷不一样,很多缺陷放在整幅图里可能只有整张图面积的千分之一甚至更少,就像在一张A4纸上找一根头发丝,还要说清楚它是裂纹还是划伤。再加上钢厂现场光照暗、粉尘多、钢板表面反光,同一个缺陷在不同工况下拍出来可能完全不像同一个东西。

还有一个现实问题:缺陷样本本身是稀缺的。负样本到处都是,正样本要等缺陷出现才能采到。很多公开数据集的图像是在实验室条件拍的,背景干净、光照均匀,拿这套权重直接上产线,大概率会被真实场景教做人。所以数据处理这一环必须花大力气。

1.2 对比一圈,为什么YOLOv8最合适

动手之前我其实比较过Faster R-CNN、YOLOv5和YOLOv8。Faster R-CNN精度确实高,但推理速度在普通工控机上跑起来不理想,产线对一帧的处理时间往往要求在几十毫秒级别,Faster R-CNN很容易卡在边界。YOLOv5也很成熟,社区资料多,但到了后续导出部署、内置增强这些方面,还是YOLOv8更省事。

我最终选YOLOv8的核心原因有这么几个。第一,YOLOv8是anchor-free设计,少掉了调anchor这一大堆事情,对于钢材缺陷这种目标尺寸分布特别不规律的数据集,省心很多。第二,YOLOv8的C2f结构在保持推理速度的同时特征提取更好,对小目标的敏感度比旧版有提升。第三,官方仓库直接集成了训练、验证、导出、推理一条链,不需要自己手写太多胶水代码。第四,也是很重要的一点,YOLOv8内置的数据增强和训练策略比较完善,比如mosaic增强、自动早停这些,能少踩不少坑。

当然不是说YOLOv8就万能。如果缺陷实在太小,YOLOv8照样会漏检,这时候需要结合滑窗或者提高输入分辨率来补救。但作为第一版方案,YOLOv8是稳妥的起点。

1.3 整体方案怎么规划

我给自己定了一条完整的执行路径,每一步都有明确的输入和输出:

  1. 收集数据,评估数据质量和类别分布。
  2. 数据清洗,修掉错误标注和重复图。
  3. 把标注格式统一转换成YOLO的txt格式,按序列切分训练集、验证集和测试集。
  4. 编写数据集配置文件,设置增强参数。
  5. 选择预训练权重,配置超参数,开始训练。
  6. 用best.pt做验证,输出per-class指标和混淆矩阵。
  7. 针对漏检误检案例迭代:补数据、改增强、调分辨率。
  8. 导出ONNX,放到推理环境里做最终测试。

这套流程看起来很常规,但每一步里都有说不完的细节。下面我按这个顺序拆开细讲。

2. 数据集拿到了,先别急着训练

2.1 数据从哪来,质量和规模怎么看

我手里的钢材缺陷数据主要来自两个来源:一个公开的钢材表面缺陷数据集,另一个是某厂现场抽拍的钢板表面图像。公开数据集的好处是标注规范,能用来快速验证流程;坏处是图像背景偏干净、缺陷形态相对典型,跟产线实际数据有gap。现场数据更真实,但标注成本高,格式也乱。

拿到数据第一步不是数张数,而是做抽样检查。我习惯把每类图片随机抽20%出来,用标注工具打开快速翻一遍,重点看三件事:标注框是否紧贴缺陷边缘,有没有把两个相邻缺陷框在一个框里,有没有明显漏标。这一步花不了太多时间,但能避免后面整个训练过程被错误标注带着走。

在规模上,我的经验是每个类至少要有300到500张有效图。少于此数量,模型很容易过拟合,泛化到新工况基本没有保障。当然,如果某些类天然稀少,可以先通过数据增强硬凑,但这只是缓兵之计,最终要回到补充真实样本上。

2.2 先把六类缺陷的样子和分布摸清楚

我把这版数据里的六类缺陷整理成了一张特征表,方便后面对照分析。

缺陷类别常见成因形态特征
裂纹轧制应力、材质不均细长线状,方向比较规律,有时带分叉
夹杂非金属夹杂物暴露斑点或小块状,颜色和基体差别明显
麻点表面腐蚀或氧化密集小坑,面积小、边缘不规则
氧化皮高温氧化不规则片状,颜色发暗,边缘模糊
轧制氧化铁皮轧制过程中压入鳞片状,颜色偏黑蓝,有一定厚度感
划伤机械摩擦直线亮痕,宽度小、长度长

读这批数据时,带着一个任务去看:哪两类最容易混。我观察下来,氧化皮和麻点经常混,因为都表现为暗色区域;划伤和裂纹也容易混,因为都是长条形。搞清楚这个,后面再做类别权重和增强策略时就有依据。

另一个容易被忽视的工作是统计分析缺陷框的尺寸分布。写个小脚本,统计所有标注框的宽高像素值,看看中位数是多少、最小的是多少。如果大量缺陷框在40像素以下,那输入尺寸就要认真考虑,640可能不够。

2.3 清洗数据比增强数据更重要

我见过不少朋友一上来就把增强拉满,各种翻转、旋转、加噪声,结果训练出来mAP挺高,一测真实场景就垮。原因很简单:原始数据本身是脏的,增强只会把脏信息放大。

我清洗数据时主要有几个操作:剔除重复图像,用文件的MD5值去重就能干;剔除标错类别的图片,比如把麻点标成氧化皮;剔除严重过曝或严重欠曝的样本,这类图即使模型训出来,在真实场景里也是负收益;对标注框做合法性检查,确保xmin小于xmax、ymin小于ymax,且都落在图像范围内。

清洗完以后,我宁愿数据集从1200张变成900张,也不愿用那300张脏数据。后来实际训练结果证明这个决策是对的,洗过的900张训练效果明显好于没洗的1200张。

3. 数据集处理的硬核细节

3.1 从XML标注转到YOLO txt:一条脚本搞定

很多标注工具导出的是XML或JSON格式,记录的是目标的左上角和右下角像素坐标。YOLO训练需要的txt不是这种格式,它要求一行一个目标:类别id、中心点x坐标、中心点y坐标、宽、高,并且全部归一化到0到1之间。

我写了一个转换脚本,把常见的XML标注转成YOLO格式:

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') W = float(size.find('width').text) H = float(size.find('height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_names: continue box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) assert xmax > xmin and ymax > ymin, f"invalid box in {xml_path}" cx = (xmin + xmax) / 2.0 / W cy = (ymin + ymax) / 2.0 / H bw = (xmax - xmin) / W bh = (ymax - ymin) / H lines.append(f"{class_names.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") img_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' with open(os.path.join(out_dir, img_name), 'w') as f: f.write('\n'.join(lines)) # 示例用法 class_names = ['crazing', 'inclusion', 'patches', 'pitted_surface', 'rolled_in_scale', 'scratches'] os.makedirs('labels/train', exist_ok=True) for xml_file in os.listdir('xmls'): if xml_file.endswith('.xml'): voc_to_yolo(os.path.join('xmls', xml_file), 'labels/train', class_names)

这里有两个非常容易踩的坑。第一个是类别id必须从0开始,不能从1开始,否则训练时类别数对不上。第二个是归一化必须除以图像的原始宽高,而不是除以某次resize之后的宽高。我当时就是忘了除以原图宽高,只算了相对坐标差值,结果训练loss能降,但输出的框位置完全不对。

3.2 训练集、验证集、测试集怎么切分

切分看似简单,实际上很讲究。最忌讳的做法是直接把所有文件路径丢进一个随机函数,然后按比例切三份。这样很容易让同一现场拍摄的连续帧同时出现在训练集和验证集里,模型相当于提前见识过验证集内容,最后指标虚高,一到新场景就现原形。

我通常按序列分组切分。比如现场图是一卷钢一个文件夹,或者一批图有一个共同的前缀,那就要保证同一来源的图只出现在一个集合里。先对样本做分组,再按组随机分配到训练集、验证集和测试集,比例控制在85%、10%、5%左右。测试集一旦划定,在整个迭代过程中尽量不要动,否则你就是在对着测试集调参,指标会失真。

代码上可以用一个简单的分配逻辑,按文件名前缀分组,再对组列表打乱,然后把组依次填入三个集合。虽然多写几行代码,但换来的是可靠指标,非常值得。

3.3 数据增强:哪些对缺陷检测真有用

YOLOv8内置了一系列增强手段,mosaic、平移、缩放、翻转、HSV色域变换都有。默认配置已经比较合理,但针对钢材表面这种特殊数据,我做了两处关键调整。

第一是HSV增强强度要收一点。钢板颜色本身是识别氧化皮、麻点的重要线索,如果把色相、饱和度调得太猛,氧化皮的颜色就可能变成类似塑料反光的颜色,反而加大误检。我用的是hsv_h=0.01、hsv_s=0.5、hsv_v=0.3,效果比默认值更贴合当前数据。第二是翻转方向要谨慎。如果产线相机固定,钢板从左往右走,那么左右翻转生成的图像在现实中根本不会出现,这时候把fliplr设成0或很小值更合理。

mosaic增强对多数目标检测任务都有提升,但在小缺陷很多的数据集上要控制力度。mosaic会把多张小图拼成一张大图,缺陷能被进一步缩小,原来就小的麻点可能直接缩到无法辨认。我建议类别分布比较均衡时用默认mosaic=1.0,如果小目标占主导,尝试降到0.5以下。cutout这种挖空增强我也试过,在钢材数据上不但没用,还会把真实缺陷挖掉,不建议使用。

3.4 dataset.yaml和目录结构

YOLOv8训练需要一份数据集配置文件,指定图像和标签的路径以及类别名。我用的是这样的目录结构:

steel/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/

对应的dataset.yaml如下:

path: ./steel train: images/train val: images/val test: images/test names: 0: crazing 1: inclusion 2: patches 3: pitted_surface 4: rolled_in_scale 5: scratches

这个文件有两点必须注意。一个是names的顺序必须和txt标注里的数字id严格对应,一旦开始训练就不能随便改,改完整个模型输出对不上。另一个是path建议用绝对路径或者相对当前命令行的路径,别用会在不同机器上变化的路径,不然换一台机器训练时容易排查半天。

4. 训练流程与参数调优实战

4.1 环境准备与基础训练

我的环境是Python 3.9、PyTorch 2.1、CUDA 12.1,安装YOLOv8直接用pip就行:

conda create -n steel python=3.9 conda activate steel pip install ultralytics torch torchvision opencv-python

基础训练命令看起来很简单:

yolo detect train data=steel.yaml model=yolov8s.pt epochs=300 imgsz=640 batch=16 device=0

但这行命令背后有几个参数要理解清楚。model=yolov8s.pt是预训练权重,用它在自己的数据上微调,收敛比从零训练快得多。imgsz=640是训练时输入网络的尺寸,如果缺陷框的中位数尺寸在40像素以下,建议直接上960。batch=16对8到12GB显存的显卡比较合适,想算自己显存够不够可以大概估一下:16张640x640x3的输入图像本身占内存才约80MB,但梯度、中间特征、优化器状态会把这个数放大许多倍,实测下来16的批量需要8GB以上显存。

我建议第一次迭代先用yolov8s,不要一上来就上l或者x。模型越大,对数据质量的要求越高,在数据还没洗干净的阶段,大模型只会更快地记住噪声。

4.2 超参数选择思路

训练参数里,我重点调整的是epochs、patience和lr0。YOLOv8默认epochs是100,但对工业缺陷这种需要精雕细琢的任务,100轮往往不够,我直接设成300。patience设为50,意思是验证集指标连续50轮不提升就自动停止,既省时间又防过拟合。

学习率lr0默认是0.01,如果训练日志显示loss一开始就剧烈震荡,就降到0.001重新启动。动量momentum保持默认即可,不需要动。还有一个值得关注的参数是close_mosaic,YOLOv8会在训练最后10轮自动关闭mosaic增强,让模型在更接近真实分布的数据上做最后精修,这个机制对小目标缺陷很友好,默认打开就好。

4.3 训练日志怎么看

训练过程会生成results.png和weights目录,里面放着best.pt和last.pt。很多人只看最后一个结果,这是不对的。best.pt是验证集mAP最高的权重,last.pt是最后一轮的结果,两者差距有时候很大。我习惯先看train/loss和val/loss两条曲线:如果train/loss一直降但val/loss涨,就是过拟合信号,可以把epochs降下来或者增大增强。如果val/loss一直在震荡不收敛,优先检查数据切分和标注。

验证集指标里,mAP50反应的是粗略定位质量,mAP50-95反应的是框和真实框的重合度要求。钢材缺陷这种业务,我实际更看重mAP50和每类recall,因为现场漏检比框不准更致命。

4.4 显存不足和训练太慢

训练中经常会遇到显存爆掉的问题,解决方案优先级我按这个顺序来:先把batch从16降到8,这样一般能救回来;如果还不够,把imgsz从640降到512;再不行换yolov8n。注意YOLOv8默认开了amp混合精度,这个能省不少显存,别莫名其妙关掉。

训练太慢的话,最大的瓶颈往往是CPU在做数据增强,尤其是mosaic。可以把workers从默认的8升到16,如果CPU核心多的话会有明显改善。硬盘IO也有影响,强烈建议把数据集放到NVMe固态硬盘上,而不是机械盘。

5. 评估指标与错误分析

5.1 别只报一个mAP

第一轮训练跑完之后,我拿到一份看起来不错的mAP数据,但真正有用的信息藏在per-class指标里。当时我的验证集整体mAP50到了0.78,看起来能接受,但拆开看后发现裂纹这一类的AP50只有0.42,recall更是低到0.55,大量裂纹被漏成背景。这种情况下整体mAP被其他容易分类的缺陷抬高了,直接上线肯定会出事。

所以我强烈建议,每次评估都输出per-class表格和混淆矩阵。YOLOv8验证时直接会生成这些结果,在runs/detect/val目录下可以找到confusion_matrix.png和results.csv。我习惯把验证结果按类别整理成一张表,比如这个格式:

类别AP50Recall
裂纹0.620.58
夹杂0.950.92
麻点0.880.85
氧化皮0.840.80
轧制氧化铁皮0.780.74
划伤0.820.78

这是示例数据,但这类表才是真正能指导迭代的东西。AP低的那一类,就是要优先补数据、修标注、调参数的方向。

5.2 漏检率比mAP更值得关注

我把漏检样本单独抽出来,建了一个文件夹,每张图按失败原因分类。看下来发现漏检主要集中在这几种情况:缺陷尺寸太小,经过640缩放后辨识度急剧下降;缺陷对比度低,和周围钢板表面的灰度差太小;缺陷边缘模糊,标注框本身就不确定。

针对小目标漏检,我先试过把imgsz从640提到960,效果有提升但显存压力变大。后来又改成滑窗推理:把大图切成若干小图,分别送进模型推理,再把结果合并回原坐标。这一招在缺陷尺寸极小的情况下非常有效,代价是推理时间增加数倍。折中方案是只对怀疑区域做滑窗,或者用更大模型结合更高分辨率。如果业务允许,优先采集更多近景图像,从数据源头解决问题。

5.3 第二次迭代怎么改

第一次迭代后不要急着乱调参数。我给自己定了一个规则:每次迭代只改一个变量。先补正好清洗过的数据,不动增强;再单独调输入尺寸;再试增强系数;最后才是换模型体积。实际做了两轮之后,我发现提升最大的一步不是从s换到l,而是把标注中的错误框修好,同时把输入尺寸从640提到960。

这样做还有一个好处:可以回滚。如果某个改动让指标下降,立刻知道是哪个变量导致的,不用把整个训练配置推倒重来。

6. 推理部署与工程化落地

6.1 导出ONNX并脱离训练环境推理

训练结束后,部署端往往不装PyTorch,直接用ONNX模型会更轻量。导出命令如下:

yolo export model=best.pt format=onnx imgsz=640 dynamic=True opset=12

dynamic=True表示输入尺寸可以是动态的,方便在推理时传入任意分辨率。但要注意,如果后面要转TensorRT,动态尺寸会增加转换复杂度,固定尺寸会更稳。我在这版项目里先用ONNX Runtime跑,固定成640x640,逻辑更简单。

ONNX推理的关键是预处理必须和训练时完全一致。我用的核心流程是:读图、letterbox缩放、RGB通道转换、归一化、转CHW送入模型、解码输出、画框。其中最容易出错的是letterbox。直接resize会把图片横向拉伸,检测框坐标跟着偏,所以在后处理时要把letterbox的缩放比例和填充偏移记下来,还原到原图坐标。

6.2 部署环境最容易出问题的三个点

部署中最常遇到的问题我捋了三个。第一是通道顺序。训练时用的是RGB,OpenCV默认读进来是BGR,如果忘了转,检测效果会明显下降,而且不会报错。第二是NMS参数。推理时置信度阈值如果按默认0.25来,很多小缺陷会被过滤掉,我在钢材缺陷场景里通常把置信度阈值调到0.1到0.15,但这样误检会增加,得用NMS的IoU阈值来控制重叠框。第三是输入归一化。YOLOv8官方推理时会对图像除以255,如果在自己的推理脚本里忘了这一步,模型输出基本等于废的。

我给自己写了个推理小脚本,用OpenCV和ONNX Runtime跑单张图,整体流程大约几十行代码。实测下来CPU推理一张640x640的图大概在几百毫秒,GPU上可以到几十毫秒,基本能满足产线慢速分检的需求。

6.3 与产线场景对接的注意事项

部署后还要面对真实环境的变量。相机一旦装好,焦距和安装距离就不要随便动,否则目标尺度变化会直接影响模型表现。光照是最核心的干扰因素,钢厂现场照明变化剧烈,模型的误检率会跟着变。我的做法是在不同时段、不同灯光下各采集一批负样本,专门测试模型有没有把阴影和反光当成缺陷。

还有输出坐标的处理。模型输出的是归一化坐标,必须乘回原图宽高才能给下游设备用。如果下游机械臂要根据缺陷坐标做动作,那坐标精度就非常关键,此时用mAP50-95这类指标去衡量会更贴合业务。

7. 踩坑实录与常见问题速查

7.1 我踩过的坑(按严重程度排)

第一个大坑是标注格式转换时把xmax和xmin的顺序搞反了。训练过程完全不报错,loss也能下降,但模型输出的框全部偏到图像边缘。排查了很久才发现是转换脚本里的赋值出错。所以转换完成后一定要做可视化检查,随机抽几十张图,把标注框画出来人工看一眼。

第二个坑是类别id和names顺序对不上。我一开始把names的第一个类当成1号,实际上YOLO从0号开始,结果训练日志里分类loss一直不收敛。后来我把txt文件里的id和data.yaml里的顺序重新对照了一遍才解决。

第三个坑是数据增强过猛。我为了追求“泛化能力”,把hsv_v调到了0.8,结果钢板表面颜色被改得面目全非,模型学到的是错误的颜色特征,检测率直线下降。后来我才意识到,工业缺陷检测里很多线索恰恰来自颜色和纹理,增强不是越多越好。

第四个坑是评估集切分没有按序列隔开。最开始直接随机切分,训练集和验证集里出现了同一卷钢的连续帧,验证mAP虚高到0.9,但换到新样本直接掉到0.65。这让我彻底记住了:分组切分比随机切分靠谱得多。

7.2 常见问题速查表

问题现象可能原因解决办法
train/loss降但val/loss不降过拟合、标注噪声大清洗数据、增强、增大训练数据、提前早停
检测框整体偏移letterbox后处理错误或坐标未还原核对缩放比和填充偏移,按原图尺寸还原坐标
小缺陷大量漏检输入分辨率不够、置信度阈值过高提高imgsz、滑窗推理、降低置信度阈值
两类缺陷互相误检类别特征重叠、样本不足补样本、减少颠覆性增强、做一个专业分类器
模型在真实环境误检多训练数据背景单一、新场景光照不同补边界样本、加入真实负样本、降低增强里的颜色变换
导出ONNX后输出异常NMS已内嵌、输入预处理不一致重新核对归一化和通道顺序,必要时在外部做NMS
训练速度极慢数据增强CPU瓶颈、硬盘IO慢调高workers、数据放固态盘、适当减小imgsz

7.3 写在最后的一点体会

跑完整个流程之后,我最深的感受是:工业缺陷检测项目里,模型能力的天花板其实很低,数据质量和工作方法才是决定成败的部分。每一次指标不达标,先不要怀疑模型选型,先回去看数据有没有脏、切分有没有问题、指标有没有看对。把数据吃透,再谈模型调优。

另外就是每次迭代只改一个变量,把中间模型全部保存下来。我现在回头看,真正让效果变好的都是那些看起来最笨的步骤:人工核对标注、分析漏检图、按序列切分数据。这些步骤不能省,也省不得。

最后再分享一个小经验:如果条件允许,训练前先用YOLOv8自带的预训练模型跑一遍公开的钢材表面小数据集,看看整体流程通不通,再切到自己的数据。先把流水线跑通,再追求精度,整个过程会顺很多。

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

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

立即咨询