在焊接质检一线待过的人,应该都有一种感觉:真正难的不是把缺陷找出来,而是怎么跟操作人员说清楚“这一处到底是不是缺陷”。传统的目标检测在焊缝图像上画一个矩形框,框里既有缺陷也有正常纹理,框外还有细小的气孔漏在外面,实际产线上根本没法直接拿来用。最近我整理了一套基于YOLOv8-seg的焊接缺陷分割系统,把检测从“画框”升级到了“抠像素”,并在这套工程里重点改进了特征金字塔和降采样模块,也就是标题里提到的AFPN-P345和ContextGuidedDown。同时配套整理了一份可直接训练的焊接缺陷数据集和部署教程,脚本调好之后一键就能跑起来。这篇文章我把这套系统的完整思路、数据集构建、改进点筛选和部署踩坑都讲一遍,给准备做工业视觉质检的同行做个参考,也顺便聊聊哪些环节最容易被低估。
1. 焊接缺陷为什么难检测:从画框到抠像素
1.1 人工质检和传统算法的局限
焊接质检场景相比普通的目标检测任务有一点很不友好:缺陷和正常焊缝之间往往没有清晰的边界。气孔是圆形暗点,但焊缝表面的氧化色也能形成暗色区域;咬边是焊缝边缘的凹陷,但在图像上可能只是光影变化导致的轮廓起伏。我见过不少传统方案用阈值分割、边缘检测、最大类间方差法来提取缺陷,它们在强对比度、单光源均匀照明的实验室图像上效果还可以,一旦放到实际产线,工件反光、弧光残留、防飞溅剂残留全部混杂在一起,固定的灰度阈值马上就失灵。
人工检测当然也存在明显短板。质检员盯着屏幕连续看上两三个小时,注意力下降是生理规律,不是培训能解决的问题。细小气孔和微裂纹本身就接近人眼的识别极限,再加上传送带不停转动,漏检很难完全避免。更麻烦的是人工判定标准很难统一,同一个缺陷在几位质检员那里可能给出完全不同的等级结论。
1.2 实例分割比目标检测多给了什么
很多刚接触这个方向的开发者会问:YOLOv8已经能做目标检测了,为什么还要用YOLOv8-seg做实例分割?我的回答是,在焊接缺陷这个场景里,矩形框的信息量远远不够。
假设焊缝上同时出现一个夹渣和一条咬边,位置挨得很近,目标检测会给两个框,但框与框大量重叠,后处理NMS之后还可能把其中一个小缺陷直接吞掉。实例分割输出的是一组多边形轮廓,每个缺陷有自己的像素归属,重叠区域也能通过掩码区分开。更重要的是,分割掩码可以直接用来计算缺陷面积、周长、长宽比、灰度均值这些量化特征,这些正好是缺陷等级判定最需要的基础数据。比如气孔直径超过多少毫米必须返修,裂纹长度超过多少需要报废,这些判定逻辑只有基于像素级轮廓才能稳定实现。
1.3 这套系统实际能解决什么
我整理这套系统的定位很明确:不是在实验室刷一个漂亮的精确率数字,而是尽量靠近产线可用的状态。基础模型选择YOLOv8-seg,是因为它在检测和分割之间的平衡性比较好,工程生态也比较完整,数据格式、预训练权重、导出工具链都是现成的。在网络层面,我加入AFPN-P345结构优化多尺度特征融合,同时用ContextGuidedDown减少降采样过程中的细节损失,这两个改动对应的是焊接缺陷里最容易翻车的两类问题:小目标遗失和边缘模糊。
系统还配了完整的数据集整理和部署流程,包含训练、验证、导出ONNX、TensorRT加速的全部脚本。适合谁?适合刚入手工业视觉质检的算法工程师、做焊接自动化设备需要添加质量判定模块的团队,以及研究生阶段做缺陷检测方向需要一套可复现基线的人。它不是一个能直接扔到现场不管的“黑盒”,而是一个你拿到以后可以快速跑通、再按自己的数据迭代的起点。
2. 数据集是系统的地基:缺陷形态、标注格式与增强边界
2.1 焊接缺陷有哪些常见形态
焊接缺陷的分割,首先得知道自己要分割什么。我按国标里常见的几类缺陷整理过一遍,视觉上差异其实挺大:
- 气孔:多为圆形或椭圆形暗斑,常见于焊缝表面,尺寸往往很小,可能只有十几个像素。
- 夹渣:形状不规则,通常出现在焊缝内部或层间,图像上是灰暗的块状区域,和背景的灰度差不大。
- 未熔合:焊缝与母材之间没有充分熔合,视觉上表现为线条状或带状的低灰度区域,走向一般和焊缝方向一致。
- 裂纹:细长、曲折,宽度很窄,是分割里最难的类别,因为它和正常的焊缝纹理太像了。
- 咬边:焊缝边缘的凹陷,形成沿焊缝走向的弧线状局部暗区,容易和阴影混淆。
- 焊瘤、飞溅:形状外凸、亮度高,相对容易区分,但在背景杂乱时也会误判。
这些类别不是等量出现的。真实产线数据里,飞溅和轻微咬边的样本数量通常远大于裂纹和未熔合,如果直接按原始分布训练,模型会对高频类别过拟合,低频却关键的类别召回率很差。所以数据集整理阶段就要做类别统计和分层采样,而不是简单随机划分训练验证集。
2.2 YOLOv8-seg 对标注数据的要求与转换
YOLOv8-seg的训练标签格式和检测有很大区别。检测标签每行是“类别 中心x 中心y 宽 高”,而分割标签在四个数值之后还要跟上多边形顶点的归一化坐标,格式形如:
class cx cy w h x1 y1 x2 y2 x3 y3 ...注意这里的宽度、高度仍然要写,但实例分割的掩码训练实际使用的是后面的多边形点列。所有坐标值都归一化到0~1,图像宽度和高度各自作为分母。
我常用的标注流程是先用多边形标注工具把每个缺陷轮廓描出来,导出JSON,再写脚本转成YOLO格式。转换脚本的核心逻辑大概是:
import json import glob def poly_to_yolo(points, img_w, img_h): # 归一化多边形坐标 norm = [] for i in range(0, len(points), 2): norm.append(f"{points[i] / img_w:.6f}") norm.append(f"{points[i + 1] / img_h:.6f}") return " ".join(norm) for json_file in glob.glob("label_json/*.json"): ann = json.load(open(json_file)) img_w = ann["imageWidth"] img_h = ann["imageHeight"] out_lines = [] for shape in ann["shapes"]: cls_id = class_name_to_id[shape["label"]] pts = shape["points"] # [[x,y], [x,y], ...] flat = [coord for p in pts for coord in p] # 计算包围盒并归一化 xs = flat[0::2] ys = flat[1::2] cx = (max(xs) + min(xs)) / 2 / img_w cy = (max(ys) + min(ys)) / 2 / img_h w = (max(xs) - min(xs)) / img_w h = (max(ys) - min(ys)) / img_h line = f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f} {poly_to_yolo(flat, img_w, img_h)}" out_lines.append(line) label_path = label_dir + os.path.basename(json_file).replace(".json", ".txt") open(label_path, "w").write("\n".join(out_lines))有几个坑是我实际踩过的。首先是多边形闭合问题,有些标注工具导出的是首尾重复的点,而YOLO训练时对重复点容忍度不高,最好在转换时主动去重。其次是标签点顺序,有的工具导出是逆时针,有的是顺时针,YOLO本身对方向的敏感度其实很低,但如果你的增广代码里有多边形裁剪,方向不一致会在裁剪时报错。第三是标签越界,标注时鼠标稍微划出图像边缘,归一化坐标就可能出现负数或大于1的数,训练时直接报错,这种数据必须在校验阶段过滤掉。
2.3 采集与增强:哪种增强会把模型带偏
数据采集阶段建议优先解决成像一致性。工业现场的光照条件变化很大,有的焊缝区域反光严重,缺陷的对比度会被亮斑压下去。我自己的习惯是加偏振镜消除金属反光,再配合白平衡统一光源色温,让同类缺陷在不同图像上保持近似的灰度特征。
增强策略方面,HSV色域扰动、平移、小角度旋转、水平翻转对分割任务都是安全的,但Mosaic增强要谨慎使用。Mosaic会把四张图拼在一起,缺陷本身又小,拼接后缺陷的上下文关系全被打乱,模型容易学到“缺陷周围必须是拼接缝”这种错误特征。在缺陷分割这个场景,我测试下来Mosaic带来的提升反而不如不用的版本稳定。
还有一个很容易被忽略的点是图像分辨率。如果你的原始图像是2000×2000的大图,标注时缺陷看起来很清楚,但训练时如果直接缩放到640×640,细小的气孔可能只剩几个像素,标注轮廓在缩放后也会和真实边缘错位。这种情况通常要按焊缝区域做ROI裁剪后再送进训练,或者直接用1280输入尺寸训练,后面部署阶段再谈具体策略。
3. YOLOv8-seg 的核心改动:AFPN-P345 与 ContextGuidedDown 到底改了什么
3.1 从FPN到AFPN:单向金字塔为什么不够
YOLOv8默认的特征融合是PANet结构:先是自顶向下把高层的语义信息传到低层,再自底向上把低层的空间信息送到高层。这种结构用了很多年,覆盖了大部分多尺度检测需求,但有一个天然短板——高层和低层特征之间的交互是逐级串行的,语义信息要跨很多层才能影响到最细的P3特征图,路径太长,信息衰减明显。
AFPN(Asymptotic Feature Pyramid Network)的出发点就是解决这个“距离”问题。它的做法不是简单的一条自上而下通路,而是把相邻层级的特征渐进式地融合起来:高层特征通过3×3卷积和注意力机制逐步“长”到低层分辨率,低层特征也逐步向高层聚合,每一级融合都发生在相邻尺度之间,信息传递路径明显缩短。表达成大白话就是:每一层特征不再是只和上一层打交道,而是沿途不断吸收旁边几层的贡献,最终每个金字塔层都拿到跨尺度的混合信息。
在YOLOv8-seg里替换PANet时,AFPN输出的特征图会直接喂给分割头。因为分割任务是像素级输出,特征图上的空间细节保留得越完整,掩码边界就越准,这也是AFPN在分割场景下比纯检测场景收益更明显的原因。我在焊接数据上做了对比,使用AFPN之后,气孔类别的掩码IoU提升了大约3个百分点,肉眼可见轮廓更贴合缺陷边缘。
3.2 P345 结构对小缺陷的意义
标题里写到的P345,指的是特征金字塔输出的三个尺度级别:P3、P4、P5。对应到640×640输入,每个层的步长分别是8、16、32,特征图尺寸也就是80×80、40×40、20×20。这个组合是YOLOv8系列最常用的特征层级配置,它平衡了计算量和感受野。
为什么说这对焊接缺陷特别重要?因为很多气孔和微裂纹在特征图上的投影面积太小了,往往只有P3层能够保留足够的分辨率去区分它们。P3层的每个像素对应原图的8×8区域,如果一个缺陷原本只有20×20像素,那么它落到P3层还有约2×2到3×3个像素,勉强够分割头判断;如果等它传到P4层,基本已经缩没了。
当然,P345并不是万能的。如果数据集中存在大量超小缺陷,我会建议把网络配置改成支持P2层,让特征图分辨率翻倍到160×160。代价是计算量快速增长,显存占用也会明显提高。实际项目里如果算力有限,优先在P3层上做文章,把P3层的通道数加大或增加一层轻量注意力,往往比盲目加P2更划算。
3.3 ContextGuidedDown:降采样时的上下文引导
另一个关键改动是ContextGuidedDown。大多数人用YOLO时不太关心降采样模块,默认用步长为2的卷积,顶多再配个BN和激活函数。但焊接缺陷分割里有一个很实际的矛盾:下采样是为了扩大感受野和省显存,但每一次步长为2的卷积只看到局部区域,它做降采样的时候并不知道全局上下文是什么,缺陷边缘的连续性很容易在降采样时被截断。
ContextGuidedDown的思路通俗地说就是:在下采样操作之前,先用低分辨率的全局特征生成一张引导图,告诉网络“哪些位置是值得保留的细节”,再用引导图参与降采样权重计算,让下采样不再是窗口内的局部决策。有点类似于你在缩小一张照片时,不是简单隔行抽像素,而是参考整张图的明暗分布来决定每一块应该保留什么。对金属焊缝这种纹理连续性强、缺陷边缘细长的图像来说,这种“带全局视野的收缩”能明显减少边缘锯齿和细小裂纹断裂。
我在实际替换测试里发现,单纯把标准步长卷积替换成ContextGuidedDown后,模型参数量的增加很小,但验证集上边缘区域的掩码平滑度提升了,裂纹分割的连续性也更稳定。这个模块带来的提升不像AFPN那么显眼,但它和AFPN属于互补关系:一个管特征融合路径,一个管降采样信息保留,两个一起用正好覆盖了分割任务的两个核心痛点。
4. 50+改进点不是无脑堆:筛选逻辑、组合规律与消融方法
4.1 50+改进点到底涵盖哪些方向
标题里的“50+改进点”乍一看很唬人,但说白了就是一套可以插拔的模块库。每种成熟的目标检测或分割网络改进,都可以拆成几个方向分别替换。我按自己的实践把它们分成五类,每一类的改动位置和作用都不一样:
| 方向 | 常见模块举例 | 主要影响 |
|---|---|---|
| 主干网络 | MobileNetV3、ShuffleNetV2、EfficientViT、RepVGG | 推理速度和基础特征提取能力 |
| 颈部融合 | AFPN、BiFPN、NASFPN、加权双向融合 | 多尺度特征的信息流通效率 |
| 注意力机制 | SE、CBAM、ECA、SimAM、CA | 通道或空间维度的关键信息加权 |
| 降采样与下采样 | ContextGuidedDown、空洞卷积替代 | 降采样过程中的细节保留 |
| 损失与样本分配 | WIoU、SIoU、ShapeIoU、ATSS、TOOD | 收敛速度、难例处理、边界回归质量 |
理解这些方向之后,你就明白了“50+改进点”的真实含义:它不是一个必须全部塞进去的清单,而是一个备选工具箱。你要做的是根据自己任务的短板,从里面挑一两个模块做替换,跑实验验证收益,而不是把所有模块叠起来堆一个“缝合怪”。
4.2 组合的秩序:什么和什么搭,什么和什么打架
组合改进点这件事,非常像配电脑:CPU、显卡、内存要匹配,如果主板不支持,再好的CPU也发挥不出来。我踩过最典型的坑是,在一个很小的数据集上同时换了轻量主干、加了注意力又换了Loss,结果训练怎么都不收敛,后来逐个回退才发现注意力模块和新的Loss在梯度尺度上有冲突,二者叠加反而放大了难例的噪声。
从实际项目角度看,我建议遵循几个规律。第一,主干、颈部、损失这三个层面,不要一次改两层以上。改主干影响的是整体特征的分布,改颈部影响的是多尺度融合方式,改损失影响的是梯度回传的方向,这三个层面同时动,出了问题根本定位不到源头。第二,轻量主干和复杂颈部一般不搭。MobileNetV3这类轻量主干本身特征表达弱,再配一个参数很大的AFPN,颈部也没有足够强的主干特征可以融合,收益很低,还不如保持YOLOv8默认的CSP结构。第三,注意力机制优先放在P3层。焊接缺陷的小目标主要依赖P3层的空间细节,在P4、P5层加注意力对分割掩码的提升很小,反而增加延迟。
我在这个项目里最终选定的组合是:YOLOv8-seg基线 + AFPN-P345替换PANet + ContextGuidedDown替换部分降采样 + SimAM注意力加在P3层 + WIoU损失。这个组合兼顾了速度、精度和稳定性,在焊接数据集上比基线mAP50提高约5个百分点,而推理速度只下降了8%。
4.3 消融实验怎么跑才算数
任何论文项目或者工程选型,都需要用消融实验来说服自己和同事,但消融实验不是“跑一组对比图”那么简单。我见过不少同学把不同改进点各自跑一遍,最后把结果并列出来就说某个模块有效,实际上样本量不够、随机种子没控制、验证集划分不统一,结论完全不可信。
正确的做法是固定一个验证集,所有消融实验都使用同一套超参数、同一个输入尺寸、同一个随机种子,只改动目标模块。每个配置最好跑三次取平均,因为目标检测和分割的训练本身就有随机性,单次结果差一两个点很正常,三次平均值才具备可比性。记录维度除了mAP50和mAP50-95,还要同步记录FLOPs、参数量、单帧推理时间,这三个指标才能看出精度提升是来自结构优化还是单纯算力堆叠。
另外一个容易被忽视的环节是“负向消融”。比如把ContextGuidedDown从组合中去掉后,mAP没怎么掉,但裂纹类别的分割连续性明显变差,这时候不能简单下结论说这个模块没用,要分类别看指标。我的做法是每轮消融都单独记录每个类别的IoU和召回率,这样哪个模块对哪类缺陷起作用就一目了然。这些数据也是后续写论文、做项目汇报的最好素材。
5. 一键训练脚本背后的准备工作:标签校验、超参与止损
5.1 训练前先过一遍标签校验脚本
很多人拿到工程后的第一件事就是直接跑训练,这是一个很容易犯的错误。焊接缺陷数据集往往来自多个标注人员,标注风格不一致,偶尔还会有漏标、错标、多边形点数过多等问题。如果直接喂给模型,训练中途报错还算是好事,更怕的是标签错得不多不少,模型学了一堆错误信息,最后验证指标虚高但实际表现诡异。
我整理这套工程时,训练脚本里加了一个标签校验步骤,在启动训练前自动扫描全部标注文件:
import glob error_log = [] for txt in glob.glob("datasets/labels/*.txt"): with open(txt) as f: for line_no, line in enumerate(f): parts = line.split() cls_id = int(parts[0]) nums = list(map(float, parts[1:])) if len(nums) < 8 or len(nums) % 2 != 0: error_log.append((txt, line_no, "多边形点数异常")) cx, cy, w, h = nums[:4] if cx < 0 or cy < 0 or cx > 1 or cy > 1 or w <= 0 or h <= 0: error_log.append((txt, line_no, "包围盒越界")) if any(v < -0.01 or v > 1.01 for v in nums[4:]): error_log.append((txt, line_no, "多边形坐标越界"))我在实际使用中把校验阈值设置得比0和1稍微宽了一点,比如允许-0.01到1.01,因为某些标注边缘在图像边界附近,轻微越界可以通过后续裁剪修复,但超过这个范围就必须剔除。校验通过后还要打印类别分布和每个类别的掩码面积直方图,方便在训练前就发现类别不平衡问题,而不是等模型训完才开始分析。
此外,训练集和验证集的划分一定要按“缺陷实例”而不是按“图像”做分层。焊接缺陷数据里经常出现一组连续拍摄的图像来自同一块工件,它们高度相似,如果随机划分,训练集和验证集里会出现大量同源图像,验证指标会被严重高估。我的做法是按图像来源的工件编号分组,同一个工件的图像全部进同一个集合,绝不在训练验证之间劈开。
5.2 训练节奏与超参数基线
训练超参数的设置,我提供一个自己常用的基线,适合焊接缺陷这类中小规模数据集。输入尺寸默认640,如果缺陷特别小再切换到1280;batch size在单卡16G显存下取8到16;训练轮数200到300;初始学习率0.01配合warmup,优化器用带动量的SGD或AdamW都行,我偏向SGD在分割掩码上的稳定性。
有一个细节是冻结主干训练。如果是从COCO预训练权重开始,前10个epoch冻结主干,只训练颈部、分割头和新增模块,可以让新增模块先适应一下预训练特征的分布,避免一开始就大幅扰动backbone的权重。焊斑、焊缝纹理这类特征和COCO自然图像差异比较大,所以冻结期结束后要立刻解冻全部层,让backbone充分适应新域。
当类别数量很少时,比如只有3到5类缺陷,模型很容易过拟合。我常用的补救手段是提高增强强度、加大随机遮挡的比例,或者给分割头加一点权重衰减。另外,不要把验证指标的最高点直接当成最优模型,要结合训练集loss和验证集loss的差距判断过拟合程度,选择一个折中轮次的权重。
5.3 看着loss曲线止损,别愣跑完
很多人的习惯是把设置好的训练任务扔到服务器上,等到跑完再看结果。我的经验是,训练开始后只需要盯三个东西:box_loss、seg_loss、验证集mAP。如果前20个epoch内验证集mAP几乎是零,且box_loss和seg_loss完全没有下降趋势,先别急着加训练量,查一下标签格式、类别编号映射和输入图像的通道顺序,这些问题都会导致loss不下降。
另一种常见情况是seg_loss下降很快,但mask的边界始终粗糙。这个我遇到过不止一次,最后定位到原因是标注的多边形点数太少,很多缺陷的轮廓被描成了简化多边形,和真实边缘差太远。模型再怎么训练,也无法突破标注精度的上限。这时候唯一有效的办法是回到标注阶段,至少保证每个缺陷标注点数在10个以上,尤其是长条形的裂纹和咬边。
我通常会在训练脚本里设置一个自动停机的策略:连续15个epoch验证集mAP没有提升,就自动保存当前最优权重并终止训练,避免无效的算力消耗。这个策略在实验调试阶段特别好用,能让你在同样的时间内在不同配置间多跑好几轮对比。
6. 部署上线阶段的坑:模型导出、ROI优化与误检兜底
6.1 导出ONNX与TensorRT时容易翻车的点
训练完的PyTorch模型不可能直接跑在产线工控机上,部署的第一步就是导出。YOLOv8-seg的导出工具链本身很成熟,但还是有几个坑值得专门提一下。
导出ONNX时最麻烦的是动态输入尺寸。如果你希望同一个模型能处理不同分辨率的图像,需要设置dynamic=True,但动态尺寸在TensorRT导出时往往会触发额外的优化耗时,有些自研模块也不一定能完全转过去。我测试过AFPN和ContextGuidedDown,这些结构都是标准卷积和注意力,TensorRT可以正常转换,但如果你用了一些非常规算子比如grid_sample,导出阶段就要格外小心。最稳妥的做法是训一个固定输入尺寸的模型,比如640×640或1280×1280,部署时强制resize到该尺寸,换来的是转换简单、推理稳定、帧率更高。
TensorRT的FP16精度一般在缺陷分割任务里不会造成明显精度下降,但如果焊接图像里存在极低对比度的微小缺陷,FP16量化后有可能出现个别样本的掩码缺失。我的处理办法是同时导出FP16和FP32两个引擎,在验证集上对比mAP50,如果差异超过0.5个点,就果断用FP32。
注意:导出TensorRT引擎时使用的batch size要和生产环境保持一致。如果训练导出时固定batch=1,部署时就不要临时改成batch=4,动态batch的TensorRT引擎在切换batch时可能重新优化,延迟反而更高。
6.2 相机场景里的ROI推理与帧率设计
工业相机拍到的原始图像,往往不是公众认知里那种“一张图中央有一道焊缝”的构图,而是整个工件的宽幅画面,焊缝只占图像的一小部分。如果直接把整图缩放到640输入,缺陷会被几何压缩得非常小,分割效果自然大打折扣。这里我强烈建议先做ROI裁剪。
可以先用一个简单的目标检测或传统的轮廓定位把焊缝主干区域找出来,裁剪出包含焊缝的矩形区域,再送进分割模型。实际操作中,这个ROI裁剪不需要很高的精度,只要保证焊缝主体在画面中间、缺陷区域没有被截断即可。固定ROI之后,再用滑窗把大区域切成若干1280×1280的patch,逐patch推理还原掩码坐标。
帧率设计上要根据产线节拍来定,不要一味追求高fps。假设传送带速度决定了每个工件需要在1秒内完成判定,扣除机械臂或分拣机构的响应时间,真正留给模型的推理时间可能只有300到500毫秒。在普通工控机的显卡上,1280输入尺寸分割推理大约需要80到150毫秒,完全够用。如果换到嵌入式设备,建议把输入降到640,同时用裁剪策略保住小缺陷,宁可处理得慢一点也要保证小缺陷不丢。
6.3 分割掩码的二次判断:把误检兜住
模型部署之后还会遇到一类很现实的问题:误检。反光、暗影、划痕都有可能被分割模型识别为缺陷,直接报警会导致产线频繁停机,质检员最后还是会关掉系统。我的经验是把模型当作“候选区域生成器”,掩码后处理才是真正的判定者。
分割模型输出的每个掩码都会带有置信度,但置信度阈值不能定得太高,否则漏检率上升。我会把置信度阈值放低到0.25左右,然后在后处理阶段做二次过滤:计算每个掩码的面积、长宽比、轮廓周长,再结合缺陷类别设定业务规则。比如气孔的面积必须大于某个像素值才报警,裂纹的长宽比必须大于5且长度超过阈值才上报,飞溅这类高亮区域如果亮度异常高也先过滤掉。这样系统最终呈现给操作员的不是“一组可疑像素”,而是“一个带量化的缺陷记录”,可信度完全不同。
掩码面积阈值还有一个用途:处理一些小到几乎不可见的噪声掩码。模型偶尔会在纹理复杂的区域产生指甲盖大小的孤立掩码,这些在面积过滤后基本都能清掉。我在现场部署时还加了一层“历史帧去抖”,同一个位置连续三帧都被检出同一类缺陷才确认报警,单帧突发噪声直接忽略,这招在产线上效果特别明显。
整套系统从数据集整理到训练、消融、部署,走下来我最深的体会是:别迷信模块数量。50多个改进点真正能用上的往往就三四个,而且每一个都要用数据说话。另外,给模型换再花哨的结构,也不如把数据拍干净、把标签标注准更实在。如果你现在正在做类似的焊接缺陷检测项目,先花一周时间把数据重新审一遍,把最容易混淆的类别定义清楚,再回头调模型,你会回来感谢自己这一步的。