先交代一个背景。前段时间在工厂现场蹲了一周一根12米长的钢管焊缝检测靠老师傅拿着强光手电一点点看一天下来眼睛酸得直流泪漏检率还压不下去。回来之后团队商量做一个基于深度学习的焊接缺陷检测系统用Python把整个流程跑通图像采集、缺陷定位、类型识别、现场告警全链路打通。这篇文章就是把我们整个研究过程和代码实现思路整理下来包括数据集怎么处理、模型怎么选、训练时踩了哪些坑、最后怎么部署到产线上给同样在做焊接视觉质检的朋友做个参考。1. 整体技术路线为什么把问题定义成目标检测1.1 焊接缺陷检测难在哪不是表面那么简单焊接缺陷检测本质上是“在复杂背景里找小目标”。焊缝本身有鱼鳞纹、飞溅、氧化色、母材纹理真实缺陷和这些背景特征混在一起加上X射线成像时还会叠加噪声图像质量波动很大。传统的图像处理管线通常先做滤波、边缘检测、阈值分割再用面积、周长、灰度均值等手工特征做分类这种思路在固定工艺参数、固定光照下尚可一旦焊速、电流、材质发生变化特征分布马上漂移算法就得重新调参维护成本很高。深度学习的优势在于不需要手工设计特征模型自己会从数据里学出对缺陷敏感的表示。CNN先学边缘和纹理再逐步组合成“气孔”“裂纹”这类语义概念这个路径和老师傅看片的经验积累过程很像。项目里最需要回答两个问题焊缝上有没有缺陷、缺陷在哪个位置。这两个需求对应到技术定义上就是图像分类加上目标定位综合下来最合理的任务形式是目标检测。1.2 目标检测、分类、分割三条路线怎么选规划路线时其实考虑过三种方案。第一种是当成图像分类把焊缝切成若干小块逐块判断是否有缺陷实现简单但定位粒度太粗缺陷横跨切割边界时容易漏判而且没法给出缺陷尺寸。第二种是语义分割对每个像素分配类别轮廓精细但标注成本高在X射线图像上缺陷边缘经常模糊标注一致性很难保证推理速度也往往不满足产线节拍。第三种就是我们最终采用的目标检测直接输出“类别边界框”既能定位又能分类标注成本介于两者之间部署逻辑最简单。模型家族选择上我以YOLOv5s作为基线做对比实验同批对比了SSD和Faster R-CNN。YOLOv5s在工程生态里足够成熟导出ONNX、TensorRT都有现成方案文档多、坑少适合做工业项目后续如果算力允许可以换YOLOv8或自研轻量结构整体流程不需要大改。选型的原则不是参数排名有多靠前而是模型能不能在产线的有限算力下稳定跑起来。1.3 开发环境与Python技术栈怎么搭整个项目代码用Python实现训练和验证环境是Python 3.8深度学习框架用PyTorch 1.12CUDA 11.7。为什么选PyTorch而不是TensorFlow核心原因是我对中间tensor做调试非常方便打印shape、修改loss逻辑、插入断点观察梯度都很快这对缺陷检测这种需要反复分析输出的场景很重要。PyTorch的动态图特性天然适合这种“训练到一半发现问题马上改逻辑再跑”的工作方式。训练机用的单张RTX 309024GB显存batch能开到24实测训练120轮大约3小时。如果只有消费级显卡RTX 3060 12GB也能跑把输入分辨率降到512、batch减半即可。推理阶段GPU用TensorRT加速CPU则走OpenVINO这个后面在第5节展开。整个环境和依赖用conda管理requirements.txt锁住主要版本保证现场部署时能还原环境。2. 数据集准备与预处理决定系统上限的地方2.1 焊接缺陷图像从哪里来公开数据加现场增量的组合深度学习模型的上限几乎由数据质量决定缺陷检测这种长尾分布问题尤其明显。焊接缺陷在正常生产中的出现率低良品数据占绝对多数缺陷样本又分布在气孔、夹渣、裂纹、未焊透等多个类别中直接用产线数据训练会遇到严重的类别不均衡和样本不足。最简单的做法是先用公开数据集搭建基线再根据现场情况增量补充。我们选用了焊接X射线图像公开数据集GDXray里面包含铝合金和钢材焊缝的典型缺陷图像气孔、裂纹、夹渣、未焊透这些类别都能覆盖另外参考带钢表面缺陷数据集NEU-DET来补充纹理干扰样本因为焊缝表面的鱼鳞纹和带钢表面的部分纹理在视觉上有相似性对增强模型抗干扰能力有帮助。两组数据按单类样本量做平衡抽样整理出约4200张标注图按7:2:1划分训练、验证、测试。如果没条件获取真实X射线图也可以用超声检测图像或高分辨率相机拍摄的焊缝表面图代替关键是缺陷类别要和现场一致。2.2 标注格式转换与细节VOC转YOLO的一行代码标注阶段采用Pascal VOC格式工具用的labelImg框出缺陷外接矩形后指定类别。焊接缺陷尺度差异大标注时要把矩形框到紧贴缺陷边缘尤其裂纹这类细长目标长宽比可能达到5:1框松一点会在归一化时引入误差后面回归损失就会被放大。为了控制质量所有图由两个人分开标一遍再逐张核对差异焊接缺陷类别边界本来就模糊一个人标容易产生个人倾向。标注完成后需要把VOC的XML转成YOLO训练要用的txt格式一行表示一个目标类别ID、中心点x、中心点y、宽、高坐标都归一化到0-1。核心转换代码很短import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class_names: continue 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) cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_names.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))这里要说一个真正影响训练的细节归一化坐标保留6位小数不要为了省存储省略精度。细长裂纹的宽可能只有0.005左右四舍五入误差很容易超过1%对回归损失的影响比想象中大很多。2.3 数据增强与样本均衡增强不是越多越好焊接缺陷现场成像受光照、烟尘、工件表面反射影响增强策略不能只做翻转。我们用到的组合是随机翻转、0到90度旋转、亮度对比度扰动、高斯噪声、模糊、Mosaic拼接。Mosaic能同时提供多尺度信息对小目标检测很有帮助。这里有一个专门为焊接场景加的约束裂纹类增强时旋转角度限制在±15度内不做大角度旋转因为真实焊缝中的裂纹方向通常有固定取向旋转太自由反而会让模型学到不存在的角度分布。类别不平衡方面气孔样本多裂纹和未焊透样本少。解决手段有两个一是用加权采样让每个epoch内各类样本出现次数大致均衡二是结合Focal Loss降低易分类样本的梯度主导作用。需要注意Focal Loss的gamma不宜直接照搬论文焊接背景区域占比大gamma太高会让模型对背景完全不敏感导致误报升高我们对比后gamma取1.5比较合适。这些策略会在第3节的Loss设计部分再具体说明。3. 模型构建与训练从基线到可用的调优路径3.1 模型结构与anchor调整先跑通再提精度基线采用YOLOv5s骨干是CSPDarknet颈部用PANet做多尺度融合输出三个尺度的检测层。对焊接图像而言中尺度特征负责气孔、夹渣等中等目标大尺寸输出层负责裂纹等细长目标。研究过程中没有一上来就加大模型而是先做了两件性价比最高的事把类别数改成实际缺陷类别数用训练集边界框重新聚类anchor。为什么要重新聚类anchor因为默认COCO的anchor是按日常物体统计的而焊接缺陷的尺寸分布完全不同。用K-Means对边界框聚类后细长裂纹的anchor宽度集中在5到12像素气孔等近圆形缺陷的anchor宽度大多在20到50像素两者相差好几倍。直接用默认anchor训练细长目标回归慢loss在前几个epoch几乎不降重新聚类后收敛明显提速mAP提升约3个点。这个操作成本极低强烈建议做。3.2 训练参数与Loss设计每个参数都有原因训练配置统一采用640×640输入batch设为1612GB显卡配合混合精度batch减半初始学习率0.01带warmup预热优化器选SGD配合动量0.937权重衰减5e-4。使用AMP自动混合精度训练显存占用降低约30%训练速度提升约40%。数据加载用num_workers8和pin_memoryTrueGPU利用率能稳定在70%以上。Loss采用YOLO的复合损失Box Loss用CIoU分类Loss和置信度Loss用BCEWithLogitsLoss三项按0.05、0.5、1.0加权。训练时要盯着TensorBoard里的分项loss曲线如果分类loss降得快但回归loss不降优先检查anchor如果回归loss降了但类别错乱检查标签是否有错位。训练策略上前5个epoch冻结骨干只训练检测头让回归头先对目标位置有基本感知5个epoch后解冻骨干全参数微调。训练全程开启EMA指数移动平均最终mAP提升1到2个点。120轮训练总时长约3小时所以这个方案可以反复实验不用等太久。3.3 评价指标与实验结果记录别只看一张图效果评价指标用mAP0.5、mAP0.5:0.95、Precision、Recall、FPS五个维度。mAP0.5是IoU阈值为0.5时的平均精度现场验收通常以这个为准mAP0.5:0.95更严格适合研究阶段评估框位精度。推理速度是硬性要求单帧处理必须小于50ms才能跟上产线节拍。需要特别说明的是数据划分方式对指标影响极大。按单个焊接工件随机划分和按整个工艺批次划分结果差很多。按工件随机划分时同一工件不同位置的图像可能同时出现在训练和测试集特征高度相似测试指标虚高按工件分组划分更贴近真实部署场景我们最终采用按工件分组切割。下表是YOLOv5s在我们整理数据集上的最终表现缺陷类别样本占比PrecisionRecallmAP0.5形态特征气孔30%0.820.850.88类圆形暗斑尺度小夹渣15%0.750.780.77不规则块状灰度不均未焊透12%0.700.730.71条状位置在根部裂纹8%0.610.580.65细长线状对比度低飞溅20%0.880.900.91小圆斑数量多整体平均-0.770.800.79-整体mAP达到0.79和预期一致。最难的是裂纹最容易的是飞溅这两类基本定调了整个系统的优化方向。4. 漏检误检分析与针对性改进4.1 裂纹等小目标为什么难检测从表和实际测试都能看出裂纹的mAP比其他缺陷低了近20个点这背后是视觉特征层面的根本原因。气孔是封闭暗斑边缘相对清晰CNN提取灰度差异特征就能识别而裂纹是曲折细线局部窗口内与焊缝表面的伪纹理几乎一样只有拉大上下文才能判断它是否贯穿。多层特征图融合虽然能带来一定帮助但裂纹宽度可能只有两三个像素在多次下采样后信息很容易被抹平。针对这个现象我们试过三种手段Tile切块检测、提高输入分辨率、TTA多尺度推理。提高分辨率有效但显存占用大TTA推理时间翻倍最终在项目里采用的是切块思路把1280×1280原始焊缝图切成四块512区域独立推理再合并结果。这样小目标按原始尺度输入召回率提升约10%代价是总耗时增加需要工程上权衡。对更极端的情况也可以后续尝试在骨干网络中加入注意力模块但需要先保证基础流程稳定。4.2 误报主要集中在哪类场景误报主要两种一种是把焊缝边缘的鱼鳞纹理误判为裂纹鱼鳞纹有周期性结构与裂纹在局部窗口内特征接近区别只在连续性和尖锐度另一种是把焊渣飞溅误判为气孔飞溅边缘不规则气孔内部暗且接近圆形在特征空间存在重叠单靠神经网络很难完全区分。处理思路是“深度学习为主、规则为辅”。针对第一类增加负样本收集大量无缺陷焊缝图随机裁剪加入训练集让模型充分见过背景纹理区域这类误报会明显下降。针对第二类在后处理阶段加几何规则飞溅边界框通常面积小于10像素且长宽接近置信度偏高的误报可以用这条先验规则直接过滤。实际效果是准确率提升了约4个点误报率明显下降。这种混合策略在工业视觉项目里非常实用不要迷信模型能解决所有问题。4.3 三轮改进实验的具体效果训练阶段做了三层递进实验记录在这里供复现参考。第一轮用默认配置跑通得到基线mAP约0.71此时裂纹一类基本检不出来飞溅却有大量误报。第二轮重新聚类anchor、开启Focal Loss类别权重、提高裂纹类采样权重模型整体mAP提升到0.76裂纹从0.50左右升到0.61。第三轮加入负样本增强和切块推理整体mAP到了0.79漏检率从8%降到5%。其中有个值得警惕的现象召回率提高的同时误报率反而上升了1.2%。这说明提高召回和降低误报在阈值取向上是矛盾的。产线通常更怕漏检所以置信度阈值可以适当降低再用第5节讲的连续帧校验来过滤单帧误报。这类取舍需要根据现场要求决定不能只看mAP数据冲高。5. 工程化落地从训练好的模型到产线可用的检测流程5.1 推理流水线整体设计模型训练好不等于系统能用中间还隔着图像采集、前处理、推理、后处理、告警整条流水线。现场图像来源有两种面阵相机拍摄的焊缝表面图或X射线DR成像输出的焊缝内部图。在线检测模式下OpenCV从相机读图先做灰度化、去噪再letterbox缩放成640×640输入模型推理完成后把检测结果映射回原图坐标。预处理里最容易出错的是强制resize。直接把图像拉伸到固定尺寸细长目标会被压扁检测器学到的形状特征全部失效。正确做法是等比缩放后填充灰色边代码很短import cv2 import numpy as np def letterbox(img, size640): h, w img.shape[:2] scale min(size / h, size / w) new_w, new_h int(round(w * scale)), int(round(h * scale)) img cv2.resize(img, (new_w, new_h)) canvas np.full((size, size, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] img return canvas, scale这段代码在整个推理链路里是要首先写对的。后面坐标还原时需要用同样的scale把归一化边界框映射到原图分辨率两处不一致会导致框偏这是调试中很常见的低级错误。5.2 NMS后处理与告警判断逻辑模型输出的是原始预测框同一目标通常有多个重叠框必须做NMS去重。置信度阈值我们默认0.35NMS的IoU阈值取0.5。但为了减少漏检工业现场会把置信度阈值降到0.25再用连续帧校验做兜底同一坐标区域连续两帧都被检出才判定为有效缺陷单帧偶发误报不会触发告警。这个机制对抑制飞溅误报特别有效飞溅在单帧里高置信度误报但在连续帧中位置不固定很容易被过滤掉。告警逻辑同时做缺陷统计统计每个类别的框数量、最大面积、平均置信度生成结构化日志写入本地SQLite或通过HTTP上报给现场管理端。现场操作工人只看到最终结果“第3段焊缝检出1处裂纹区域坐标xxx”不需要理解模型内部细节。这种从模型输出到业务消息的封装是整个落地系统里容易被忽略但很重要的一环。5.3 部署加速与格式转换推理速度是硬指标。PyTorch原始推理在RTX 3090上每帧约25ms但如果现场用原有工控机没有独立显卡CPU推理可能到几百毫秒完全跟不上节拍。部署链路是先用torch.jit保存脚本模型再转ONNX然后按现场硬件分别转TensorRT或OpenVINO。NVIDIA GPU场景下TensorRT FP16推理单帧可以压到5到10msIntel CPU场景下OpenVINO能压到80ms左右。转换期间遇到的最典型问题是自定义算子不兼容。YOLOv5早期实现里的Focus模块在转ONNX时会提示算子不支持解决办法是把它重写成标准的conv加slice拼接或者导出时关闭某些优化。建议所有检测模型在选型阶段就避免引入过于定制化的算子标准卷积、标准上采样在转换链路上最稳妥。现场如果既没有独显又对延迟不敏感也可以直接用ONNX Runtime CPU推理功能优先速度次之。5.4 数据回流与持续迭代模型不是一个训练完就一劳永逸的黑盒。工厂换焊材、环境温度变化、相机位置偏移缺陷形态都会跟着变模型指标会悄悄下降。项目实际运行时应该建立“现场采样—半自动标注—增量训练—评估—发布”的轻量闭环每天自动抽取检测中的低置信度样本和误报样本定期交给质检员批量标注积累到几百张后做增量训练。标注格式沿用VOC和YOLO两套流程上不需要额外开发。这个迭代闭环看起来不复杂但很多项目在模型验收后就停下来了。实际上焊接工艺调整带来的图像分布漂移是常态而不是意外。如果现场没有数据回流机制模型的有效周期可能只有几个月。这是我在项目里反复强调的长期运维关键点。6. 常见问题与避坑记录6.1 训练不收敛时的排查顺序新手上路最容易遇到loss不降或震荡剧烈。排查要按顺序来不要东试一下西试一下。第一步检查数据加载确认类别编号和标签路径一一对应。我遇到过因为数据集排序方式不同导致标签错位但loss仍在下降的情况模型根本无法收敛到合理效果这是最隐蔽的问题。第二步看anchor用预训练模型但不更新anchor细长缺陷的回归目标精度会很差loss可能卡在2.0附近。第三步看学习率batch减半时学习率要按比例调整否则容易震荡发散。第四步检查AMP个别场景下混合精度损失导致数值不稳这时关掉FP16、改用FP32能解决问题。6.2 显存不足和CPU部署太慢怎么处理显存不足是工业场景最常见的问题。8GB显存显卡优先做四个调整batch减半加梯度累积、输入分辨率从640降到512、主干从YOLOv5s换成YOLOv5n、开启AMP。通常前两个调整就能解决问题精度牺牲控制在可接受范围内。如果还想再压可以把图像切成切片推理一次只推理一部分缺点是多一次拼接逻辑。CPU部署太慢时优先考虑OpenVINO原生PyTorch CPU推理和OpenVINO之间往往有3到5倍差距但需要注意OpenVINO对动态shape支持不完整建议固定输入尺寸。现场Batch size固定为1不要为了整齐在CPU上开batch单张图单次推理是最稳的。实测下来模型结构越标准转换后速度越快这也是前面反复强调避免自定义算子的原因。6.3 试运行阶段最容易踩的坑试运行阶段别直接上自动告警这是很多团队都会犯的错。正确做法是先并行跑两周模型输出和现场判定结果同时记录每天对比差异计算出实际误报率和漏报率后再打开告警开关。同时系统要保留每帧检测结果的缩略图方便事后分析误报和漏检。我实际遇到过这样的情况现场反馈某段时间误报突然变多排查发现是车间灯光改造后提高了照明亮度图像整体亮度和训练集分布产生偏移。解决办法是加光照归一化并在训练集中增加亮度扰动增强。这次排查让我养成了习惯不管什么项目预处理阶段一定要加光照归一化训练数据增强必须包含亮度扰动。焊接检测尤其如此现场车间照明千差万别早上和下午的光线都可能不一样模型如果对光照敏感早晚会出现稳定性问题。测试时也要额外构建一组不同光照、不同距离的图像做鲁棒性验证这比单纯追求mAP数字更有意义。
