YOLO + SubspaceAD:一张良品图,检出所有未知缺陷
在工业质检这个圈子里摸爬滚打了几年,我越来越觉得传统的视觉检测方案到了一个瓶颈:已知缺陷能查,但新出现的缺陷形态往往要等客户投诉之后才后知后觉。这次我想聊聊一套我最近在模拟产线上完整跑通的方案——用YOLO做已知缺陷的定位分类,再用SubspaceAD做未知缺陷的兜底检出。最核心的亮点是,SubspaceAD这部分只需要良品图参与训练,也就是说,你手里只要有一批“没有缺陷”的样本,就能训练出一个能发现“所有不对劲”的检测器,特别适合那些缺陷样本极难收集的场景。
这套思路解决的问题很直接:产线上漏检的从来不只是“没见过的旧毛病”,更多的是“根本没想到会出现的的新毛病”。YOLO负责把已知的、已经标注过的缺陷快速找出来并分类,这是它的强项;而SubspaceAD负责捕捉那些偏离正常分布的任何异常,不管这个异常你之前有没有定义过。两者互补,才是真正覆盖了检测需求的全貌。
我先把结论放在前面:这套方案不是万能的,但它确实解决了我实际项目中最大的痛点——缺陷样本不足、未知缺陷频出、以及频繁重训模型带来的成本压力。如果你也在做表面缺陷检测、工业视觉质检,或者任何“正样本充足但负样本稀缺”的视觉场景,这篇文章值得你认真看一下。
1. 方案整体设计与思路拆解
1.1 为什么单独用YOLO或单独用异常检测都不够
先说YOLO。YOLO系列在目标检测领域的表现不用我多说,工业缺陷检测里它也是主力选手。它能做精准的定位、分类,甚至输出置信度让你做后续分拣决策。但它有个天然短板:只能检测它见过的、被标注过的缺陷类别。一旦出现一种新的缺陷形态,比如某天镀层工艺波动导致出现了一种从未见过的鱼鳞状纹理,模型压根理解不了,它只会把这块区域当作背景忽略掉。
反过来,单独用异常检测(比如PatchCore、PaDiM这类)也不是不行。它们只学良品分布,理论上任何偏离都能测出来。但它们的问题在于:只能告诉你“这里不对”,很难告诉你“这里是什么毛病”——而这恰恰是工厂里工程师最需要的。操作员看到报警,如果不知道具体是划伤还是脏污,没法快速处置。
所以把两者组合起来才是正解。YOLO解决“已知缺陷是什么、在哪里”,SubspaceAD解决“这里有没有未知的不对劲”。前者是监督学习的强项,后者是无监督/自监督的强项。在实际产线场景下,让两套系统并行推理然后融合结果,比单独用任何一套都稳。
1.2 SubspaceAD选型背后的逻辑
SubspaceAD本质上是用子空间建模来刻画“良品图像”的内在分布。它的核心假设是:所有良品样本虽然在像素层面看起来各不相同,但它们其实都分布在一个相对低维的子空间里。而任何缺陷,不管是什么形态,本质上都是对良品子空间的一种偏离——投影到子空间之后,重构误差会显著变大。
讲个生活化的类比:你所在公司的员工都穿统一的制服,这套制服的款式、颜色、尺码变化其实很有限,你可以把它想象成“员工外观子空间”。有一天突然有人穿了一件大红色冲锋衣出现在办公室,你一眼就能发现他不对劲——不是因为你见过他的冲锋衣,而是因为他偏离了你对“办公室员工应该穿什么”的整体认知。SubspaceAD就是把这个“整体认知”学出来,用重构误差来度量偏离程度。
选择SubspaceAD而不是其他异常检测算法,我当时的考量是:它足够简单、足够快、而且无需复杂调参。相比基于特征嵌入的深度异常检测方法,SubspaceAD在推理阶段的计算量很低,不需要额外加载一个巨大的骨干网络,非常适合部署在已有的YOLO检测系统旁边。
2. 核心细节解析:SubspaceAD到底是怎么工作的
2.1 子空间建模的两条路线
我在落地时验证过两种子空间建模路线,效果各有侧重。第一种是PCA主成分分析路线:把每一张良品图拉平成一个高维向量,堆叠后形成矩阵,然后做SVD分解,取前k个主成分作为子空间基。推理时,把待测图像向量投影到这个子空间里,再反向重构回去,计算原始向量与重构向量之间的欧氏距离,也就是重构误差。误差大,说明这个样本有很多成分无法用良品子空间解释,视为异常。
第二种是字典学习路线,比如KSVD或者在线字典学习。它不限定基必须是正交的,而是学习一个过完备的字典,让良品样本能被稀疏编码。推理时的思路和PCA类似,还是算重构误差。字典学习的表达能力比PCA强一些,能捕捉更复杂的纹理变化,但训练时间更长,参数更多,工程上的可控性差一些。
我最终选择的是PCA路线作为主方案,原因很直接:产线上良品图数量大、变化复杂,PCA的训练时间可控,子空间基向量可以直接离线算好、推理时只需要矩阵乘法,性能和稳定性都更有保证。这里要特别说明,这是基于常见实践的合理选型,如果你面对的纹理特别复杂,字典学习路线值得一试,但要做好调参的心理准备。
2.2 重构误差的具体计算过程与参数选择
具体操作是这样的。假设训练集中有n张良品图,每张图缩放到统一尺寸w x h,灰度化后拉平为d维向量(d = w * h)。所有训练向量组成矩阵X,形状是n x d。在实践里n一般取2000到5000张,d可能高达数十万,所以必须先做中心化,再通过SVD或随机SVD算出前k个主成分。
k的选择很关键。k太小,子空间表达能力不足,良品本身的重构误差就大,误报率飙升;k太大,子空间太宽松,一些细微缺陷也被“解释”掉了,漏报率升高。我的经验是从累计方差贡献率入手,一般取到95%到99%之间的拐点。实际操作中我习惯做一次扫描实验:用一小批良品验证集,画出“重构误差阈值 vs 误报率”的曲线,选一个既保证良品通过率又不会让缺陷样本溜过去的k值。
推理阶段的计算过程如下:给定一张测试图,同样缩放灰度化拉成向量x,减去训练集均值mu,然后做两次矩阵乘法:先投影到子空间,得到低维系数,再重构回像素空间,得到x_hat。重构误差用欧氏距离除以维度数归一化,作为异常分数。归一化这一步很容易被忽略,但如果不做,图像尺寸一大误差值就会水涨船高,阈值没法在不同尺寸间迁移。
2.3 阈值怎么定才靠谱
重构误差的阈值设定是整个SubspaceAD部分最需要经验的地方。很多同学一开始会用“所有训练良品重构误差的最大值”作为阈值,这在训练数据足够纯净的情况下勉强可行,但一旦训练集里混入几张轻微异常图,阈值就被污染了。
我更推荐的做法是统计方法:对训练集中每一张良品图计算重构误差,得到一个误差分布的均值和标准差,然后设定阈值为均值加上3到5倍标准差。这样做的好处是天然给出了置信度概念,便于产线管理人员理解和调整。实际操作中,我还会把验证集里的人工标注缺陷图混进去测一遍ROC曲线,把“误报率1%时对应的重构误差”作为更严格的兜底阈值。产线上出现报警后,我会把误报样本收集回来,定期用新数据微调均值和方差,让阈值缓慢漂移适应环境变化。
3. 实操过程与核心环节实现
3.1 整体处理流程梳理
整个流水线我把它划分为四个阶段:数据准备与预处理、YOLO模型训练、SubspaceAD构建、结果融合。数据准备是所有环节的地基,这块出问题后面全白搭。
阶段一的数据准备,核心目标就是产出两批数据:一批是已经标注好已知缺陷类别的YOLO训练集,另一批是纯良品的SubspaceAD训练集。我要求良品图必须覆盖正常工况下的所有颜色波动、光照变化和纹理差异,这一点远比数量重要。产线上常见的诈骗式做法是只挑“最干净最理想”的图当良品,结果模型会把正常的环境变化误判成缺陷。
阶段二的YOLO模型训练,我用的是一套轻量化的检测框架,输入尺寸设成640x640,重点在于数据增强策略要用对。工业缺陷有个特点:小目标多、长宽比极端、样本量少。所以我开了Mosaic增强、CopyPaste增强,还把旋转角度范围限制在正负10度以内,避免增强出来的样本偏离真实物理形态。训练轮数不用太多,早停机制开着,验证集mAP到0.85以上就够用了。
阶段三是SubspaceAD构建,步骤很明确:统一图像尺寸到256x256,灰度化,堆叠训练矩阵,SVD分解,保存均值和主成分矩阵。这一步我用离线脚本跑,跑完保存成npz文件,推理时直接加载。实测下来整个训练过程在普通工作站上不到10分钟,这也是我推荐PCA路线的一个重要原因。
阶段四是推理时的融合策略,在实时检测进程里同时跑YOLO推理和SubspaceAD推理,然后按一定规则合并结果。关于融合,详见下文的详细说明。
3.2 YOLO部分的关键训练经验
关于YOLO部分,我想多写几句实操细节,因为这些细节普通文档里不会写。第一点是标注框的规范。工业缺陷的边界往往是渐变的,标注人员很容易把框画大或者画小。我要求所有标注人员遵循“紧贴缺陷核心区域,不包含正常纹理”的原则,并且对同一批图片做交叉复核。标注质量直接决定YOLO的上限,后期发现模型学偏了,绝大多数原因都在标注上。
第二点是难负样本的处理。缺陷检测场景中“像缺陷但不是缺陷”的样本(比如正常纹理边缘、反光点、灰尘)特别容易造成误检。我会专门收集这类样本作为背景类加入训练集,标注为空背景,让YOLO学会不把它们当缺陷。这个操作效果立竿见影,能把误检率压下去一半以上。
第三点是推理时的置信度阈值。别动不动就调到0.5。工业场景下我更习惯把置信度设在0.25到0.35之间,靠后续的NMS和面积过滤来保证精确率。只要SubspaceAD在兜底,YOLO这边的几Hz误检完全可以通过下游规则过滤掉。
3.3 SubspaceAD的核心实现步骤
SubspaceAD的核心实现不复杂,但每一步都有坑。第一步是图像预处理。我这里用了灰度图而不是彩色图,原因有两个:一是工业产线上很多光源是单色光,颜色信息本来就有限;二是灰度图能直接压降特征维度,让子空间训练更稳定。如果你的场景中颜色差异很关键,可以保留三通道做PCA,但维度会激增,训练时间和内存都会上去,一般我会配合图像分块来用。
第二步是训练集构建。我开发了一套筛选脚本,先把采集到的图按时间顺序排好,剔除运动模糊、过曝和欠曝图,再人工快速浏览一遍确保没有明显缺陷混入。这里要提醒的是,异物灰尘和飞溅的油渍经常混在良品图里,人工筛选这步不能省。我见过很多项目图省事直接拿原始数据跑训练,结果模型学了灰尘的特征,反而把真正良品判成异常。
第三步是SVD分解与子空间保存。数据矩阵X做完中心化后,我用随机SVD来做分解加速。注意,内存管理是个关键问题。几千张256x256灰度图,拉平后是65536维,矩阵规模在几百GB级别,直接全量进内存是找死。我的做法是把数据分块读入,分批计算协方差矩阵或使用增量式SVD,只保存最终的主成分矩阵,这样就绕开了内存瓶颈。
第四步是异常分数计算与阈值设定。这块我在原理部分已经说过了,实操里还要注意一点:每张图的归一化系数要一致,比如都用像素数目的倒数。这样不同尺寸的图像之间,重构误差才具备可比性。
为了让你对训练阶段的时间有概念,我贴一组实测数据:2000张良品图,尺寸统一为256x256灰度图,取累计方差贡献率98%对应的子空间维度(实际大概在50到150之间),整套训练流程在CPU上跑大约8分钟,GPU上更快。推理阶段,单张图的重构误差计算只需要两次矩阵乘法和一次减法,能做到毫秒级,完全不影响实时性。
3.4 两级结果融合的策略
YOLO和SubspaceAD各自输出之后,融合策略直接决定整个系统的容错能力。我的方案分为四层:
第一层:YOLO检出的已知缺陷区域,如果置信度超过0.5,直接判定为缺陷,不再参与SubspaceAD的判断。这样保证已知缺陷的高精度分类能被完整保留下来。
第二层:YOLO置信度在0.25到0.5之间的候选框,让SubspaceAD对框内区域做二次判断,然后在高置信度下决定是否采纳。这个策略的核心价值是“让已知模型的犹豫去请教未知模型的判断”,最终决定不再只看一边的意见。
第三层:整张图的SubspaceAD全局异常分数如果超过阈值,表明图像里存在模型无法解释的异常。
第四层:把YOLO置信度低但SubspaceAD分数高的区域标记为“未知缺陷”,单独输出给人工复核。
这套融合逻辑的核心思想很简单:YOLO可信的时候听YOLO的,YOLO不确定的时候让SubspaceAD来裁判,两边都说没问题的区域才放行。
3.5 性能优化与部署方案
推理阶段我用的是一张普通的工业显卡,YOLO推理大概耗时12毫秒每张图,SubspaceAD的矩阵运算大概3毫秒每张图,两者加起来不到20毫秒,算上图像采集和预处理,整体节拍能稳定跟上产线每秒20帧左右的检测需求。
这里有一个特别值得优化的点:YOLO和SubspaceAD的输入尺寸是不一样的。如果SubspaceAD也直接用640x640的尺寸,维度高达40多万,矩阵乘法计算量大几倍,完全没有必要。我实测256x256已经能很好平衡重构误差的表达能力和计算量。另外,因为是灰度图,图像从YOLOSTream里直接转一份灰度缓存,就不需要再做一次完整的解码缩放流程。
4. 常见问题与排查技巧实录
4.1 光照变化导致误报怎么办
这是SubspaceAD落地最容易碰到的问题。产线环境的光照很难做到绝对恒定,灯管老化、外界光线渗入、甚至镜头上的灰尘都会导致整体亮度变化。这种全局性光照变化在重构误差上非常敏感,会产生大量假异常。
我的处理思路有两个:一是预处理阶段做光照校正,比如使用顶帽变换或者局部直方图均衡化,让图像的特征更聚焦在纹理差异而不是全局亮度上;二是在训练良品集时有意混入不同光照条件的样本,相当于让子空间“见过”光照的合理波动范围。实测下来,后者的效果更简单直接,前者的鲁棒性更好,建议两个一起用。
4.2 重构误差阈值总是飘
阈值飘的根源通常是环境漂移,就是产线在使用过程中,良品的正常形态本身在慢慢变化。比如设备的某块滤网逐渐堵塞,导致良品表面的纹理产生了微小变化,重构误差整体上移,固定阈值就会开始误报。
解决方案是设计一个“阈值自适应”机制:定期从产线上采集一批被YOLO和人工都判为良品的图,用它们更新重构误差分布的均值,让阈值跟着环境的慢变量一起漂移。我设置的是每2小时自动采样100张图重新计算均值和方差,既不会因为个别异常图污染统计量,也能持续跟踪环境变化。
4.3 已知缺陷和未知缺陷同时出现怎么办
当一张图上既有YOLO能识别的划痕,又有一块YOLO完全没见过的镀层脱落时,处理逻辑容易出错。如果只是简单“OR”两个结果,那没问题;但如果YOLO的置信度比较高,占了主导地位,SubspaceAD的未知缺陷提示就可能被流程忽略。
我的解决方式是在结果融合时区分区域。YOLO的候选框会先被抠出来,SubspaceAD对框覆盖区域之外的背景区再做全局检测。这样既不影响已知缺陷的分类,又能独立发现未知缺陷。若两个问题叠加出现在同一个区域,则以“未知缺陷”优先,因为这个识别难度更高,更需要人工介入确认。
4.4 小缺陷漏检问题
SubspaceAD另一个常被吐槽的问题是:极小的缺陷(比如几个像素的针孔、细微划痕)在整图重构误差上往往淹没在背景纹理的波动里。
针对这个痛点,我尝试过把图像切成若干重叠Patch分别做子空间检测,让局部异常在Patch尺度上的重构误差被放大,然后在还原坐标时做重叠融合。代价是推理时间会增加一倍左右,但换来的是小缺陷召回率明显提升。如果你所在产线特别看重微小缺陷,这个方向是值得投入的。
4.5 相同模型在不同产线之间复用
最后说一个工程复用问题。很多团队想把一套训练好的YOLO+SubspaceAD直接复制到另一条产线,但效果往往大打折扣。原因很简单:不同产线的光源角度、工作距离、产品材质都不一样,子空间形态自然不同。
我的建议是:YOLO模型可以在同行业跨产线微调复用,而SubspaceAD部分必须针对新产线重新用良品图训练。好在SubspaceAD的训练成本很低,在新产线开机投产的初期,花一两个小时收集良品图跑一遍流程,就能完成模型的快速迁移。这个低成本复用的特性,也是我推荐这套组合方案的原因之一。
按我个人实际踩坑几次之后的体会,这套系统最舒服的地方在于:你不需要为了“未知缺陷”去绞尽脑汁地标注样本,只需要把良品图这个现成的数据好好利用起来。产线上的新缺陷总是层出不穷,但只要我们掌握了“正常是什么样”,一切偏离正常的信号都能被感知。如果后续你想进一步提升,可以考虑把SubspaceAD的输出做成反馈信号,自动从产线上扒出疑似异常的小片段,分发给标注员快速打标,再反向补充进YOLO的训练集,让系统形成一个正向循环的自进化闭环。