☰
焊接缺陷检测实战:从YOLOv8训练到TensorRT部署全流程
2026/10/20 3:24:39 网站建设 项目流程

去年有回我拿着刚做出来的焊接缺陷检测模型去某车间试机,现场一测,验证集上97%的召回率直接掉到83%。原因倒不复杂——测试集里是干净的实验室样件,生产线上却是反光、飞溅、油污、光照变化全凑齐了。那会儿我才意识到,做这套基于深度学习的焊接缺陷检测系统,难的不是写模型代码,而是把数据、训练、部署这一整条链路扎扎实实走通。

这篇文章就把我实际做这套系统的完整过程拆开来讲,从任务定义、方案选型、数据标注,到模型训练、调参、TensorRT/ONNX部署和GUI界面设计,全部用Python实现。内容包括我能直接给出来的代码片段、参数配置、踩过的坑和判断标准,适合正在做机器视觉课题的学生、工厂里的算法工程师,以及想自己搭一套质检Demo的开发者。你不需要有很强的深度学习理论背景,但最好先跑过YOLO这类基础模型的训练流程。

1. 焊接缺陷检测到底难在哪

1.1 传统视觉方案为什么搞不定

焊接缺陷检测并不是一个新鲜话题。在深度学习火起来之前,工厂里常用的手段无非是阈值分割、边缘检测、形态学滤波、手工特征加SVM这些。我在项目初期也尝试过用OpenCV做一轮预处理加规则判断,效果只能说“看情况能吃”。

难就难在工业现场的图像环境极度不干净。焊道表面经常有高反光、飞溅的焊渣、氧化色、不规则纹理,甚至同一个焊件换个角度拍出来,灰度分布都差一大截。传统算法依赖的人工设计特征往往是针对特定光照、特定角度、特定焊件设计的,换一条产线就要重新调一遍阈值和参数,而且一旦背景复杂,误检就压不住。

更麻烦的是,很多缺陷本身就是低对比度的,比如细微裂纹。在图像上它只是一条若有若无的暗线,连通域分析和Canny边缘检测很容易把它当成正常纹理放过去,或者反过来把打磨痕迹当裂纹报出来。这种场景下,靠“写规则”来覆盖所有情况,基本是死路。

1.2 深度学习模型的优势与边界

深度学习检测模型走的是另一条路:给定一批标注好的“正常/缺陷”图像,模型自己去学该看什么特征。你不需要告诉它“裂纹是暗的、细长的、方向随机的”,它会在训练过程中自动组合出适用于该数据分布的判别模式。这带来了两个直接的好处:一是省去大量人工设计特征的时间;二是对复杂背景下相似纹理的容忍度明显好于传统方法。

但这个优势有它的边界。深度模型不擅长做“零样本识别”,它只见过你喂给它的数据形态。这是整个项目里最需要提前想明白的事情:深度模型能做的是把“正常”和“缺陷”的边界学出来,但它学不会“现场和数据集不一样”时的自动迁移。所以数据采集、数据规范、现场环境控制,做得越到位,模型在现场就越稳。我后面会说,这不是一句空话,而是决定项目成败的关键。

2. 整体方案选型:分类、检测还是分割

2.1 先定任务:三种路线对比

拿到“焊接缺陷检测”这个需求之后,第一步不是急着拉模型,而是先问一个问题:你要的是“这张图有没有问题”,还是“问题在哪个位置”,还是“缺陷的轮廓具体多大”。不同的答案,对应的技术路线完全不同。

方案输出形式优点缺点适用场景
图像分类整图正常/缺陷最简单,数据好标,训练快无法定位缺陷位置,不能做尺寸测量只做是否有缺陷的快速筛查
目标检测缺陷的矩形边界框能定位、能数个数,YOLO系很成熟对细长裂纹框不准,框会包含背景干扰气孔、夹渣、咬边等离散缺陷
语义分割像素级缺陷区域最精确,能测量长度、面积标注成本高,训练难度大细微裂纹、需要精细化评估的场景

我刚开始做的时候,领导给的需求是“帮产线把明显缺陷挑出来”,那就先从目标检测入手。后来发现有些裂纹细到检测框会把焊道纹理一大片都包进去,才又补了一个分割模块做细检。建议你根据自身的质检需求来选,不要一上来就追求“最先进”。

2.2 我最终选的结构

我的最终方案是:主干用YOLOv8做缺陷定位,兼顾速度和精度;另训练一个U-Net变体专门处理裂纹类缺陷的像素级分割。推理时先跑检测,筛出图像中的缺陷候选区,再对可疑区域跑分割确认。这样整个系统在保持实时性的同时,还能输出缺陷的长宽、面积等量化信息,方便后续做质量评级。

选择YOLOv8而不是更早的版本,主要看中两点:一是它的训练工程化程度高,数据增强、损失函数、Anchor机制都封装好了;二是官方提供清晰的导出路径,可以直接烧ONNX、TensorRT,省去我自行转换的功夫。如果你用的是YOLOv5,思路也完全一样,代码上只是微调。

选U-Net做分割也没有太高深的理由,就是结构简单、显存消耗小、在小数据集上不容易过拟合。后面我还会提到一个关键点:如果焊件种类多,分开训练比硬塞进一个模型更稳。

3. 数据工程:工业场景真正的胜负手

3.1 数据采集:什么样的图才算有效样本

很多项目失败不是因为模型不够好,而是训练数据没有覆盖现场的真实分布。我在这套系统上真正花时间最多的一步就是数据采集和清洗。

先说数量。我手头这个项目最后用到了大约四千多张标注图像,共标注了近一万两千个缺陷实例。这个量级在工业视觉里算中等偏小,但有效果,因为类型覆盖相对充分。我的建议是:每个常见缺陷类型至少要有三百到五百个实例,并且至少要来自二十个不同的焊件或批次。不同来源保证多样性,这在泛化上比单纯堆图重要得多。

再说采集规范。我踩过一个大坑:刚开始让现场人员帮忙拍照,大家都习惯拍“最清晰的正面照”,结果训练出来的模型对角度特别敏感。后来重新定义了采集规则——同一缺陷至少从三个角度拍,距离和光照也要覆盖正常生产中的变化范围。实际上工业相机安装位置是固定的,那么就更需要把相机装好后,采集多个时段、多个焊接参数下的真实图像。现场的光线变化,比实验室里调出来的任何“增强”都管用。

再强调一点,千万别只用好样本。模型学到的“正常”边界取决于你喂了多少正常状态。我专门让现场留了一批“有点脏但没缺陷”的图,比如飞溅、轻微氧化、打磨痕迹,这批图有效压下了不少误检。

3.2 标注规范与一致性

数据标注这事,我不多讲工具操作,但有一个原则想重点说:标注标准必须在动手之前定死。

缺陷框怎么画,不同人画出来的框能差出好远。有人习惯把整个缺陷连同周围泛白区域都框进去,有人只框核心暗区。如果训练数据里“框的大小标准不一”,模型学到的就是混乱特征,mAP上不去还在其次,最难受的是你根本分不清是模型问题还是数据问题。

我当时组织了两个同学一起标,大家先花半天时间对着二十张样图把标注标准过了一遍,包括:

  • 矩形框必须紧贴缺陷最外缘,不留背景边;
  • 一条连续裂纹算一个目标,断成两截但距离很近时按一个目标计;
  • 飞溅和焊渣不算缺陷,除非与气孔连在一起;
  • 任何拿不准的样本,放到“存疑”目录,统一讨论后决定。

这里最好使用在线协作标注工具,里面加一个“存疑”标签,团队可以直接讨论。标注完成后我还做了一次抽样复核,挑出标注分歧大于20%的图像重新统一。这个动作对后续训练的帮助,甚至超过调模型结构。

3.3 数据增强与不均衡样本处理

焊接缺陷有个很典型的不均衡问题:气孔类缺陷又大又明显,一张图里好几个;裂纹类缺陷又小又少,跑遍整个数据集也没几个实例。如果直接训练,模型会偏向学习“气孔长什么样”,而弱化裂纹。针对这一点,我的处理方式是组合拳:

  • 几何增强:水平翻转、小角度旋转、随机裁剪、缩放。但注意,翻转对某些焊件方向敏感,左右翻转一般安全,上下翻转就要看场景了,我们一起分析过,焊接图像通常不存在绝对上下方向,所以也加上了。
  • 色彩增强:亮度变化、对比度变化、HSV扰动、加入高斯噪声。模拟车间不同批次焊件的成像差异。
  • 复制粘贴增强:对稀有类缺陷,把它的像素区域剪切出来,随机贴到正常焊道图像上。这个操作对检测气孔效果非常好,但对裂纹这类纹理型缺陷要小心,直接贴会让背景对不上。我实际测试下来,裂纹类复制粘贴提升不到两个点,还容易出假边界,后来干脆对裂纹使用了原始图加局部对比度拉伸,专门把小目标清晰度提上去。
  • 图像混类增强MixUp和Mosaic,YOLOv8内置了,训练时直接使用即可。

另外,别忘了增强只加在训练集上,验证集和测试集必须保持原始分布。这一点我见过有人反复裁失误,验证集也被增强,测出来的指标虚高,现场一测直接缩水。

4. 模型训练与调参实录

4.1 环境配置与代码结构

先把我这边的环境列出来,方便你参考。整条链路都是Python,主框架用PyTorch,模型部分基于YOLOv8和U-Net。训练硬件是一张24GB显存的GPU,如果你的显存小一些,把batch size或输入分辨率降下来就成,稍微影响收敛速度而已。

项目的核心代码结构大致是这样的:

project/ ├── config/ │ ├── data.yaml # 数据路径和类别定义 │ ├── train_params.yaml # 训练超参数 │ └── infer_config.yaml # 推理后端配置 ├── data/ │ ├── raw_images/ # 原始图像 │ ├── annotations/ # 标注文件 │ ├── train.txt │ ├── val.txt │ └── test.txt ├── models/ │ ├── detector.py # YOLO检测器封装 │ └── segmentor.py # U-Net分割器封装 ├── utils/ │ ├── augmentations.py │ ├── metrics.py │ └── visualization.py ├── train_detector.py ├── train_segmentor.py └── deploy/ ├── export_onnx.py └── run_gui.py

训练检测模型时,数据格式用YOLO的txt格式,每个缺陷目标一行:类别编号 + 中心点x + 中心点y + 宽度w + 高度h,坐标都是相对图像的归一化值。类别定义我只设了四类:气孔、夹渣、咬边、裂纹。不要分得太细,类别太多会引起混淆,类别太少又无法满足质检分级需求,四到六类通常是一个好区间。

data.yaml关键内容如下:

path: /path/to/dataset train: train.txt val: val.txt test: test.txt nc: 4 names: 0: blowhole 1: slag 2: undercut 3: crack

4.2 关键超参数配置

我实际训练检测模型用的超参数如下:

task: detect mode: train model: yolov8s.pt epochs: 300 batch: 16 imgsz: 1280 workers: 8 optimizer: AdamW lr0: 0.002 lrf: 0.01 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 warmup_momentum: 0.8 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 10 translate: 0.1 scale: 0.5 fliplr: 0.5 mosaic: 1.0 mixup: 0.2

值得展开说一下几个参数的选择逻辑:

imgsz设为1280,这是整份配置里最关键且最容易被忽略的一项。焊接缺陷里的裂纹、小气孔往往只有几十个像素宽,如果输入尺寸用默认的640,小目标在下采样后直接就消失了。但我也不建议盲目用更大的分辨率,训练代价和部署帧率都会随之恶化。1280是我测试下来“小目标能看见、显存刚好住、现场推理还能保持30帧以上”的平衡点。

epochs设为300,明显多于常规训练。工业缺陷数据量普遍不大,多训练可以保证在走向过拟合之前曲线充分收敛。配合早停机制,如果验证集损失连续40轮没有下降就自动停,避免无效训练时间。

mosaic保持开启,这个增强能有效增加背景多样性,对焊道这种背景纹理多变的场景非常有利。唯一的风险是mosaic后的图里如果同时混了太多缺陷框,会让模型看到一堆边界不完整的目标,所以我在最后20轮关掉了mosaic和mixup,让模型收敛得更稳。

4.3 训练过程中的判断指标

训练不是等到全部epoch跑完才算完,我习惯每10个epoch就看一次验证集结果。重点看三个量:

  • mAP@0.5:主要反映“框出来且框得差不多”的准确率,适合评估定位能力。
  • mAP@0.5:0.95:对框的精确贴合程度更苛刻,现场质检一般不用强求太高,能到0.55以上就说明框质量不错。
  • F1曲线:在置信度变化时,精确率和召回率的平衡情况。这个曲线对后面设定报警阈值很有用。

我这次训练到约180轮时,mAP@0.5从0.82跳到0.89,后面基本收敛,最终验证集mAP@0.5为0.91。

但这里必须提醒一下:mAP这类指标衡量的是“框位置对不对,类别对不对”,它不能直接告诉你在真实产线上误检率和漏检率到底如何。更贴近业务的做法是单独设定一个置信度阈值(我这边是0.35),然后再统计在这个阈值下的精确率和召回率。因为质检场景里,漏检会放走缺陷品,误检会干扰产线效率,两者代价不一样,需要根据产线实际情况调整阈值。不要下意识就取默认0.25。

4.4 分割模型的补充训练

分割部分我用的是轻量U-Net,输入尺寸和检测一致,1280。损失函数用Dice Loss和二值交叉熵的加权组合,权重大约是0.7比0.3,Dice权重高一点有助于分割小目标。

U-Net的迭代只需约80到120轮,因为特征是每个像素级别的,不需要那么多轮次。训练时配合一个小技巧:对裂纹类样本做ROI裁剪,只取缺陷周围区域作为训练样本,这样模型的重点完全放在局部纹理上,分割精度提升明显。

训练完毕之后,我把分割模型的输出做了后处理:先取概率图的0.5阈值做二值化,再用形态学开运算去掉离散小噪点,最后用连通域分析提取每个裂纹实例的长度和最大宽度。这个输出适合生成质检报告。

5. 系统部署:从模型到可用的检测界面

5.1 用ONNX导出并加速

模型训练完毕之后,不能在PyTorch环境里直接跑推理,效率太低。我把检测模型导出为ONNX格式,再加载到ONNX Runtime里推理。

导出用官方函数,注意几个关键选项:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") model.export( format="onnx", imgsz=1280, dynamic=True, #动态batch simplify=True, #简化计算图 opset=12 )

导出的onnx文件在CPU机器上大约几十MB。实际推理时,对于1280x1280的输入,单张推理时间在GPU上约25到35ms,CPU上约180到220ms。如果你的现场工控机没有GPU,建议把输入降到1024,并把batch设为1,推理时间可以压到120ms左右,取舍就看你对小目标漏检的容忍度了。

ONNX Runtime推理代码的关键流程如下:

import onnxruntime as ort import cv2 import numpy as np session = ort.InferenceSession( "best.onnx", providers=["CUDAExecutionProvider", "CPUExecutionProvider"] ) # 预处理为模型输入格式 def preprocess(image, input_size=1280): h, w = image.shape[:2] scale = min(input_size / h, input_size / w) new_h, new_w = int(h * scale), int(w * scale) resized = cv2.resize(image, (new_w, new_h)) canvas = np.full((input_size, input_size, 3), 114, dtype=np.uint8) canvas[:new_h, :new_w] = resized # 归一化 0-1 blob = canvas.astype(np.float32) / 255.0 blob = np.transpose(blob, (2, 0, 1))[None] return blob, scale

5.2 推理流程与后处理

ONNX模型输出的是一个张量,占位形状是[1, 84, N]之类,这里不能直接拿来做业务判断,还需要经过坐标解码和NMS过滤。

我写了一套相对完整的后处理流程:

def postprocess(preds, conf_thres=0.35, iou_thres=0.45): # preds shape: [1, 4+nc, 8400*scale],需要转置解析 preds = np.transpose(preds, (0, 2, 1)) # [1, N, 84] boxes = preds[..., :4] scores = preds[..., 4:] class_ids = np.argmax(scores, axis=-1) class_scores = np.max(scores, axis=-1) # 过滤低置信度 keep = class_scores >= conf_thres boxes, class_ids, class_scores = ( boxes[keep], class_ids[keep], class_scores[keep] ) if len(boxes) == 0: return [] # 将中心点xywh转为xyxy,然后做NMS xyxy_boxes = np.concatenate([ boxes[:, :2] - boxes[:, 2:] / 2, boxes[:, :2] + boxes[:, 2:] / 2 ], axis=-1) # NMS实现可以用opencv的或自己写 indices = cv2.dnn.NMSBoxes( xyxy_boxes.tolist(), class_scores.tolist(), conf_thres, iou_thres ) results = [] for idx in indices: results.append({ "box": xyxy_boxes[idx], "class_id": int(class_ids[idx]), "score": float(class_scores[idx]) }) return results

这里的conf_thres就是前面说的现场判定阈值。我在界面程序里把这个参数暴露出来,一线调试员可以直接调整,不用改代码重编。

5.3 GUI界面与业务整合

为了让车间里的操作人员能顺利使用,我基于PySide2写了一个简单的桌面GUI。界面布局并不复杂,就三块:

  • 左侧显示原始图像;
  • 中间叠加检测框和分割掩膜结果;
  • 右侧实时显示缺陷类型、置信度、数量、面积等,并支持一键导出CSV质检报告。

关键代码框架如下:

from PySide2.QtWidgets import (QWidget, QLabel, QPushButton, QFileDialog, QVBoxLayout, QHBoxLayout) from PySide2.QtGui import QPixmap, QImage import cv2 import numpy as np class DetectionWindow(QWidget): def __init__(self, detector, segmentor=None): super().__init__() self.detector = detector self.segmentor = segmentor self.init_ui() def init_ui(self): self.image_label = QLabel("请选择图片") self.result_label = QLabel("检测结果待显示") self.btn_open = QPushButton("打开图像") self.btn_open.clicked.connect(self.open_image) layout = QVBoxLayout() layout.addWidget(self.btn_open) layout.addWidget(self.image_label) layout.addWidget(self.result_label) self.setLayout(layout) def open_image(self): path, _ = QFileDialog.getOpenFileName( self, "选择图像", "", "Image Files (*.png *.jpg *.bmp)" ) if not path: return image = cv2.imread(path) vis_image = self.detector.detect(image) # 把BGR转为RGB并显示到QLabel rgb_image = cv2.cvtColor(vis_image, cv2.COLOR_BGR2RGB) height, width, channel = rgb_image.shape bytes_per_line = 3 * width qimg = QImage(rgb_image.data, width, height, bytes_per_line, QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(qimg))

GUI不是核心难点,但有一个细节值得注意:现场电脑性能参差不齐,如果推理一次要三秒以上,操作员就会疲劳,反而更容易漏看。我建议在GUI里把检测结果和历史记录列表结合展示,同时显示最近100张图的统计曲线,这样质量管理员能看到趋势,而不只是单张结果。

整条系统的代码量集中在图像预处理和后处理逻辑上。真正业务代码不多,难点都在“怎么把模型的结果翻译成可执行的质量判断”。

6. 常见问题与排查心得

6.1 现场常见的坑

我把实际运行过程中遇到的高频问题整理出来了,每一类我都亲自踩过:

现象根本原因解决办法
现场误检暴增训练数据里没有覆盖现场反光、飞溅样本补充采集负样本;调高置信度阈值;必要时加反光区域过滤预处理
细小裂纹检不出输入分辨率太低,小目标下采样后丢失提高imgsz;对裂纹训练分割模型,用ROI方式辅助检测
同类缺陷不同焊件上表现差异大数据划分时按“图像随机分割”,同一焊件多个角度图被分到训练和验证集,造成指标虚高必须按焊件ID划分数据,同一焊件的所有图像要么全在训练要么全在验证
模型对微弱亮度变化敏感训练样本中光照变化不够采集时覆盖不同时刻的车间光照;训练时增强亮度抖动和随机gamma校正
推理速度慢ONNX模型没有配置GPU执行提供者检查providers列表,确保CUDAExecutionProvider放在最前
分割掩膜边缘粗糙二值化阈值固定,没有做后处理概率图先做OTSU再配合形态学闭运算

6.2 一个重要教训:数据划分泄漏

这一点我单拎出来说,因为它太容易被忽略了。最初做数据划分时,我用train_test_split按图像文件随机切分,验证集mAP一直虚高到0.93,但现场可靠性不够。后来排查才发现,同一块焊件被拍了多张不同角度的图像,其中一部分进了训练集,一部分进了验证集,模型实际“见过”验证集内容。修正为按焊件ID划分后,验证指标从0.93回落到0.91,好像数字变化不大,但现场表现提升明显。

这种问题叫数据泄漏,在工业视觉项目里比模型复杂度更影响最终效果。无论用什么模型,数据划分逻辑都必须和业务无关,前提是“任何一个造成信息外泄的路径都要堵死”。

6.3 训练到部署阶段的实用建议

最后写几条我个人认为对这类项目最有价值的经验,不一定写在哪篇文档里,但很管用:

先跑通再调优。我见过很多初学者一上来就研究最新的检测头、注意力机制,花了一个月调结构,结果数据还没整理完。不要这么干——先用最普通的YOLOv8s + 默认参数训练一轮,把全流程从标注到GUI跑通,再回头优化。路线通不通,比性能高不高更重要。

标注一致性优于标注数量。五百个标准统一的样本,效果经常好过两千个标准混乱的样本。团队标注之前一定要先定规则,标完之后抽检一致性。

置信度阈值按代价定。不要把模型的默认置信度直接拿来做产线判定。需要自己拿一批现场图,计算在不同阈值下的误检数和漏检数,两个方向对产线损失不一样,选一个能接受的平衡点。

留一批“黑暗样本”反复回归。我每优化一版模型,都会拿之前所有误检和漏检图片重新跑一遍,确保优化没有破坏旧场景。这个操作比盯着mAP涨跌更实在。

现场记录比算法调参更值钱。车间里焊工换了参数、母材批次变了、相机角度动了,缺陷形态都会跟着变。一定要让现场人员养成保存异常图的习惯,图像积累起来了,模型迭代才有的放矢。数据是这种系统真正的护城河。

这套系统断断续续做了三个多月,最深的体会不是模型从哪个版本换到哪个版本,而是真正吃透了工业数据特有的脏和乱。回看最初几天跑出来的结果,和我最后交付时最大的差别,其实在数据组织和调试心态上。如果你也在做类似的检测系统,我建议一开始就把“数据版本管理”和“按缺陷类型的独立评估”这两件事做好,后面会省下大把时间。

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

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

立即咨询