ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于深度学习与YOLO的焊接缺陷检测系统实践

基于深度学习与YOLO的焊接缺陷检测系统实践

干过质检或者跟焊接产线打过交道的人,应该都懂这个场景:老师傅戴着面罩拿肉眼查焊缝,赶上夜班或者连续查几百个工件,眼睛都看花了,漏检照样防不住。后面就算上了机器视觉,传统算法面对弧光干扰、飞溅、工件表面的油污反光,参数调到头也还是误报不断。这几年基于深度学习的焊接缺陷检测系统成了热点,原因很简单——它不是在“写规则”去套所有缺陷,而是让模型自己从大量标注好的图像里学出“缺陷长什么样”,泛化能力比传统算法规整得多。我自己也完整做过一个基于Python的焊接缺陷检测项目,从数据准备到模型训练再到部署落地,踩了不少坑,这篇就把整个系统的研究思路和代码实现从头到尾拆开讲清楚,给准备入坑的朋友一条能直接抄的路线。

这套系统解决什么问题?核心就是替代人工质检中的“眼睛疲劳和标准不统一”,把检测任务自动化。具体能做的包括:识别焊缝中的气孔、夹渣、未熔合、裂纹、咬边这些常见缺陷类型,输出缺陷位置和类别,在产线上联动报警或控制机械臂分拣。适合谁看?一是做机器视觉落地的工程师,二是搞智能制造课题研究的学生,三是想评估深度学习方案能不能用在自己工厂产线上的技术管理者。文里的代码基于Python和PyTorch实现,模型以目标检测作为主路线,全文不绑定任何收费商业软件,全部用开源工具链,照着走基本能复现。

1. 焊接缺陷检测的需求拆解与方案选型

1.1 缺陷类型与检测难点到底是什么

焊点或者焊缝的缺陷,在工业标准里有一套明确的分类,最常见的几类是气孔、夹渣、未熔合、裂纹、咬边和烧穿。气孔是小圆坑,夹渣是不规则暗色块,未熔合和裂纹都是细线状但方向不同,咬边在焊缝边缘呈沟槽状。这些缺陷形态差异大,直接导致一个问题:单一算法很难同时兼顾它们,因为有的体积小、有的对比度极低、有的和正常焊纹十分相似。

检测难点的第一层是图像采集环境。焊接现场有弧光闪烁,有飞溅颗粒,工件表面有氧化色和反光,相机的曝光稍有不稳,一张图中的缺陷特征就可能被高光淹没。第二层是特征本身——未熔合和裂纹都是长条形,但裂纹往往只有几个像素宽,在1080P图像里占比不足0.1%,这种小目标对深度学习模型的分辨率设计和anchor尺寸设定都非常敏感。第三层是样本不均衡,产线正常品远多于缺陷品,缺陷里气孔又远多于裂纹,如果直接拿原数据训练,模型会学成“只会报气孔”的偏科生。

所以在项目启动之前,我建议大家先花一周时间做数据盘点:收集多个批次、不同光照条件的图像,按缺陷类别统计数量分布,筛掉模糊和标注争议大的图。这一步决定后面所有工作的上限,数据质量不达标,模型再先进也白搭。

1.2 为什么是深度学习而不是传统机器视觉

传统方案做焊接缺陷检测,通常走的是图像预处理加特征提取的路子:先滤波去噪,再用边缘检测、灰度阈值分割或形态学操作把缺陷区域圈出来,最后用支持向量机或者随机森林做分类。这套方案在实验室干净样本上表现尚可,一上线就露馅:不同钢材反光度不同,焊接电流电压一变,图像灰度分布就全变了,阈值跟着失效,调试周期长还锁死在某条产线上。

深度学习方案的核心优势是把“特征设计”这件事交给了网络。模型在训练过程中自动从图像里学到有区分度的特征抽象层级,浅层关注边缘纹理,深层关注语义形状,对光照变化、工件批次差异的鲁棒性明显更强。尤其在焊接这种强干扰场景下,卷积网络对局部特征的组合表达能力远超手工特征。

但深度学习也不是万能药。它吃数据、吃算力,而且对标注质量要求高;推理部署环节比传统算法复杂,需要做模型压缩和格式转换。实操里我见过不少团队跳过需求分析直接租显卡开训,最后模型指标挺漂亮,却因为推理延迟压不到产线节拍而无法落地。所以选型结论是:如果缺陷类型固定、产线环境稳定、样本量少,传统方案够用;如果你要面对多样化产品型号和复杂干扰,深度学习是更优解,但必须按系统工程来做。

2. 整体架构设计与模型选型逻辑

2.1 检测任务定义:分类、定位还是分割

动手写代码之前,先把任务类型定清楚。焊接缺陷检测有三种常见的任务定义方式:图像级分类、目标检测、实例分割。图像级分类只回答“这张图上有没有缺陷”,不告诉你位置,适合做粗筛闸口,比如大批量首检快速排除正常件。目标检测输出每个缺陷的边界框和类别,是当前产线用得最多的方案,因为品控人员看到框的位置就能去补焊或打磨。实例分割则把缺陷像素精确抠出来,适合对缺陷面积有量化要求的场景,比如计算气孔率、评估裂纹长度,但标注成本和计算开销都高。

我做的这个项目选择目标检测作为主路线,原因很直接:产线需要的是“在哪里、是什么、有多大”,边界框足够,同时分割标注的时间接近检测的三倍,而我们在项目周期内没有那么多标注人力。如果你后续要算缺陷面积占比,可以在检测结果基础上加一个轻量分割头,不用一开始就上完整分割模型,工程上更灵活。

2.2 模型选型:YOLO系列与轻量化取舍

模型选型我从YOLOv5s开始做基线,后来又对比了YOLOv8n和YOLOv8s。选YOLO系列的原因总结下来有三点:一是工程成熟度,数据加载、增强、训练、导出,整条链路的代码都是开箱即用,社区资料多,遇到问题好搜;二是推理速度快,YOLOv5s在单张1080P图上用GPU推理可以跑到10毫秒出头,完全能满足在线实时检测;三是增强策略丰富,YOLO自带Mosaic、MixUp这些数据增强,对样本量有限的焊接缺陷任务来说,变相扩了好几倍数据。

YOLOv5s和YOLOv8s怎么选,我建议看部署硬件。你的边缘设备如果是老型号GPU或者只有CPU,优先考虑YOLOv5s或者YOLOv8n,虽然mAP稍低一点但实时性稳;如果算力充足、要更高的精度上限,YOLOv8s的C2f结构和解耦头对细节特征提取更好,小目标的召回率通常能高出2到3个点。我在实际项目中先用YOLOv5s跑通流程,最后因为现场误检率要求苛刻,换了YOLOv8s,最终mAP50从0.912提到0.934,代价是推理帧率从90FPS降到60FPS,产线仍然够用。

注意:不要一上来就追求大模型。焊接缺陷样本量通常只有几千张,大模型在数据不足时很容易过拟合,训练慢、部署难,精度反而可能不如小模型。先小后大,按需升级才是正路。

2.3 数据增强与训练策略的思路

数据这块我的做法分三部分。第一部分是离线清洗和标注,用LabelImg和X-AnyLabeling这类工具画框,类别按标准分类设定六类:porosity、slag、lack_fusion、crack、undercut、burn_through。第二部分是在线增强,训练时开启Mosaic和随机仿射变换,把不同焊缝图拼在一起训练,让模型见过更多缺陷拼接方式,同时配合随机亮度对比度调整来模拟不同弧光强度下的图像变化。第三部分是类别平衡处理,针对裂纹样本少的问题,我单独给它开了更高的增强倍率,并在loss里增加类别权重,避免模型完全被气孔和夹渣带偏。

训练策略上,我用了两阶段思路:先在公开的焊接缺陷数据集上预训练一轮,再迁移到现场数据上微调。迁移学习的好处是,网络在前一个任务上学会了基本的纹理和边缘特征,微调时只需要适应焊接缺陷的特定形态,收敛速度和最终精度都有明显改善。实测下来,直接冷启动训练要120个epoch才稳定,迁移训练40个epoch就达到同等水平,省下的不是一点半点时间。

3. 核心代码实现与实操过程

3.1 环境搭建与数据集准备

环境的版本搭配很容易踩坑,我这边测试过相对稳妥的一套组合:Python 3.10、PyTorch 2.0.1、CUDA 11.8、Ultralytics YOLOv8.2.0。显卡是单张RTX 3090,24G显存,训练YOLOv8s在1080P图上batch size可以开到16,显存占用约18G。如果你的显卡显存小一些,把batch size降到8,或者把训练图分辨率从1280降到960,效果差距不会太大。

数据集目录结构按YOLO格式组织:

datasets/ ├── weld_defect/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── labels/ │ ├── train/ │ ├── val/ │ └── test/

标注文件每行五个数值,格式是:类别ID、归一化中心x、归一化中心y、归一化宽度、归一化高度。注意这里一定是归一化后的坐标,取值0到1之间,数据格式错了会直接导致训练loss不下降或者边界框错乱。

3.2 训练脚本的关键实现

用Ultralytics封装库训练时,核心代码非常短,但参数配置要用心。我用的训练配置示例如下:

from ultralytics import YOLO model = YOLO("yolov8s.pt") results = model.train( data="weld_defect.yaml", epochs=150, imgsz=1280, batch=16, lr0=0.01, lrf=0.01, mosaic=1.0, mixup=0.2, fliplr=0.5, scale=0.5, patience=20, device=0, workers=8, val=True, project="runs/weld_defect", name="exp1", pretrained=True, )

几个参数的经验解释:imgsz用1280而不是默认的640,是因为裂纹和未熔合这类缺陷宽度只有几十像素,在640下特征几乎被压缩没了;mixup开0.2而不是默认的0.1,略微增强泛化,但开太大缺陷边缘会变得模糊反而难学;patience开20做早停,防止后期过拟合浪费训练时间。数据配置文件weld_defect.yaml里写入类别数量和类别名,路径用绝对路径最稳妥,相对路径有时会因为工作目录不同而报错。

3.3 训练过程监控与评估

训练过程中每5个epoch看一下验证集上的PR曲线和混淆矩阵。我踩过的一个典型坑是:训练到第80个epoch时mAP50还在涨,但mAP50-95开始掉,说明模型开始过拟合训练集的纹理细节而丢失泛化能力。这时不要犹豫,用早停截断,回到第70个epoch的权重做最终模型。

训练完成后用验证集做一次详细评估,打印每类的精确率和召回率:

from ultralytics import YOLO model = YOLO("runs/weld_defect/exp1/weights/best.pt") metrics = model.val(data="weld_defect.yaml", split="val") print(metrics.box.map) # mAP50-95 print(metrics.box.map50) # mAP50 print(metrics.box.mp) # 所有类别平均精确率 print(metrics.box.mr) # 所有类别平均召回率

如果某个类别召回率明显低,比如裂纹类的召回率只有0.6而其他类都在0.9以上,优先检查两个方向:一是标注是否漏标了细小的裂纹框,二是数据增强时裂纹被随机裁剪出画面。我实际遇到的情况是后者,Mosaic拼接导致细裂纹被切了一半,模型学不到完整特征。解决办法是把scale参数调低,并给裂纹类单独制作一批不参与Mosaic的保留数据混合训练。

3.4 推理代码与结果可视化

训练完的模型在推理阶段代码更简单,但输出信息的利用方式值得讲究。我的推理脚本在检测之外还加了一个质量统计模块:

from ultralytics import YOLO import cv2 model = YOLO("runs/weld_defect/exp1/weights/best.pt") img = cv2.imread("test_img/part_023.jpg") results = model.predict(source=img, conf=0.25, iou=0.45, imgsz=1280, device=0) boxes = results[0].boxes cls_names = results[0].names for i in range(len(boxes)): cls_id = int(boxes.cls[i]) conf = float(boxes.conf[i]) xyxy = boxes.xyxy[i].tolist() print(f"缺陷类: {cls_names[cls_id]}, 置信度: {conf:.3f}, 位置: {xyxy}")

conf阈值我建议设在0.25到0.3之间。设得太低,误检增多,现场频繁报警会让操作员直接关掉系统;设得太高,漏检风险上升,万一真有裂纹没报出来,责任就大了。阈值取舍要跟产线质量负责人一起定,不要自己做主。

4. 效果评估、调优方向与部署落地

4.1 指标怎么看:mAP不是万能的

焊接缺陷检测的评估不能只看一个mAP。mAP50反映的是IOU阈值0.5下的平均精度,适合看整体检测效果;mAP50-95则对边界框的拟合格外严格,缺陷检测中这类小目标天然框不准,mAP50-95不高是常态,不必过度焦虑。更要看的是每类缺陷的召回率,尤其在安全相关的检测场景里,漏一个裂纹比多报十个气孔严重得多。

我习惯做一张评估矩阵表,把每个类别的precision和recall列出来,再统计“误检来源”。比如下表是一个典型结果:

缺陷类别样本数PrecisionRecall主要问题
气孔11200.950.93正常焊纹小凹坑误判为气孔
夹渣4600.910.88飞溅颗粒被误判
未熔合1800.820.74细长框定位偏移
裂纹950.700.58小目标漏检严重
咬边2300.880.85与阴影混淆
烧穿1200.940.91强光干扰

这样的表比单一指标有用得多,直接指导调优方向。

4.2 调优优先级:先数据后模型再后处理

很多人一上来就调网络结构,其实效果最明显的永远是数据侧。我在项目里把裂纹样本从95张扩到800张,召回率从0.58直接提到0.76。扩数据可以对原图做小角度旋转、局部放大、加高斯噪声、模拟弧光强曝光,注意不要用翻转把左右结构搞变形,焊缝方向统一会更好。

模型侧容易见效的调整是anchor尺寸。用YOLO自带的自动anchor检测脚本,先统计验证集标注框的真实尺寸分布,如果发现裂纹框的宽度远小于默认anchor,就重新聚类生成新anchor。我跑完后anchor的第三层尺寸从原来的大框级别下探到十几个像素,小目标召回率再涨了两到三个点。后处理侧可以针对单张图上连续多帧检测结果做时序滤波,焊接缺陷如果是静止的,这个处理能把偶发误检压下去不少。

4.3 模型导出与产线部署经验

训练好的PyTorch模型不能直接上产线,我用的部署链路是:导出ONNX格式,再通过推理优化转成TensorRT的engine文件,在GPU上以FP16精度跑。导出的关键代码:

model.export(format="onnx", imgsz=1280, dynamic=False, half=True)

TensorRT转换时注意固定输入尺寸,如果不固定,引擎会对多种分辨率做优化选择,实际推理性能会打折。另外焊接现场的工控机硬件不要省,最低建议是搭载独立显卡,显存8G以上,否则1280分辨率的推理延迟很容易超过50毫秒,跟不上产线节拍。

如果是CPU部署,把模型切成YOLOv8n并量化到INT8,帧率可以恢复到15到20FPS,精度会掉约5%。对焊检场景我的建议是优先保精度,硬件能上GPU就不要省这个钱。

5. 常见问题与排错实录

5.1 检测框偏移严重怎么办

训练完跑验证集,发现缺陷检测框总是偏左或偏上,大概率是标注数据的问题。我在项目初期让两个同学分别标注同一批图片,框的标注习惯不一致:一个框紧贴缺陷边缘,一个框留了很大的白边。模型学到的框尺寸分布就会变得混乱,推理时定位自然不稳定。解决办法是重新统一标注规范,严格规定框必须包含缺陷的全部可见区域,允许外扩不超过5个像素,标注完再跑一次质量抽检。

5.2 loss不收敛该怎么排查

loss曲线平着走不下降是最让人头大的问题。第一步检查数据格式,用脚本读几张图和一个标签文件,画出标注框确认没有错位;第二步检查类别ID是否连续,比如只有1、2、3类而没有0类,模型的类别映射会出问题;第三步检查学习率,l r 初始值调到0.005以下试试;第四步检查显存异常和梯度爆炸,loss突然变成nan多半是数据里混入了损坏图片,逐一排除法最有效率。

5.3 误检多,正常焊纹被当缺陷

这个问题的根源通常是负样本太少了。模型只见过“有缺陷”和“无缺陷”的图,但没学过“看起来像缺陷但实际正常”的干扰纹理。解决思路是单独收集一批只有正常焊纹、划痕、油污的反例图片,作为背景类加入训练。YOLO系列模型本身没有显式的背景类别输出,但你可以通过难负样本挖掘,在验证集上筛出误检严重的图,把它们加入训练集并打上正确标签,两三个轮次后误检率就能明显下降。

5.4 模型在A批次工件上表现好,换B批次就垮了

这是领域漂移问题,非常常见。焊接件的批次不同,材料表面状态会有差异,例如涂层厚度、氧化程度、纹路方向,都会让图像分布发生偏移。对策有三个:一是训练时收集尽可能多批次的图像,不要只用一个月的货;二是推理侧加图像归一化预处理,统一亮度和对比度;三是每隔一段时间用新批次的小样本做主动学习,标注几十张图微调模型,让系统保持和产线同步。

我个人在实际操作中的体会是:深度学习焊接缺陷检测这个项目,七成时间花在数据和工程上,三成花在模型上。模型选型直接从成熟的YOLO系列下手最稳妥,除非你有极强的学术创新需求,否则别去复现花哨的网络结构。踩过几次坑之后,我最后悔的一件事就是没在项目早期把标注规范和评估矩阵定细,后面返工占了大量时间。

最后再分享一个小技巧:训练完不要急着部署,找一条不在训练验证集里的真实焊缝视频,让模型在连续几百帧上跑一遍,把误检和漏检的帧截出来做案例分析。这种在真实时序数据上的检验,比任何指标都更能暴露模型的实际问题。焊接缺陷检测系统的价值最终要落在产线不良品率有没有下降上,而不是落在论文里的图表上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进