先说个结论:我文章里说的TVA,不是某家厂商的官方模型,而是我们团队内部的代号——Transformer-based Visual Attention,一套从注意力机制出发、专门为3C产品视觉检测设计的架构。打磨这套东西花了我们三个多月,期间被产线压过、被过杀率投诉过、也被shader反光折磨到想砸相机,最后总算把一个耳机充电仓金属中框的外观检测项目从"能跑"做到了"能长期稳定跑"。
这篇是系列第三篇。前两篇把TVA的注意力原理和主干网络拆完了,这篇专门讲落地:数据怎么收拾、模型怎么训、部署怎么压速度、踩过的坑怎么排,尽量写成一份可以直接拿去“抄作业”的记录。适合正在做工业质检算法、或者准备把视觉Transformer引入产线的工程师看,哪怕你目前还在用CNN做检测,这篇里的思考路径也能直接用。
1. 为什么3C视觉检测需要TVA来破局
1.1 3C外观检测的痛点远不止“小缺陷”
很多人一说3C检测,第一反应就是“缺陷小,所以难”。这个说法对,但不全面。我在项目里归纳过,真正让传统方案崩溃的是三类问题同时出现:
第一类,是缺陷本身的多样性。金属中框上常见的缺陷包括划伤、碰伤、压伤、脏污、异色、麻点、水印、亮斑,有些缺陷在特定光源下几乎看不见,换个角度又明显得刺眼。第二类,是表面材质带来的干扰。金属抛光面反射率极高,光源稍微偏一点,整张图就是一片过曝的高光;而哑光面又容易把灰尘误检成脏污。第三类,是产线节拍。我们那条产线单件检测时间要求在3秒以内,过了就要增加工位、加人,成本立刻翻倍。
传统机器视觉在单一场景下能做到不错,但一遇到“来料批次不同导致底色轻微变化”“光源亮度漂移”“机台震动造成对焦偏移”这些生产常态,鲁棒性就直接崩。AI方案里,早期CNN能解决一部分问题,却也解决不彻底。这就是我后来坚持做TVA的根本原因:不是跟风Transformer,而是现有的方案在结构化特征和高频细节的权衡上,已经到顶了。
1.2 CNN的瓶颈:细节被吃掉,长距离关联靠不住
CNN的问题要从下采样说起。为了扩大感受野,CNN不断用卷积+池化把特征图缩小,但两次下采样后,原本只有几十个像素宽的细划伤就可能只剩几个像素,再经过几次卷积就被抹平了。哪怕你用FPN把深层和浅层特征拼起来,浅层特征的语义信息又不够,最终小缺陷识别依然要看运气。
另一个问题更隐蔽:CNN每个位置的输出主要受其局部感受野影响。对于划伤这种“一小段就有几十个像素”的缺陷,局部特征本来是够的;但对于大面积脏污边缘和产品高光区域交叠这种场景,局部卷积很难区分“这是缺陷”还是“这是反光”。它缺少一种全局的上下文协商机制。人工复判时能一眼判断,是因为我们看了整张图,知道“这个亮斑只是倒影的一部分”,但CNN没有这个能力。
当然,这里不是要否定CNN。工业上大量部署仍然是CNN,包括我们自己有些老旧产线也还在跑轻量卷积模型。但每当出现误判且人工复判找不出原因时,打开可视化基本都是同一类现象:模型注意力分散在高光边缘和纹理噪声上,而不是缺陷本体。这说明结构本身的表达上限已经到了,不是你调参能解决的。
1.3 TVA到底破什么局:注意力让特征自己找重点
TVA的思路其实不复杂:把图像切成patch序列,通过query-key-value的自注意力机制,让每个位置可以“看向”全图其他位置,动态决定哪些区域该被重点编码。划伤区域和周围正常纹理之间只要存在细微差异,注意力就可以把相关像素全部关联起来,不管它们在空间上相隔多远。
真正让我下决心切换的,是一次公开数据集上的对比实验:同样的训练数据、同样的分辨率,TVA在细划伤上的召回率比当时的CNN方案高出十几个点,而且在高光干扰下错误率更低。后来分析原因,注意力机制天然带有“抑制无关区域”的倾向——它会自己学到“高光边缘不是缺陷,只是反光”,这种全局判别能力是局部卷积很难学到的。
从投入产出比看,TVA换来的收益是实打实的:漏检率下降,过杀率下降,换批次后的性能衰减速度也明显慢。破局破的是“不敢用小目标模型做工业检测”的局,打破的是“产线数据不够、Transformer训不动”的刻板印象。
2. 重构核心:TVA架构到底改了什么
2.1 输入重构:不要一上来就切大patch
开头部分我特意说“不要一上来就切大patch”,因为这是很多从CNN迁移过来最常犯的错。图像切成patch时,如果每个patch是32×32甚至64×64像素,一个十几像素宽的细划伤直接被压进一个token里,注意力再强也分不清它和噪声的区别。我们最终把方案定在patch=16,配合512×512输入,等于把整张图分成32×32个token,细节保留度和计算量能同时接受。
另外不建议直接把灰度图当作单通道输入丢给Transformer。三通道对工业相机来说很常见,但如果你的相机确实只输出灰度图,我可以分享一个实用技巧:先把灰度图做一层基本的对比度拉伸,再复制到三个通道。这么做不增加算法复杂度,但能让位置编码和注意力里的“伪彩色交互”更稳定,实测训练收敛更快。
我们还在最前面加了一个轻量的卷积stem,也就是先用几个卷积层把原始分辨率整理到合适尺度,再切patch。卷积stem可以很好地保留边缘和角点等底层特征,避免Transformer一上来就对原始像素做全局关联,训练前期还能让loss下降速度明显提升。
2.2 全局+局部双通道注意力:别让网络只顾着看远方
Transformer的注意力一步到位做全局关联,听起来很好,实际训起来有两个问题:计算量随token数量平方上涨,以及“局部细节”往往被全局上下文稀释。比如划伤这种局部连续特征,如果跟全图所有区域都去算相似度,可能有价值的关联只有很小一部分。
所以TVA内部采用双通道设计:一个局部注意力分支在固定窗口内做精细化建模,另一个全局分支每隔几层提取少量全局token做上下文摘要。局部分支处理细节纹理,全局分支处理大类决策,再通过可学习的融合权重把两者合并。这个结构不新奇,但对3C检测来说效果出奇的好,因为它恰好切合“既要看得细,又要看得远”的检测需求。
实现上,我们会把局部窗口设置为7×7,全局token数量固定在64个,两层做一次跨分支融合。你可以把全局token理解成“管理层”:局部细节是一线员工,管着具体像素的特征,但最终判定缺陷类型时,需要管理层综合全场信息做出决策,而不是让每个像素自己单打独斗。
2.3 多尺度特征融合与分割头:检测粒度决定过杀率
只用一个尺度的输出去做检测,基本不会满意。我们最终使用类似特征金字塔的方式,把TVA中间三层和末层的特征都引出来,逐层上采样、拼接,经过一层轻量分割头输出像素级缺陷概率图。这里我特别想强调“分割头”这个概念:缺陷检测若只输出一个框,对细划伤来说没什么意义,因为一条划伤可能横跨整个中框,框的边界怎么定都是错。不如直接做像素级分割,每个像素输出一个“是缺陷”的概率,再在后处理里决定要不要报警。
损失函数上,我强烈建议用Focal Loss配Dice Loss的组合。Focal Loss解决缺陷像素和正常像素数量悬殊的问题,Dice Loss则直接拿交并比做学习目标,更贴近最终关注的指标。不少项目只用一个交叉熵,就会发现网络很容易把“背景”和“小缺陷”混淆,因为背景像素太多,交叉熵里的背景项直接淹没了缺陷项。
另外在特征融合的权重上,不要全用固定常数。我们用一个很小的可学习模块,由网络自己在训练中调整每层特征的重要性。这样做的好处是,换产线换材质后,你只需要重新训练这个融合模块就能恢复大部分精度,主干不需要动。
2.4 训练范式也重构:小样本下的辅助任务
工业检测绕不开一个灵魂拷问:缺陷样本太少怎么办?我们那条产线一个月才能自然出现几百个有效缺陷,其中很多还是相似形态。直接硬训TVA这种参数量不小的网络,过拟合是必然的。
我们做了两件事,效果都很直接。第一,用同机台采集的大量无缺陷图像做自监督预训练,让网络先学会“何为正常”。这个预训练没有标注成本,只需要跑一遍图像重建或对比学习任务,模型在这个阶段学会的底层纹理表达,后续迁移到缺陷检测时节省了大量样本需求。第二,训练时加入辅助任务:不仅输出缺陷分割图,还同时预测“正常/异常”的整图分类。分类任务样本多、噪声少,能稳定主干特征的语义,分割任务则让特征保留像素级细节,两者共享一个骨干,互相钳制、互相促进。
重构训练范式,本质上是在用无标注数据和无缺陷数据,去弥补缺陷标注不足的短板。如果你正被“标注只有两三百张”困住,与其到处收集更多数据,不如先看看手里的正常样本能不能用起来。
3. 真实项目:某型号耳机仓金属中框外观检测的落地全流程
3.1 采集与标注:先把“脏数据”变成“能训练的数据”
这个项目要检测的是某型号耳机充电仓的金属中框,检测项包括碰撞痕、划伤、脏污、异色、亮斑。产线原有方案是传统机器视觉加人工复判,过杀率高到工人每天要复核一千多件,漏检率还压不下去。
第一阶段是采集。我们用8K线扫相机加多角度条形光源,分别拍顶光和底光两套图,合起来作为输入。为什么要两套?因为划伤在顶光下最明显,脏污压伤在底光下更容易显露,单一张图永远有盲区。这一步是整个项目的地基,数据采集阶段多花一周,训练阶段就能少折腾一个月。
标注上我们没有直接按目标框标。我把缺陷类型定义成五类,标注人员需要在像素级把缺陷轮廓描出来。听起来很费人力,但训练分割模型必须这样。为了避免标注不一致,同一批样本由两个人分别标,再对比差异性,差异超过阈值就退回重标。另外特意从产线收集了一批“疑似缺陷但其实是正常纹理”的难例,这类样本对降低过杀率至关重要,一开始我没有专门收集,导致训出来的模型把中框边缘的加工纹路全当成了缺陷。
最终数据集是:正常图8000张,异常图3000张,其中五类缺陷每类至少400个独立样本。对Transformer来说这个量不大不小,配合预训练和增强后,够用。
3.2 训练配置与关键参数:照着调试也能跑出八九分
如果你要从零复现,我建议从一个“TVA-S”的小配置切入:输入512×512,patch=16,Transformer层数12层,注意力头数8,特征维度384,局部窗口7×7,MLP隐藏层比例4,输出特征做金字塔融合后接分割头。这个规模单卡能训,效果也足够用于多数3C外观缺陷。
优化器用AdamW,初始学习率1e-4,warmup 500步,之后余弦衰减到1e-5。Batch size在两张卡上开到32,梯度累积再等效到64。学习率是Transformer最敏感的开关,我见过太多人把CNN那套“1e-2起步”直接搬过来,结果前几百步loss就冲上天。这里不厌其烦再说一次:Transformer的learning rate千万不要从刚入手开始试大的,先用1e-4或更小。
损失权重上,Focal的α设为0.75、γ设为2,Dice权重0.4,分类辅助任务权重0.3。训练60到80个epoch,前期用随机裁剪、翻转、颜色抖动做增强,最后10个epoch只保留轻微的平移增强,让模型在接近真实分布的数据上稳定收敛。整套流程下来大概6到8小时,验证集mIoU能到0.82左右,缺陷分类准确率能到96%以上。
另外一个小参数:position encoding用相对位置编码比绝对位置编码稳定得多。3C产品对位置敏感,框体边缘和中心区域的缺陷出现概率完全不同,绝对位置编码容易让网络记住“缺陷只在特定坐标出现”,换一个产品型号就失效,相对位置编码没有这个毛病。
3.3 部署与节拍:把模型压到产线能用
模型在验证集上表现不错,但上线第一步就撞了墙:初始推理需要45毫秒,连上前后处理、相机触发和PLC通信,单件节拍接近3.5秒,超出了产线许可。于是整个部署流程里最重要的一环不是“跑得多准”,而是“跑得多快”。
我们先导出模型到开放中间表示格式,再用目标设备的推理后端做FP16优化。FP16是性价比最高的第一步,精度损失很小,速度却能提升一半。接着是批量推理,把两张光路图像合入一个batch同时推理,利用并行能力进一步压缩耗时。最后做了注意力剪枝,把局部分支中贡献度最低的20%头剪掉,再用蒸馏把大模型知识迁回小模型,速度压到了13到14毫秒。
这里有个实操经验:剪枝前先用一个小测试集测每个注意力头的“必要性”。我习惯的做法是逐个把单个头置零,跑一遍验证集TP和FP数量,把结果影响最小的头剪掉。这个过程看起来笨,但在产线场景里往往比各种花哨的自动化剪枝算法更可控。最后整套链路单件耗时稳定在1.6秒左右,余量充足。
3.4 结果对比:别只盯着准确率
上线试运行两周后的指标是这样的,我跟传统方案和CNN方案放在一起看过:
| 方案 | 漏检率 | 过杀率 | 单件耗时 | 换批次稳定性 |
|---|---|---|---|---|
| 人工目检 | 1.20% | 8.00% | 4到6秒 | 差,疲劳后下滑 |
| 传统机器视觉 | 0.80% | 12.00% | 2.5秒 | 差,光漂移就崩 |
| CNN检测模型 | 0.35% | 5.50% | 2.1秒 | 中等,需每周重新验证 |
| TVA方案 | 0.22% | 3.20% | 1.6秒 | 较好,换批次仅小幅掉点 |
坦白讲,准确率这个数字我们内部已经不太愿意看。工业现场更看重“漏检率×过杀率”这个组合:漏检一次可能直接导致客诉,过杀一次只是增加人工复核成本。TVA方案把两个指标同时压下来,才是真正打动产线的点。另外它在换批次后只掉零点几个点,维护成本远低于CNN方案动不动就要重新调权重。
4. 常见问题与排查技巧实录
4.1 训练不收敛:把“注意力坍塌”查出来
第一个想说的坑,是训练loss震荡不降。我们第一次训TVA时,loss在前两千步纹丝不动,验证集上所有类别概率几乎一样。查可视化后确认是“注意力坍塌”:所有token的注意力分布趋同,模型退化成比全局平均池化还弱的结构。
排查顺序我建议固定这样:先查学习率是否过大,再看warmup是否不足,最后看position encoding和dropout配置。TVA对这三件事极度敏感。我们最后的解法是三个组合拳:初始学习率降到5e-5,warmup延长到1000步,同时在注意力和FFN层各加0.1的dropout。如果你的loss开始正常下降,说明不是数据问题,是训练配置问题。
还有一个容易被忽略的预置条件:主干最好用无监督预训练初始化,不要从头随机初始化。从头训TVA并非不能收敛,但所需的epoch数会多一倍,而且在工业现场你通常没有那多时间。
4.2 细划伤漏检:先从数据和token粒度找原因
上线后,细划伤漏检率一度维持在0.4%,其他缺陷都被压下去了,就这类不听指挥。排查后发现是两件事叠加:一是部分划伤在512分辨率下只有十几个像素,跟patch尺寸接近;二是顶光图像里的划伤,在底光图像里几乎不可见,对模型来说是“同一样本的两个矛盾视角”。
解决思路分两条线走。数据层面,专门采集了一批极细划伤样本,同时在传统增强之外加入了“随机擦除”增强,模拟细划伤被部分遮挡的情况。模型层面,把最底两层的局部窗口从7×7缩小到3×3,相当于让浅层更关注局部像素级特征,再融合深层全局信息。这两个操作结合,漏检率从0.4%降到0.2%上下。这里也说一句:如果拿到项目第一周就发现某个类型缺陷压不下来,先别急着调网络结构,90%的情况是数据采样不充分或者标注口径不一致。
4.3 过杀率高到要被产线投诉
漏检率压下来了,过杀率却一度冲到6%,被产线负责人拉着开了一次会。逐帧复盘后发现,大部分误报分布在中框边缘的加工纹理、灰尘颗粒和反光区域。这部分误报有很强的“分布偏移”特征:测试集里很少见,但实际上线后每个班次都会出现。
我的应对是三层结构。第一层,调整决策阈值:因为false positive和false negative的成本不同,不能用同一个0.5阈值,我们按代价矩阵算出了每类缺陷的最优阈值,划伤类用0.35,脏污类用0.6。第二层,加一个轻量的二次分类器,只针对TVA输出的候选缺陷区域做复核,专门学习“这是缺陷还是纹理”这种难分问题。第三层,也是最关键的:每周从产线回流一次误报样本进入训练集,持续滚动。两周之后,过杀率稳定在3%左右。
这里我想强调一个认知:工业检测的过杀率永远不可能降到零,因为它本质是“用可接受的人工成本换取不漏检”的工程取舍。你要做的是把过杀率压在产线能接受的范围内,而不是追求完美。
4.4 换料换线后模型掉点:不要从头重训
3C产线一个月内可能换好几次物料批次,不同批次的底色、光泽、加工纹理都有细微差异。第一次换料后我们发现,模型在旧批次上好好的,在新批次上漏检率直接翻倍。
解决问题分三步。第一步是“域对齐”:换料当天安排专人多角度采集两到三百张覆盖各缺陷类型的新样本。第二步是“轻量微调”:不让整个模型都动,只微调最后的融合权重和分割头,主干的Transformer层全部冻结。这一步很关键,冻结主干可以避免模型过拟合到新批次的偶发特征,旧批次精度也不会掉太多。第三步是“滚动更新”:把新旧批次样本按一定比例混合,再走一轮微调。
采用这套流程后,换料后的恢复时间从原来的一个多星期缩短到一天以内。如果你发现“冻结主干只微调头部”效果不好,那一定是前期主干没有预训练好,或者新旧批次差异过大,这时候才考虑用少量新数据对主干做低学习率的二次预热,但不要直接全参重训。
4.5 显存或推理时间吃紧:蒸馏+剪枝的实操顺序
模型从预览到真正上线,需要的资源常常是骨感的。我们一开始跑原始尺寸模型,显存占用大,帧率上不去。最终的方案是先把大模型TVA-L蒸馏成TVA-S。蒸馏的教师与学生共享输入和特征空间,学生用教师输出的软标签做辅助监督,能比从零训练的小模型高出两到三个点。
蒸馏完成后,再按我前面说的“单头置零”方法做结构化剪枝,最后才进入推理后端做FP16与内核融合优化。这里顺序不能反,先剪枝后蒸馏会损失太多信息,先蒸馏再剪枝可以让小模型在剪枝后有更好的恢复能力。
硬件条件实在有限的话,我还有一个压箱底建议:把输入分辨率从512降到384。3C检测一年到头碰到的大多数缺陷,都是线状或块状缺陷,384分辨率损失的高频细节远没有想象中多,节拍却可能因此快30%。如果你的缺陷是0.1mm以下级别的微小颗粒,“降分辨率”这招就要非常谨慎,优先考虑蒸馏和剪枝。
最后分享一个我自己反复验证的经验
写到这里,我特别想说的是:做3C视觉检测,真正的门槛往往不是算法,而是“你觉得算法已经写完了,但其实数据还没有长好”的阶段。TVA只是帮我们突破了旧方案的上限,但真正让指标稳定下来的,永远是“缺陷样本回流、误报样本回流、批次变化跟踪”这条数据闭环。如果你打算在自己的项目里上TVA架构,我建议先花两周把采集和数据回流机制理顺,再回头训模型。数据闭环跑顺了,一个中等性能的模型也比“数据一团糟的SOTA模型”可靠得多。
下一篇如果还有机会,我想专门写写三把光源结构和频闪控制对检测模型的影响。那些看起来跟算法无关的“硬件小事”,往往才是模型能不能发挥出来的胜负手。