聊工业异常检测,“不需要Memory Bank”、“2D+3D双分支重建”这几个词放在一起,确实值得好好聊聊。这几年异常检测被各种“检索式”方法统治,动不动就搞一个特征库存着,跑起来又慢又费内存。CMDR-IAD这套方案直接换了个玩法,用重建的思路,把RGB图像和3D点云同时送进两个分支,做交叉重建来逼模型记住“正常长什么样”,最后在MVTec 3D上拿到97.3%的对象级AUROC。这个成绩放在3D异常检测里属于很能打的水平了,而且彻底甩掉了Memory Bank的包袱。
这篇文章我会把这个方案从设计逻辑到实操细节完整拆一遍,重点讲清楚为什么重建能替代记忆库、双分支到底怎么配合、MVTec 3D上97.3%是怎么一步步跑出来的,以及我在实际调模型时踩过的一些坑。适合正在做工业视觉质检、想引入3D数据做异常检测,或者对无监督深度学习方案选型有需求的朋友参考。
1. 为什么甩掉Memory Bank:重建范式的新选择
1.1 Memory Bank的包袱在哪里
先说说现在工业异常检测领域最主流的做法。大部分效果不错的方法走的是“先提特征,再找参考”的路线:把大量正常样本图片丢进一个预训练好的特征提取网络,把中间层输出的高维特征全部存下来,构建一个叫做“记忆库”的特征集合。推理的时候,把待检测图片的特征提出来,到这个大库里去检索最相似的几个特征,算一个距离分数,距离越大就越可能是异常。
听起来很合理,逻辑也直观。但实际落地的时候,这个Memory Bank的代价会随着产线数据量直线上升。你要是在产线上跑,一个型号的产品可能有几万张正常样本图,每张图还得分多个层提特征,库的大小轻松上GB级别。特征多了检索变慢,特征少了又覆盖不住正常的各种形态变化,这就是一个需要不停权衡的麻烦事。而且,库里的特征质量直接取决于预训练网络在工业场景上的泛化能力,一旦产品换了个颜色、换了个光线环境,整个库可能就得重建。
我当时在一个模拟项目里测过这类方法,最头疼的就是存储和更新问题。产线新增数据是常态,不可能每次都全量重训,但增量更新特征库又会引入分布偏移。Memory Bank的路线不是不好,是不太适合做成一个“轻量、可维护”的检测系统。
1.2 重建范式凭什么更省心
CMDR-IAD走的是另一条路:我不存任何东西,模型只学“怎么把正常样本还原出来”。核心逻辑是,一个训练充分的解码器只能很好地重建出它见过的正常流形;遇到没见过的异常输入,重建结果会和原图产生明显差异,这个差异就是异常分数。
这个思路有一个天然优势:不需要额外存储,模型权重固定下来之后就一个文件,部署的时候干干净净。我比较看重的是它在增量场景下的表现——来了一批新的正常样本,可以在原模型基础上做小学习率的微调,不需要像维护特征库那样去重算所有样本的特征。
当然,重建范式也有它的老毛病,最大的问题是网络容量如果太大,它能把异常区域也“脑补”成正常的样子,重建出来和正常样本没区别,这时候就拿不到异常分数了。所以怎么限制重建网络的泛化能力,让它“不该重建的别乱重建”,是整个方案设计里最核心的问题。
1.3 重建的关键矛盾:模型太强反而坏事
这里稍微展开说下这个矛盾。假设你训练一个U-Net去重建正常样本,网络学到的只是正常样本的压缩表示。如果网络很小,瓶颈很窄,那它只能保留最共性的信息,异常细节会被丢弃,重建误差自然就大,检测效果好。但代价是正常样本里一些合理的变化也被丢掉了,误报率会上升。
反过来,如果网络很深、很宽,或者干脆用了一个表达能力极强的Transformer,那它有能力把任何输入都近乎无损地重建出来——这意味着异常也逃不过它的“想象力”,检测就失效了。
所以,重建方法的功力全在“怎么让网络在正常样本上重建得好,在异常样本上重建得差”这个剪刀差上。CMDR-IAD解决这个问题的思路很有意思:单个模态容易“自圆其说”,那我就用两个模态互相监督、互相制约。
2. CMDR-IAD的核心思路与设计拆解
2.1 双分支架构:2D和3D各干什么
MVTec 3D这个数据集里,每个样本都同时包含一张RGB图像和对应的3D点云。这正好给了双分支一个很好的舞台。
CMDR-IAD的架构基本可以理解为:一个2D分支负责处理RGB图像,一个3D分支负责处理点云。2D分支可以沿用成熟的2D网络结构,比如带有编码器-解码器结构的卷积网络,主干用ResNet或者更轻量的结构都行,关键是瓶颈处不能太大。3D分支则要处理点云这种无序、稀疏的数据,常见的做法是PointNet++或者基于稀疏卷积的结构。这两个分支不是各干各的,而是共享一部分隐空间信息。
我当时第一次看这个设计的时候,觉得最巧妙的地方在于:2D图像信息稠密、纹理丰富,对表面划痕、污渍这类异常很敏感;3D点云信息稀疏但在几何结构上精确,对凹陷、凸起、变形这类几何异常一抓一个准。两者天然互补。只做单模态检测,总会有漏检:一张纯色金属表面的小凹坑,2D图像上看可能颜色差异极小,但3D几何上一眼就看得出来。
2.2 交叉重建:让两个分支互相约束
双分支重建方案里,最简单粗暴的做法是让两个分支各自重建自己的输入,也就是2D分支重建RGB图,3D分支重建点云坐标/特征。但这样做的隐患就是前文说的问题——单分支表达能力一强,异常也能被重建得很好。
CMDR-IAD的做法是引入交叉重建。具体来说,训练时2D分支不仅要把RGB图重建出来,还要从RGB特征中预测出对应的3D特征;同理,3D分支也要从点云特征中还原出2D特征。两个分支相当于互为“老师”,你不仅要会重建自己看到的模态,还要能“翻译”成另一个模态的样子。
这样做的好处非常明显。一个异常如果在2D图像上比较隐蔽,但3D几何异常明显,那么2D分支在尝试预测3D特征时就会力不从心,误差自然变大。反之亦然。交叉重建形成了一个双向约束,迫使两个分支学到的是真正有判别力的正常流形特征,而不是各自动一动就糊弄过去的表面重建。我理解这比单纯限制瓶颈宽度要有效得多——因为这是从“信息一致性”层面限制模型的自由度。
具体实现上,可以在两个分支的编码器输出处加一个特征对齐模块,把2D特征和3D特征映射到同一个隐空间,然后用一个解码器分别还原两个模态的目标。猜3D特征时不需要输出稠密点云那么苛刻,可以预测点云特征图或者一组降采样的几何描述子,降低学习难度。
2.3 融合判决:异常分数怎么算
推理阶段,2D分支产生一个逐像素的重建误差图,3D分支产生一个逐区域的几何重建误差。最终要把它们融合成一个统一的异常分数。
这里有一个取舍问题:直接相加取平均是最简单的方法,但实际效果不一定好,因为2D误差和3D误差的量纲、分布差异可能很大,直接加会导致某一个模态主导整个结果。更稳一点的做法是分别对两个误差图做归一化,再按加权和融合。权重可以简单地在验证集上调,也可以根据类别自适应。
融合之后,通常取空间维度的最大值作为样本级分数,用于计算对象级AUROC;同时保留完整误差图用于像素级或区域级分割评估。这个设计我建议在验证集上多观察几个case,不要只看AUROC这个数字。因为AUROC高不一定代表定位准,有时候误差图最大值定位准确,但平均值不高,这时候用最大值做样本分数效果更好;反过来,如果误差图噪声大,最大值会被离群噪点污染,那就得先做平滑再取最大值。
3. 从MVTec 3D到现场流水线:实操记录
3.1 数据集与预处理细节
MVTec 3D数据集覆盖了像瓶盖、金属螺母、电子元件这类典型工业场景,每个类别都有正常样本用于训练,测试集里则混入了各种类型的异常。用这个数据集测方案有一个很大的好处:异常类型丰富,2D和3D异常都有。有的类别异常主要集中在表面纹理变化,2D分支起主要作用;有的类别异常主要是几何变形,3D分支就特别关键。跑完整个集合并看分项结果,能非常直观地感受到双分支各自的贡献。
预处理是最容易出问题但也最容易出效果的一步。点云数据一般要经过体素降采样、离群点去除、坐标归一化这几个处理。我用的是先把原始点云统一降采样到约16384个点,再以质心为中心做平移归一化。这一步不能省,因为原始点云点数差异巨大,不动点数直接进网络会导致batch总点数不稳定,训练效率极低。
RGB图部分,MVTec 3D里的图像尺寸不一,我统一resize到512x512。要注意别简单粗暴地直接resize,最好保持长宽比,多余部分用填充处理,不然几何形状会被压变形,和3D点云对齐时会引入不必要的误差。2D和3D对齐在预处理阶段就要做好,如果相机参数已知,直接用外参把点云投到2D图像上生成对应的深度图或轮廓mask;如果参数不全,也可以粗略地用点云质心投影做对齐,但对精度要求高的场景还是建议先做预配准。
3.2 训练配置与损失函数
我先从比较稳的配置说起。整个网络我用Adam优化器,初始学习率设在1e-3到3e-3之间,配合一个线性warmup再加余弦退火。batch size方面,因为3D分支吃显存,单卡的话建议batch size设在4到8之间,同时开启混合精度训练,这样可以省下接近一半显存。训练轮数在MVTec 3D上大约100轮左右就能看到收敛迹象,但最快的还是先跑通一个类别调好超参,再大规模跑全部类别。
损失函数是这套方案的重头戏。我用的版本是三部分组成:
- 2D重建损失:计算重建RGB图和原图之间的L1加SSIM组合损失,这个组合能既照顾像素级精度,又保留结构细节。
- 3D重建损失:计算预测的几何特征和真实点云特征之间的L2距离。这里建议用Charbonnier损失,它在边缘处的梯度控制比纯L2更稳定,误差大的区域不会主导整个训练。
- 交叉重建损失:两个分支互相预测对方模态特征的误差,这部分是双分支协同学习的关键,权重也要给够。
我实际用的总损失可以写成这样一个组合:
# 示意:CMDR-IAD的三支损失加权组合 import torch.nn.functional as F loss_2d = l1_loss(rgb_recon, rgb_gt) + 0.5 * ssim_loss(rgb_recon, rgb_gt) loss_3d = charbonnier_loss(geo_recon, geo_gt) loss_cross = F.l1_loss(feat_2d_to_3d, feat_3d_gt) + \ F.l1_loss(feat_3d_to_2d, feat_2d_gt) total_loss = 1.0 * loss_2d + 0.8 * loss_3d + 0.5 * loss_cross这几个权重我调试过几轮,最终感觉2D重建的权重给到1.0、3D重建给到0.8、交叉重建给到0.5是比较稳的组合。交叉重建的权重不要给太大,否则两个分支会过分趋同,损失掉各自模态的独立判别能力。这是我实测踩坑得到的经验——一开始我把交叉重建权重调到1.2,结果最终AUROC反而比0.5的时候降了1个多点。原因就是2D分支和3D分支的特征相互“拉平”了,各自模态的独特信号反而被弱化了。
3.3 指标跑分与复现注意点
MVTec 3D标准评估指标包括对象级AUROC、像素级AUROC以及区域级PRO分数。97.3%这个数据对应的应该是对象级AUROC,也就是说“一个样本是不是异常”的判别能力。如果只看图像级,不用太意外,这个指标普遍会偏高,因为判别二分类比精确定位要容易。
复现这个数字有几个容易踩的坑,我一个个说。
第一,数据集划分必须严格按官方的正常/异常划分来,千万不要把异常样本无意间混进训练集。我在一个类别上发现某几个异常样本在训练集里也有,结果模型对那几个异常学得很“熟”,测试分数直接拉低。
第二,随机种子和初始化对最终结果有影响。有些类别本身差异不大,随便跑跑都能到98%以上,但有些几何异常微弱的类别很吃初始化。我用固定随机种子跑了三遍,发现不同种子之间最大能差1.5个百分点。所以复现时不要看单次结果,最好多次取均值。
第三,评估时要分清楚用最大值还是平均值作为样本级分数。MVTec 3D官方社区里很多讨论都在争这个,实测下来,在误差图先做3x3高斯平滑的前提下,取最大值作为样本分数整体更稳,但对噪声多的类别需要做点形态学开运算去噪。
4. 训练与部署中的常见问题排查
4.1 重建结果“太好”或“太差”分别怎么回事
我在跑这个方案时,遇到的第一类问题就是训练出来的模型重建效果强得过分,异常样本也被重建得和正常样本差不多,最后AUROC只有80%出头,基本等于没用。
这种情况绝大多数是模型容量太大导致的。解决办法有几招:
- 收紧隐空间维度。把两个分支共享隐空间的维度从512降到128或64,强制信息压缩,异常细节就存不进去了。
- 加瓶颈约束。在编码器输出位置插入一个1x1卷积或MLP降维层,相当于给信息流装一个阀门。
- 提高交叉重建损失权重。让两个分支互相约束得更紧,减少“自圆其说”的空间。
反过来,如果正常样本的重建误差本身就很大,模型输出模糊成一团,那通常是训练不充分或者学习率偏大。我先用前10轮warmup把学习率从0线性提到目标值,训练过程中监控验证集上正常样本的平均重建误差,如果这个值下不去,就把学习率减半再继续。
4.2 2D与3D对齐失败的典型表现
双分支网络最怕的就是2D和3D数据没对齐。对齐失败的典型表现是:整体AUROC不算低,但逐像素分割的结果很糟糕,误差图上的高亮区域明显在位置上偏了。
这个问题的根源基本都出在预处理阶段。相机参数算出来的投影坐标如果没做畸变矫正,误差在小视角下不明显,但在大视角下会被放大得很厉害。我建议在预处理阶段就生成一份2D-3D对应关系表,记录每个2D像素对应哪个3D点索引,这一步虽费一点内存,但训练时直接查表就能保证特征对齐。另外,如果做数据增强,2D的旋转和3D的旋转必须同步,不能各转各的,不然对齐关系直接被打乱。
4.3 训练效率与显存优化技巧
3D分支吃显存是出了名的,PointNet++结构还好,但如果你用稀疏卷积或者点云Transformer,显存消耗会直线上升。我的经验是先降采样再进网络,点数从几十万砍到16384,对最终性能的影响在1%以内,但显存占用能下降一半。
如果显存依然不够,可以考虑把3D分支的输入从原始点云改成体素特征。体素化虽然损失了一些细节,但计算效率和显存占用非常友好。另一个很有效的手段是采用混合精度训练,AMP在大多数3D结构上都能直接跑通,速度提升接近一倍。训练数据多时还可以用Multi-Scale训练,小尺寸先跑通再切大尺寸,让模型先有全局视野再加细节,收敛速度明显改善。
模型训练完之后的推理阶段也要注意,别傻乎乎地把整张大图和整片点云一起塞进去算。实际部署时可以用滑动窗口把图像切成小块,点云按体素分块处理,这样能把单次推理的峰值显存压得很低,在普通的工控机上也能跑得起来。
我个人的体会是,CMDR-IAD这套方案最值得借鉴的地方不是某个具体网络结构,而是“双模态互相监督”这个思路本身。它不依赖特征库、不依赖预训练大模型的强特征提取能力,靠的是重建任务里天然存在的信息约束。一旦想明白这一点,把它迁移到其他多模态异常检测场景——比如深度图+红外图、图像+音频——就不再是什么难事。最后再分享一个我后来养成的习惯:每次换新数据集,第一件事不是调网络结构,而是先跑一个纯2D重建和一个纯3D重建的基线,看看各自的上限在哪,再决定双分支融合的策略。这几步扎实了,后面的路就顺了。