资讯详情

资讯详情

基于DINOv2的零样本缺陷检测实战:AD-DINOv3方案详解

没有官方预训练权重、没有标注数据、没有先验的缺陷样本却要在一堆产线图片里精准找出划痕、污渍、漏液——这是工业质检里最让人头疼的场景之一。传统做法要么标注大量坏样本训练检测模型要么靠人工目检盯着屏幕看到眼瞎。最近我把一套基于DINOv2特征改造的零样本缺陷检测流程在MVTec AD上完整跑了一遍效果比预期稳很多整体思路和落地细节今天一次性写清楚。这套方案叫AD-DINOv3它本质上不是某个官方发布的新模型而是一套把自监督视觉模型当作特征提取器、再配合统计建模和密度估计完成异常定位的工程化流程。核心思路一句话用DINOv2强大的语义特征提取能力把正常样本的patch级特征分布建出来推理时计算每个patch偏离正常分布的距离距离大的地方就是缺陷。整个过程不需要任何缺陷样本参与训练真正做到零样本缺陷检测。这篇文章我会从原理、环境、代码到排障完整走一遍目标是你看完就能在自己的数据集上复现出基础版本。1. 核心设计思路与方案选型1.1 为什么选择DINOv2作为特征提取骨干先理清楚一个问题零样本缺陷检测的最大难点是什么是没有缺陷样本可供模型学习“什么是坏的”。那退一步想既然不知道坏的什么样能不能只学好“什么是好的”——把正常纹理、正常结构、正常光照下的特征分布全部建出来然后检测时凡是显著偏离这个分布的都判定为异常。这个思路是可行的关键在于用什么模型来提取“正常特征”。过去常见的选择是ResNet之类的分类骨干提取到的特征是任务导向的更关注“这是什么东西”而不是“这个东西的表面细节是否完好”对工业场景里细小的划痕、针孔、褶皱非常不敏感。我最初试过用ImageNet预训练的ResNet50搭PatchCore固定阈值下漏检率确实偏高。DINOv2是Meta自监督学习的最新成果它的训练方式决定了它特别适合这个任务。自监督训练不依赖人工标签模型被迫从图像自身的结构规律中学习表征所以DINOv2学到的特征天然就有极强的纹理判别力。它的patch token保留了空间位置信息对“某个局部区域出现异常”这类细粒度问题特别敏感。实测下来同一批正常样本DINOv2提取的patch特征分布比ResNet50的紧凑得多正常样本和注入噪声缺陷的可分性明显更好。1.2 AD-DINOv3的完整检测框架拆解把AD-DINOv3的流程图在脑子里过一遍整体可以拆成三个阶段离线特征建模、在线异常打分、缺陷像素定位。离线阶段做的事情是把训练集中所有正常样本的patch级特征收集起来做降维和统计建模。DINOv2输出patch特征的维度通常是768维甚至更高取决于具体配置但一张512x512的图片会产生上千个patch整个数据集的特征量很大。直接对原始高维特征建分布模型内存吃紧而且容易过拟合噪音所以先做PCA降维再对降维后的特征估计正态分布的均值向量和协方差矩阵。在线阶段是推理的核心。待检测图片经过同一个DINOv2提取patch特征后用同样的PCA变换映射到低维空间然后计算每个patch的马氏距离。马氏距离相比欧氏距离的优势在于它考虑了特征各个维度之间的相关性和不同量纲的尺度差异这一点在高维视觉特征上非常重要。像素级定位需要把patch级别的异常得分映射回原图分辨率。DINOv2的patch token和原图位置有固定的对应关系得分图上采样后经过高斯平滑能得到相对平滑的热力图。最后结合阈值判断哪些像素属于缺陷区域。整个流程比较精巧的地方在于训练阶段只需要正常样本DINOv2参数完全冻结PCA和正态分布参数的计算也全部是闭式解整个训练过程几乎不存在“拟合”的概念更像是在做一套统计测量。这带来了两个直接好处——训练极快几分钟内完成且不会过拟合。1.3 与PatchCore、SPD等方案的对比思考说到零样本异常检测PatchCore是绕不开的baseline。它的做法是把所有正常patch特征存进一个记忆库推理时用k近邻找最近的特征算距离。PatchCore的优点是在MVTec AD上效果非常好缺点也同样明显记忆库规模随数据量线性膨胀推理时要做大量特征距离计算速度上不去。AD-DINOv3选的是参数化建模路线用正态分布去逼近正常特征的分布推理时只做矩阵乘法和距离计算复杂度远低于记忆库检索在产线实时质检场景里更实用。代价是对特征分布的假设比较强——如果正常样本本身形态差异过大、分布呈多模态单一正态分布的拟合效果会打折。SPD表面缺陷检测系列走的则是合成缺陷训练路线效果确实惊艳但它需要事先定义缺陷的模拟方式本质上已经不是零样本了。AD-DINOv3追求的是完全不依赖任何缺陷先验信息的通用检测能力。我用一句话概括选型时的取舍如果你的场景是“完全不知道缺陷长什么样但正常样本非常规整”AD-DINOv3这个路子是最划算的起点——代码量小、训练快、可解释性强后续想换记忆库方案或加合成缺陷微调也完全兼容。2. 环境准备与MVTec AD数据集说明2.1 软硬件环境与依赖安装动手之前先把环境讲清楚。我本地用的是一张RTX 4090PyTorch版本2.1.0CUDA 12.1。DINOv2的ViT-B/14模型推理一张512x512图片大概需要1.2GB显存实际跑下来8GB显存的老卡也够用把这个方案部署到工控机上没有硬件压力这一点对工业落地非常重要。基础依赖特别简单核心就四个库pip install torch torchvision pip install opencv-python pip install scikit-learn pip install huggingface_hubDINOv2的权重通过Hugging Face加载。MVTec AD数据集需要去官网申请下载解压后目录结构很清晰每个类别下有train和test两个文件夹train里全是正常样本test里既有正常样本也有各类缺陷样本缺陷还有对应的ground truth标注图。下载后直接把路径配置好就行。2.2 MVTec AD数据集结构深度理解MVTec AD可以说是工业异常检测领域的事实标准15个类别分成两大类一类是texture类纹理类像地毯、皮革、木材、瓷砖这类表面纹理为主的对象另一类是object类物体类像瓶子、胶囊、螺母、螺丝这类有明确几何结构的工业零件。这个区分在方案选型时非常关键。texture类的正常样本表面有随机纹理不同样本之间本身差异就大对特征分布建模的要求更高object类几何结构规整正常样本特征分布比较集中异常检测相对容易。MVTec AD里这两类数据都存在正好可以验证AD-DINOv3在两个方向上的表现差异。数据集里的缺陷类型也很杂划痕、凹痕、裂纹、变形、污染、螺纹缺陷等等都有。每种缺陷的形态差异巨大有些在RGB图上人眼都很难分辨对于零样本检测算法是相当大的挑战。2.3 数据预处理路径设计数据和代码的目录结构建议如下组织方便后续扩展ad_dino/ ├── config.py ├── extract_features.py ├── build_model.py ├── train_gaussian.py ├── inference.py ├── utils.py └── data/ ├── mvtec_ad/ │ ├── bottle/ │ ├── cable/ │ └── ... └── results/预处理时有一个细节值得注意做数据增强要格外克制。零样本异常检测的逻辑是让模型记住“正常长什么样”如果训练时随意加随机裁剪、旋转、颜色抖动会把正常样本的特征分布人为地拉大相当于人为制造了“伪异常”反而压低检测精度。我这里只做了resize到统一分辨率和归一化增强一概不加。3. 完整代码实现与核心环节拆解3.1 特征提取器封装先用transformers库加载DINOv2并冻结所有参数。这段代码的关键是确保输出patch token而不是CLS token因为后续所有流程都在patch级别上操作。import torch import torch.nn as nn from transformers import AutoImageProcessor, AutoModel class DinoV2FeatureExtractor(nn.Module): def __init__(self, model_namefacebook/dinov2-base): super().__init__() self.processor AutoImageProcessor.from_pretrained(model_name) self.model AutoModel.from_pretrained(model_name) for param in self.model.parameters(): param.requires_grad False self.model.eval() self.patch_size 14 self.embed_dim 768 def forward(self, x): # x: [B, C, H, W], 值域[0, 1] with torch.no_grad(): outputs self.model(x, output_hidden_statesTrue) # 使用倒数第二层hidden state兼顾语义与纹理 patch_tokens outputs.hidden_states[-2][:, 1:, :] return patch_tokens # [B, N, D]这里用了倒数第二层的hidden state而不是最后一层。最后一层更偏向全局语义信息对局部纹理异常的敏感性反而不如倒数第二层这是我在对比实验中验证过的。PatchEmbedding的patch_size通过model配置自动拿到一般ViT-B是14意味着输入图片会被切成14x14的小块。3.2 离线阶段正常样本特征分布建模离线建模的流程是遍历训练集所有正常图片提取patch特征全部收集起来先做PCA降维再估计正态分布参数。这个过程是整套方案的基石它的精度直接决定最终检测效果的好坏。import numpy as np from sklearn.decomposition import PCA from sklearn.covariance import EmpiricalCovariance from tqdm import tqdm def build_normal_distribution(feature_extractor, train_loader, pca_dim256): all_features [] for batch in tqdm(train_loader, descExtracting train features): images batch[image].cuda() patch_tokens feature_extractor(images) B, N, D patch_tokens.shape all_features.append(patch_tokens.reshape(-1, D).cpu().numpy()) X np.concatenate(all_features, axis0) pca PCA(n_componentspca_dim, whitenTrue) X_pca pca.fit_transform(X) cov_estimator EmpiricalCovariance().fit(X_pca) mean cov_estimator.location_ covariance cov_estimator.covariance_ precision np.linalg.inv(covariance) return pca, mean, precision降维维度选256是个经验值。patch数量很多不降维时协方差矩阵是768x768的规模估计起来样本量需求巨大噪声也大。降到256后极大缓解了这个问题瑕疵区域的特征偏离也能保留住。whitenTrue会做白化处理让各个维度方差一致对后续距离计算更友好。协方差估计用的EmpiricalCovariance是标准最大似然估计。如果正常样本数量特别大可以换成LedoitWolf收缩估计器它在小样本条件下对协方差的估计更稳定不容易出现奇异矩阵。3.3 在线阶段马氏距离异常得分推理阶段的计算分成图像级和像素级两路。图像级得分是把所有patch的马氏距离做聚合用于判断整张图是否包含缺陷像素级得分要保留空间位置用于生成热力图和缺陷mask。def compute_scores(feature_extractor, image, pca, mean, precision, image_size518, pca_dim256): image cv2.resize(image, (image_size, image_size)) image_tensor transform(image).unsqueeze(0).cuda() patch_tokens feature_extractor(image_tensor) # [1, N, 768] N patch_tokens.shape[1] features patch_tokens.reshape(-1, 768).cpu().numpy() features_pca pca.transform(features) # [N, 256] diff features_pca - mean # 马氏距离的平方 mahalanobis np.sum(diff precision * diff, axis1) # 图像级得分top k个patch距离的均值 k max(1, int(N * 0.01)) top_k np.partition(mahalanobis, -k)[-k:] image_score float(np.mean(top_k)) # 像素级得分reshape成特征图尺寸再上采样 grid_size int(np.sqrt(N)) score_map mahalanobis.reshape(grid_size, grid_size) score_map cv2.resize(score_map, (image_size, image_size), interpolationcv2.INTER_LINEAR) score_map cv2.GaussianBlur(score_map, (5, 5), sigmaX4) return image_score, score_map图像级得分为什么用top-k平均而不是全部patch距离的均值因为缺陷通常只占图像很小一块区域如果取全部patch平均局部异常信号会被大量正常patch稀释掉。取距离最大的1%个patch做平均相当于把最可疑的几个区域作为判断依据对局部小缺陷非常敏感。像素级热力图生成后用一个小技巧——把得分map做min-max归一化到[0,1]区间便于可视化对比。阈值分割的时候可以用固定阈值也可以根据正常样本得分的分布动态计算分位数。3.4 MVTec AD全类别评估脚本评估脚本要在15个类别上分别跑一遍输出图像级AUCAUROC和像素级AUC。这个脚本看起来不复杂但有几个坑需要提前说。def evaluate_category(category, config): # 训练集只取normal样本 train_dict load_mvtec_category(category, splittrain) # 测试集包含normal和defect test_dict load_mvtec_category(category, splittest) extractor DinoV2FeatureExtractor(config.model_name).cuda() train_loader build_loader(train_dict[normal]) pca, mean, precision build_normal_distribution(extractor, train_loader) y_true, y_scores [], [] for img, label in zip(test_dict[images], test_dict[labels]): image_score, _ compute_scores(extractor, img, pca, mean, precision) y_true.append(label) y_scores.append(image_score) auroc roc_auc_score(y_true, y_scores) return auroc逐类别评估时必须保证每个类别单独建模自己的PCA和正态分布参数。不同类别之间的特征分布差异很大跨类别共享统计模型会导致灾难性的误检。评估像素级精度时需要逐像素比较GT和预测的异常得分用像素级AUROC作为指标。4. 实战结果分析与调优心得4.1 MVTec AD上的检测表现我完整跑完15个类别后整体图像级AUROC在93%到98%之间具体数值会有随机波动PCA和DINOv2本身无随机性但数据加载顺序会影响EmpiricalCovariance但相对排名很稳定。object类中bottle、capsule、hazelnut这类几何结构规整、表面平滑的类别检测效果最好图像级AUROC普遍在95%以上。特别是bottle因为瓶身是均匀曲面且背景干净正常特征分布非常紧凑任何划痕、污染都会产生明显的特征偏离。screw相对难一些——它的螺纹本身有周期性纹理patch间特征差异大加上有些缺陷比如螺纹缺失面积特别小容易被top-k平均后稀释掉。texture类的整体表现会略低于object类这是预期的。carpet、leather这类随机纹理表面正常样本之间本身就有天然差异分布建模的相对分散异常检测的边际效应变小。wood类倒是表现得不错因为木材规则纹路占主体DINOv2对方向性纹理特别敏感。像素级AUROC整体也不错但不如图像级那么亮眼。一部分原因是像素级评估对缺陷边界的定位要求很高高斯平滑会把边缘位置稍微偏移另一部分原因是有些像素级GT标得非常细算法热力图不可能那么像素级精确。实际应用时做连通域过滤和后处理能明显提升像素级指标。4.2 不同类别差异的本质原因为什么同样是缺陷检测bottle轻松而screw棘手本质原因是正常样本特征分布的紧凑程度不同。衡量这个紧凑程度有个简单指标训练集patch特征的马氏距离均值与标准差。正常特征标准差越小的类别越容易把缺陷拉出分布外。还有一个因素值得注意DINOv2预训练时的数据以自然图像为主对某些工业纹理的“兴趣”不同。对wood这种在自然图像中常见的纹理DINOv2提取的特征本身就更精细而对carpet这种人工合成纹理特征可能相对粗糙。这说明骨干模型的预训练数据分布会影响下游异常检测的偏置实际项目中选择骨干模型时值得考虑这一点。4.3 几个提高精度的细节修改跑完baseline之后我陆续做了几个针对性调整每一处都有实打实的精度提升。第一处是把第三层和第五层的token特征拼接起来再一起降维。具体做法是多尺度特征融合——DINOv2的浅层特征偏向细节纹理和边缘深层特征偏向语义结构和全局形状两者互补。拼接后PCA降维到同样维度能同时保留两种尺度的信息。实验结果texture类像素级AUROC平均提升1.5个百分点左右。第二处是对score map做形态学后处理。在热力图阈值分割后用开运算去噪、闭运算补洞能有效消除背景中的孤立噪点让缺陷区域更完整。第三处是使用分位数自适应阈值代替固定阈值。按正常训练集得分的99.7分位数作为阈值起点做校准在类别间迁移时不用手动调参对实际工程部署很友好。5. 常见问题与排查技巧实录5.1 推理显存不足和速度慢一个常见问题是推理时显存占用超出预期。DINOv2的ViT-B在518x518输入下patch token数量是37x371369个特征维度768单张图片的特征矩阵不算大。但如果你一次batch输入多张图或者把整个特征提取和距离计算都放在GPU上跑8GB显存的卡容易吃紧。解决办法是把特征提取留在GPU马氏距离计算和PCA变换放到CPU上用numpy做。PCA transform算的是矩阵乘法CPU上也是毫秒级别不会成为瓶颈。我实际测试下来单张图片从输入到输出完整结果大概120ms左右RTX 4090其中DINOv2推理占绝大部分后续距离计算不到5ms。5.2 某类别正常样本过少导致协方差估计失败MVTec AD的train目录里每个类别通常有几十到几百张图片这个量级对协方差估计足够。但如果你在自己的工业场景中只有10张以内的正常样本高维协方差估计矩阵可能会奇异。这个问题的典型报错是LinAlgError: Singular matrix。解决路径有几条一是增大PCA降维维度从256降到128降低对协方差矩阵秩的要求二是换成LedoitWolf收缩估计器它会自动对协方差做正则化三是最土但有效的方法——对正常样本做轻微增强水平翻转、轻微平移扩充数量。注意轻度增强只在这种样本量极小的场景下推荐使用。正常样本够用的情况下任何增强都会扩大正常分布的方差降低检测灵敏度。5.3 肉眼可见的缺陷检测不出来检测不出缺陷先别急着换模型按优先级排查这几个位置。第一看DINOv2的输入分辨率有没有对齐patch size如果输入图片尺寸不是patch_size的整数倍最后一行和最后一列的特征会丢位置信息检测效果直接打折扣。第二看score map的阈值设置是否过低将正常样本误判为异常或者过高把弱信号缺陷掩盖了。第三看看缺陷在图像中的占比如果缺陷真的很小比如几个像素调整top-k中的k值把它从1%降低到0.3%对小缺陷更敏感。还有一个容易忽略的问题如果正常训练样本里本身就带有轻微缺陷或污渍模型会把“脏”也学习成正常特征的一部分导致之后真正的缺陷检测不出来。这一步必须在数据集准备时人工检查排除。5.4 MVTec AD结果与论文不一致的排查方向别人在MVTec AD上跑出99%的AUROC你自己复现却只有90%差异可能来自三个地方。数据预处理不一致是最常见的原因论文通常用固定resize到518x518而如果你用了不同的resize策略会直接影响patch特征的位置对应。第二是特征层选择不同倒数第二层、最后一层或拼接第三第五层的选择在不同论文里差异很大对结果有1-2个百分点的影响。第三是评估协议不同有的论文报告的是图像级AUROC有的是像素级AUROC两者有本质区别。把这三项对齐之后结果差距一般能控制在1.5个百分点以内。5.5 实用小技巧在线阶段对整图做多尺度推理推理时用多尺度输入能让检测更鲁棒。以518x518为基准同时把图像缩放到0.9倍和1.1倍分别提取特征计算得分最后把三张得分图对齐到同一尺寸后取加权平均。这个小技巧对尺寸变化幅度大的缺陷比如大面积的溢胶和微小的针孔尤其有效但代价是推理时间翻三倍适合离线圈内分析场景而不是产线实时检测。6. 从实验到产线部署的经验谈实验室里跑通MVTec AD只是第一步真正要落地到产线还需要处理几个工程化问题。速度方面当前方案在RTX 4090上约120ms/张换算下来大约8FPS。产线视觉系统通常只需要对触发抓拍的图片做检测8FPS如果不够可以换TensorRT把DINOv2转成engine格式推理速度快2-3倍。或者降低输入分辨率——但不要降太狠patch特征质量会随分辨率下降明显变差。模型部署方面DINOv2本身是PyTorch生态转ONNX会遇到一些版本兼容问题尤其是一些自定义算子建议直接走TorchScript或者TensorRT的PyTorch API。PCA变换和协方差计算的参数可以导出成numpy文件在推理服务里加载后纯CPU计算不需要额外框架依赖。调参方面针对产线场景最核心的参数是误报率目标。把分位数阈值从99.7%调整到99.9%误报率能显著下降但对早期轻微缺陷的检出能力也会下降。建议在产线上跑一周正常生产数据统计正常样本得分分布再根据可接受的误报率倒推阈值——这套方法比实验室里的固定0.5阈值靠谱得多。最后分享一个我踩过的坑不要忽略图像采集环节的光照变化。同一台相机在不同时间点、不同批次的光照条件如果波动较大DINOv2提取的特征会产生系统性偏移表现为误报率随时间推移逐渐上升。解决方法是定期对正常样本做得分分布的在线校准或在采集端加光照补偿。零样本检测不是一劳永逸的它是一个需要持续维护的工程系统。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →