制造业的产线上,“漏检”和“误检”这对冤家,一直是视觉工程师的心病。传统方案调来调去,往往按下葫芦浮起瓢。最近我在一个结构件表面检测项目里,彻底扔掉了“画框”这个拐杖,做成了一套不需要标注目标框的AI视觉检测系统,效果反而出奇的好。这套思路不是我的原创,但落地过程中的一些经验和踩过的坑,我觉得值得拿出来聊聊,特别是对于那些还在为标注成本发愁、或者被复杂规则折腾得焦头烂额的朋友,应该会有直接的帮助。
1. 为什么我决定“不画框”来检测缺陷
1.1 传统缺陷检测的别扭之处:框在哪,质量就死在哪儿
先说个扎心的现实。很多刚接触AI视觉的工程师,以为缺陷检测就是把照片丢给模型,模型自动告诉你“这里坏了”。结果一做项目才发现,第一步就卡在标注上。得有人用标注工具,把每一张缺陷图上的瑕疵区域框出来,框得越准,模型学得越好。听起来没问题对吧?但产线上的缺陷千奇百怪,裂纹是细长的,凹坑是不规则的,划痕是弯曲的,有的缺陷颜色和背景几乎一样,肉眼都得凑近了才分得清。
在这种情况下,标注人员的水平直接决定了模型的天花板。我见过一个项目,标注员为了赶进度,把一条很长的划痕断成了三截,只框了中间最明显的部分。模型训练完,肉眼可见的漏检。还有更致命的,某些缺陷本身就很难定义边界。比如注塑件上的轻微流痕,你说它算不算缺陷?它影响功能吗?不一定。但客户就是觉得难看。让标注员去框这种“感觉”,他只能凭直觉发挥,结果就是一批数据一个风格,模型训练出来妥妥的“精神分裂”。
所以,“框”这个动作,不仅消耗人力,还把缺陷的先验知识(位置、形状)强行灌给了模型。这导致模型的泛化能力很差,换一条产线、换一种光照,之前的标注基本作废。而且,越是依赖框,标注的共识成本就越高。让三个工程师分别给同一张图的缺陷画框,画出来的IoU能到0.7就算烧高香了。模型学着一堆充满分歧的标签,它不懵才怪。
1.2 一次尝试的缘起:被“漏检”逼出的新思路
我们当时接的是一家做精密结构件的客户需求,要检测加工表面的微小划痕和压伤,产量大、节拍快。按照老办法,先拉了半个月的图像,找了三个标注员,吭哧吭哧框了两周,加起来两万多张图。结果一上测试集,漏检率居高不下。客户那边催得急,产线上每天的QC(质检员)倒班都顶不住了。
我那会儿就在琢磨,有没有一种办法,能绕开“画框”这个流程,直接从图像本身去学习“什么是正常的”。只要学会了正常样本的样子,不正常的东西一出现,它自己就会报警,而且不需要告诉它具体哪儿不正常。这个思路往深了想,其实就是不定义缺陷本身,而是定义“正常”。这比定义缺陷要容易得多,因为正常样本到处都是,采集成本极低。
于是我就开始动手验证,最初用的不是多复杂的深度学习模型,就是把一堆正常的灰度图拿去训练一个自编码器。想法很简单,如果模型只见过正常样本,那么遇到缺陷样本时,它的重建误差(Reconstruction Error)应该会显著变大。实验结果还真有那个趋势。缺陷区域确实在误差图上亮了起来,但背景纹理的干扰也大得吓人。不过至少验证了方向可行:不画框,也能揪出异常。
2. 核心思路拆解:缺陷检测的本质是“找不同”
2.1 为什么“找不同”比“认缺陷”更实际
我们换个角度来看。画框检测,本质上是“认缺陷”,是让模型去学习某一类缺陷的视觉特征。比如学会“划痕”的样子,那就得给它看几百张画了框的“划痕”图。问题在于,产线上的缺陷是无穷无尽的,你可以标注出常见的几十类,但总会有一种新形态的缺陷,训练的时候没见过,模型就会漏检。
不画框的方案,换了个角度,把缺陷检测变成了“找不同”。我不需要知道缺陷长什么样,我只需要知道“正常”长什么样。正常的表面的纹理、光泽、颜色,在理想状态下是有规律的。一旦出现缺陷,不管它是什么类型,它在空间分布上一定会打破这种规律,在像素层面产生局部突变。模型只要足够敏感,就能捕捉到这种突变。
这个思路在工业场景里特别实用。因为大多数生产线上,正常产品的数量是远大于缺陷产品的,正常样本的占比一般在90%以上。用这些干净的正常样本来训练模型,数据采集和清洗的难度很低。而“画框”方案里,往往还需要做大量的数据增强、难点样本挖掘,就是为了对抗样本的多样性。不画框方案一上手,你会发现数据准备的工作量直接降了一个维度。
2.2 技术路线的取舍:从特征嵌入到重建误差,哪个更靠谱
确定了“找不同”的方向,具体实现上有几种路线,我给大家做个对比。
第一种是重建误差路线,典型代表就是自编码器和生成对抗网络的变体。模型吃一张图,吐一张图,目标是让输出逼近输入。正常样本重建得很好,缺陷样本重建不出来,就会在误差图上“现形”。这种方案部署简单,模型轻量,但有个天生的问题:如果模型学习能力太强,它可能连缺陷也一起重建出来了,导致漏检。所以在训练时得控制模型的表达容量,但这又容易欠拟合。
第二种是特征嵌入路线,代表算法是PatchCore。这种方案不直接重建像素,而是把图像切分成一个个小块,用预训练的卷积神经网络把这些小块提取成特征向量。正常样本的特征会被存进一个“记忆库”里。推理时,把测试图像的小块也提取特征,然后去记忆库里找它最相似的特征,如果相似度不够高,就判定为缺陷。这个方案现在的鲁棒性最好,对纹理背景的干扰抵抗力很强,我最终选择的就是这条路线。
第三种是基于知识蒸馏的Teacher-Student方案。用一个大模型当老师,一个小模型当学生,两个都看同一张图。老师预测的结果和学生的预测结果做对比,正常情况下它们应该一致,一旦出现缺陷,学生模型就会预测错乱,和老师的输出产生差异,差异图就是缺陷图。
这三种方案我实际都跑过一轮对比,测试下来,PatchCore的稳定性明显优于重建方案,准确率也更高,知识蒸馏的调参成本会高一些,适合有充足时间打磨的团队。对于多数制造业客户,我偏向前两种的实用主义路线。
2.3 无监督异常检测在三种场景中的适配性
- 场景A:表面纹理检测(金属拉丝、磨砂面)。这种场景最难,因为正常纹理本身就有随机性。用画框方案,框都框不准。我的经验是必须用PatchCore,并且特征提取网络得选大一点的,才能捕捉到细微的纹理异常。
- 场景B:规则表面检测(玻璃面板、塑料外壳)。这种场景相对简单,正常表面高度一致。自编码器就能胜任,因为背景干扰小,重建误差很容易凸显缺陷。推荐先用自编码器跑一版,成本低、速度快。
- 场景C:结构件轮廓检测(螺丝、卡扣、密封圈)。缺陷往往发生在边缘和角落,这里的上下文信息很关键。画框方案喜欢把边缘截断,导致模型学不到完整轮廓。而无监督方案能更好利用全局上下文。
3. 实操过程:从零搭建一套“无框”缺陷检测系统
3.1 数据准备:先干三件脏活累活
数据是地基,虽然不用画框了,但预处理绝对马虎不得。我总结为三个步骤:清洗、裁切、归一化。
第一,清洗。这是所有步骤里最费人的。一张图拍摄的角度偏了,光线反光了,甚至料件上沾了手指印,这都算“正常”还是“异常”?如果手指印被当成正常样本喂给模型,模型就会学会容忍手印,那是灾难。所以清洗的原则是:肉眼看不出来的异常,要当正常样本处理;肉眼能看出非表面缺陷的干扰,要直接剔除。
第二,裁切。产线上拍出来的原图往往是几百像素见方的区域,缺陷可能只占几个像素。直接把全图丢给模型,计算量太大,而且严重的背景干扰会淹没缺陷特征。我习惯按感兴趣区域做裁切,把有效检测区切出来,尽量让正常纹理在图中占比高,这样模型学得更专注。每个区域的尺寸尽量保持一致,避免模型学到尺度差异。
第三,归一化。工业相机的成像受光源影响非常大,不同批次可能存在亮度波动。如果不做归一化,模型就会去适应亮度的变化,真正的缺陷特征反而学不到。我采用的是简单的均值和方差归一化,把图像像素值缩放到标准范围,实测对于消除光源波动影响有明显帮助。
3.2 环境配置与核心模型选型
硬件方面,我用的是单张中高端显卡,操作系统用的常见发行版,软件栈就是标准的深度学习框架加开源的异常检测工具库。这套组合跑PatchCore非常流畅,训练百来张正常样本的图像只要几分钟,推理一张图在GPU上不超过几十毫秒,完全满足在线检测的节拍需求。
PatchCore的核心思路,我刚才提过一嘴,这里再展开。它把输入图搞成很多重叠的图像块,用预训练好的特征提取网络把这些块映射成向量。正常样本的全部向量都存下来,构建成一个“记忆库”。但库里向量数量太多,会拖慢推理速度,所以PatchCore还内置了一个贪心采样算法,挑出最有代表性的那部分向量,大幅压缩记忆库的体积,推理速度能快好几倍,同时精度损失却几乎可以忽略。
它最终的预测,也很有实操价值。推理时,输入图像的每个块都生成一个特征向量,去记忆库里找最接近的邻居,算一下距离。这个距离大于某个阈值,就认为它是region anomaly(区域异常)。把所有异常区域合在一起,还能得到一张像素级的异常得分图,直接转成热力图叠加在原图上,定位缺陷位置,非常直观。
3.3 让参数更实用的调校细节:阈值、核心尺寸和特征选择
很多朋友第一次跑通模型之后,最头疼的就是阈值设置。其实阈值设置之前,有几个更底层的参数要调好。
一是图像块的尺寸。PatchCore的核心尺寸参数直接决定特征的尺度。缺陷比较小,比如几个像素的麻点,就得用小一点的块,比如16像素或者32像素。缺陷比较细长,比如划痕,就得适当放大块尺寸,比如用96像素甚至128像素,让块内同时包含划痕和周边的正常纹理作为背景上下文,这样异常更容易凸显。这个参数需要对比实测来选,我一般会拿一批验证集试几个候选值,看哪组能让正常样本和缺陷样本的得分分布分得最开。
二是特征池的构建方式。正常样本尽量涵盖产线可能出现的所有正常状态。有时候同一种产品会切换不同工序,表面粗糙度可能会有差异,这种差异属于正常波动,但模型分不清,就会把它当成异常。所以采集数据时,尽量把不同工序状态下的正常样本都塞进去,让记忆库足够丰富。
三是阈值设定。阈值不是拿着验证集跑出来的,而是要到现场去试出来的,而且要多试几轮。因为真实产线上的光照、振动都会影响得分分布。我习惯的做法是,先在离线数据集上算一个初始阈值,然后装到产线测试,记录误报情况,再根据实时数据回馈来微调。有时候上下浮动0.5个得分,误报率就能差出一个数量级。
3.4 推理部署:如何把模型塞进产线系统里
模型训好后,下一步就是部署。工业现场不比实验室,电脑配置参差不齐,有的甚至还是老式工控机,没有独立显卡。针对这种情况,我会把模型做一次集中化处理,就是用推理加速框架把模型封装成接口,生成动态链接库。这样就能绕开原始深度学习框架的环境依赖,一个工厂里几十台电脑,不用每台都折腾配置环境,直接拷贝文件、注册组件、调用DLL接口就行。
接口的输入输出也很讲究。输入就是一张BMP格式的灰度图或者彩色图,输出是一个包含得分、坐标信息的数据结构。得分是全局异常分数,坐标是异常区域的中心点。这样,工控机上的上位机软件只要调用接口,传一张图进去,就能拿到结果,然后在界面上画个红框报警。
部署流程里,我总结出几个容易踩坑的点:
- 模型输出彩色图还是灰度图,一定要和训练时保持一致,否则精度直接掉一截。
- 接口的输入尺寸要固定,因为实际产线上的源码图和裁切图的尺寸可能不一样,如果是动态尺寸输入,每个尺寸都得重新适配,麻烦且易错。所以我习惯在训练时就把输入尺寸固定到某一个值,推理时也一样,多出来的像素直接填灰度值,不参与计算。
- 工控机的CPU版本推理速度如果跟不上,最简单的优化是把输入尺寸再缩小一档,或者换用轻量级骨干网络。我实测过,把输入尺寸从512降到384,推理时间能缩短三分之一,精度损失在可接受范围内。
4. 常见问题与排查技巧实录
4.1 误报压不下来?先别急,多半是“正常”没学全
误报是这类系统的头号敌人。每次信号一响,产线工人跑过去一看,啥事没有,多折腾几回,工人就不信这个系统了,直接把它当摆设。排查思路不要盯着模型,要先问:这个“正常”的状态,模型真的见过吗?
有一次客户反映一个产品在某个固定位置总是误报,我过去一看,原来那个位置贴了一张小小的标签纸,是印刷上去的序列号。正常产品都有的东西,但我们的正常训练集里恰好没怎么覆盖。就这么一个疏漏,导致所有带标签的样本都变成了“异常”。后来我在采集阶段就默默记下一本账:正常样本里必须覆盖所有的固定特征,包括文字、标签、定位孔、按钮区域。这样做之后,误报率立刻下去了。所以遇到误报,先回去翻训练集,比调阈值管用得多。
4.2 真的缺陷漏过去了?可能是“块头”没对好
漏检比误报更让人头疼,因为你不知道什么时候会漏。有一次我们检测一种细长的裂纹,长度跨度很大,有的裂纹只有几十个像素,有的甚至贯穿整个检测区。我一开始用默认的块尺寸去训练,结果长裂纹漏检严重,因为长裂纹的图像块在特征空间里,更像是一条“正常的边缘过渡”,不像缺陷。后来我把块尺寸调大,让它能看到裂纹旁边的正常背景,特征对比一下就出来了,漏检率终于降下来。
所以,如果你发现某种缺陷一直漏检,优先怀疑是不是图像块的感受野太小,看不清“全局”。还有一个细节,裂纹的方向也有影响,水平和垂直方向的检测难度不一样。最好在训练数据里多放一些各个方向的样本。
4.3 现场内存爆炸?这是高分图像带来的隐性问题
工业相机现在分辨率越来越高,有的直接给到1200万像素甚至更高。如果训练和推理时直接用全分辨率图,内存和显存都会撑不住,推理速度也惨不忍睹。
我做过一次挑战,用的是一台只有16GB内存的工控机,跑1200万像素的图。直接把全图塞进模型,内存直接爆掉。后来把检测区裁切出来,只针对关键表面做检测,内存占用降到了合理范围,推理速度也回到了满足产线节拍的水平。我的建议是,不要迷信高分辨率。在保证缺陷被分辨出来的前提下,尽量压缩图像尺寸或者裁出关键区域。
4.4 各类异常模式的排查速查表
| 常见问题 | 优先排查方向 | 推荐处理手段 |
|---|---|---|
| 误报集中在某个固定区域 | 该区域存在正常标记、定位孔或固定特征 | 扩充该区域正常样本;或将区域排除出检测范围 |
| 细小点状缺陷漏检 | 图像块感受野过大,纹理特征被平均化 | 调小块尺寸,例如从128降到64或32 |
| 长条裂纹缺陷漏检 | 块感受野过小,缺乏背景上下文 | 调大块尺寸,例如从32升到96或128 |
| 光照变化引发误报 | 归一化参数失效或光源老化 | 重新采集各光照条件下的正常样本,调节归一化参数 |
| 推理速度不达标 | 输入尺寸过大、特征库冗余 | 按比例缩小输入尺寸;精简记忆库规模,减少检索量 |
| 新换产线立即误报 | 训练集与现场状态不匹配 | 用少量现场样本进行微调,重新训练并更新记忆库 |
提示:排查时不要“头痛医头”,先花时间搞清楚问题是数据问题、特征尺度问题,还是阈值问题,方向对了才有效。
5. 工具选型解析与经验心得
5.1 框架怎么挑,能少走一半弯路
异常的检测工具有个特别好的地方,就是大部分研究已经开源了,而且还在持续更新。我在好几个项目里都是直接使用开源框架作为基础,它集成了各种主流的异常检测算法,包括无监督、半监督以及更前沿的基于大模型的方案。调用方式很友好,而且踩过坑的工程师们已经把很多教程写得比较清楚,上手成本比从零写模型低太多。
我个人的排序建议:如果追求结果稳定,先上PatchCore;如果想轻量快速验证,先用自编码器;如果团队有算法专家,可以考虑探索基于变换器的最新方案,但对绝大多数现场项目来说,传统的算法已经足够用。先进不一定好用,稳才实用。
5.2 关于“不画框”方案,我的几点体会
这套方案已经帮我在三四个行业的检测项目里落过地,包括金属件外观、塑料件外观、纺织品表面。整体来看,交付效率确实提升了,项目的确认周期明显缩短。但也要说点实话,它并不是万能的。
它最适合的,还是表面纹理均匀、正常状态相对单一的场景。如果产品本身设计就非常复杂,每当一个不同型号上线,就需要重新采集正常样本更新记忆库,维护成本会显著增加。而且,它只能告诉你“哪里不一样”,不太擅长告诉你“这个不一样严不严重”。最终判定是否合格的规则,还是要靠阈值来定,这个需要客户和你慢慢打磨。
但我的另一个重要体会是,这个方案极大改变了项目交付各方的协作关系。以前调模型要反复催客户标注,客户还总是标注得不符合规范;现在采集一批正常样本,一个下午就能搞定,客户省事,我也省心。而且实际上因为减少了人工干预,客观性更强了,客户对检测结果的信任度反而提升了不少。
6. 写在最后的几个小建议
如果你正准备用这个思路去做产品,别急着一次就追求完美。我的做法是分两步走:第一步,搞明白产品到底有多少“正常状态”,用一周时间把正常样本攒足,宁多勿缺;第二步,先上最简单的自编码器验证流程,把检测界面、报警逻辑、数据回传这些外围环节跑通,再换PatchCore做精度优化。这样整个交付节奏会从容很多。
另外,不管用什么方案,现场的抗干扰设计永远别忽略。光源晃一下、相机抖动一下,都会导致成像波动。模型再强也扛不住输入剧烈变化。所以硬件固定好后,尽量加装遮光罩和减振装置,这些细节对检测准确率的影响,很可能比十个好的算法参数还大。
这套“不画框”的方案,说到底就是一场思路转换:与其费劲教模型认识每一种“错”,不如让模型清楚地知道什么才是“对”。只要抓住了“正常”,那些不速之客自然原形毕露。