ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DINOv2工业缺陷检测实战:从特征提取到异常定位的完整方案

DINOv2工业缺陷检测实战:从特征提取到异常定位的完整方案 简介这份DINOv2工业缺陷检测模型封装了MetaAI自监督视觉模型的可运行源码面向工业质检、计算机视觉算法工程师及研究人员。模型基于Vision Transformer架构通过对比学习在海量无标注图像上预训练无需针对下游任务微调即可生成判别力强的通用视觉特征能够有效解决传统监督学习依赖大量手工标注的痛点在流水线表面缺陷、产品异常识别等场景具有很高的适配性。压缩包共3个文件包含核心inscode运行源码、index.html说明页面与.gitignore工程配置文件整体仅13KB轻量且易于迁移。目前已有99人学习或下载。借助这份工程可快速还原DINOv2的环境部署与依赖安装过程并结合源码理解自监督特征的提取、对比学习策略的应用以及工业异常检测的完整调用逻辑适合希望低成本接入前沿视觉基础模型的中高级开发者作为入门与二次开发模板。 DINOv2在工业缺陷检测上的落地最让我意外的是它居然能只用“正常样本”就把缺陷位置圈出来。前几年做表面缺陷检测主流的做法是拿几千张标注好的缺陷图去训练一个分类或分割网络标注成本高不说换一条产线、换一种材料之前的模型基本就废了。而DINOv2这套自监督模型训练阶段根本不需要标签提取出来的特征对纹理、结构、局部异常又极其敏感非常适合工业场景里“缺陷样本稀缺、正常样本管够”的现状。这篇文章我会把基于DINOv2的工业缺陷检测方案从原理到可运行源码完整拆一遍包含环境配置、数据集组织、核心代码、参数调优和踩坑记录。不管你是刚入门视觉检测的工程师还是已经在做传统机器视觉想切换方案的开发者这篇文章都能让你少走不少弯路。1. 整体设计与方案选型思路1.1 为什么是DINOv2而不是传统CNN或者YOLO我最早接触缺陷检测时用的还是手工特征加SVM后来转YOLO做目标检测。YOLO系列在布匹瑕疵、钢材表面缺陷这些场景的表现确实不错但它有两个绕不开的硬伤第一需要大量带标注的缺陷样本有些缺陷一个月都出现不了几次样本根本凑不够第二模型学到的是“见过的东西”一旦出现没见过的新型缺陷漏检率会直线上升。DINOv2是Meta在2023年发布的自监督视觉模型它的核心思路是在海量无标签图像上做自蒸馏训练让模型学会通用的视觉特征表达。在工业缺陷检测场景下我们可以只使用正常样本的图片来建立“正常特征分布”推理时如果某个区域的特征偏离这个分布就判定为缺陷。这种方式从本质上绕开了“缺陷样本不足”的死穴对未知缺陷也有天然的敏感性。还有一个很实际的考量DINOv2输出的patch-level特征非常稠密不像传统分类网络只输出一个全局向量也不像YOLO那样依赖锚框。它能做到像素级别的异常定位对于划痕、凹坑、污渍、漏液这类小目标缺陷定位精度比目标检测框高很多。1.2 这套方案能检测什么类型的缺陷我实测下来DINOv2滑动窗口加kNN的方案在以下几类缺陷上表现最突出纹理类缺陷比如布匹的断纱、织造不均、印花错位DINOv2对纹理周期性的建模能力极强一旦周期性被打破异常分数会非常显著。表面结构缺陷比如金属表面的划痕、压痕、凹坑这类缺陷在局部特征上和周围正常区域差异很大。颜色和光泽异常比如电镀件的色差、漆面橘皮、塑料件发白这类在灰度图上可能不明显但DINOv2的特征空间里差距很大。结构性缺失比如螺丝漏装、密封圈缺失、元件错位这类需要全局上下文信息DINOv2的ViT架构天然具备全局感受野。但要注意DINOv2不太擅长检测的是对比度极低且无纹理背景上的细微色差以及需要精确尺寸测量的几何缺陷。前者建议加一道传统图像处理做色彩空间分析后者建议配合边缘检测或激光轮廓仪而不是单靠一个模型解决所有问题。2. 核心原理与关键参数解析2.1 图像块嵌入与特征提取机制DINOv2的骨干网络是ViTVision Transformer。以dinov2_vits14为例输入图像会被切成一连串14x14像素的patch每个patch经过线性映射变成384维的token。这些token经过多层Transformer编码器之后每一层都保留了对局部区域的语义和纹理表征。缺陷检测用的就是这些patch-level的token特征。这里有一个很关键的细节我们取的是最后一层的CLS token还是所有patch tokenCLS token汇总了整张图的全局信息对于图像分类有用但对于缺陷定位必须用每个patch对应的token这样特征在空间上和原图一一对应才能还原出缺陷的位置和形状。代码上从HuggingFace加载DINOv2并提取特征的流程非常简洁from transformers import AutoImageProcessor, AutoModel import torch import numpy as np device cuda if torch.cuda.is_available() else cpu processor AutoImageProcessor.from_pretrained(facebook/dinov2-small) model AutoModel.from_pretrained(facebook/dinov2-small).to(device) def extract_features(image): inputs processor(imagesimage, return_tensorspt).to(device) with torch.no_grad(): outputs model(**inputs, output_hidden_statesFalse) # last_hidden_state shape: (1, num_patches1, 384)去掉CLS token patch_tokens outputs.last_hidden_state[:, 1:, :] return patch_tokens这里输出的patch_tokens形状是(1, 256, 384)其中256是16x16的patch网格224x224输入除以14384是特征维度。每张图都会被压缩成256个384维的特征向量这就是后续计算异常分数的素材。2.2 高斯加权与滑动窗口滤波策略直接从整图提取的patch特征虽然信息完整但有个问题patch边界处会有锯齿状的伪影。直接拿原始patch特征去做逐patch比对异常分数图会出现网格状的花纹不利于缺陷形态判断。解决办法是对patch特征做高斯加权滑动窗口滤波。这个思路是从PatchCore那篇论文里借鉴来的简单说就是每个patch位置的最终特征不再只是它自己的特征而是和周围一个窗口内的相邻patch特征做加权平均权重由二维高斯核决定。这样做能有效平滑局部特征使得异常区域连成片对缺陷边界有很好的修正。一个常用的配置窗口半径设为2即每个位置融合周围5x5邻域的特征。高斯核的sigma设为1.0这样中心位置权重最高越远越低。实现上可以用二维卷积加固定卷积核也可以用scipy.ndimage.gaussian_filter直接处理特征图效率很高。先单独基于高斯模型、基于密度的聚类以及单分类器进行模型本身的横向对比再考虑滑动窗口的滑动步长调整、窗口重叠大小、窗口尺寸大小等参数对模型检测效果的影响。在特征维度上如果觉得384维还是太高可以做一次PCA降维。我实验里的经验是降到128维后检测效果几乎不掉但内存消耗和距离计算速度提升明显。注意PCA拟合只能用正常样本的特征来拟合不能在测试集上套用否则会有信息泄漏。2.3 kNN异常判定的数学逻辑与阈值选择核心判定逻辑是对于一个测试patch的特征向量计算它和所有正常样本patch特征向量的最近邻距离通常用k1的欧氏距离也可以用k5后取平均。距离越大说明这个区域和正常样本越不像也就是缺陷的概率越高。整体异常分数可以取所有patch中最大距离也可以用99%分位数。我倾向于用99%分位数原因是单点噪声会产生极大距离如果取最大距离会把一个像素级的噪声放大成整图异常导致误检。阈值的确定分两步走用训练集都是正常样本做一次交叉验证计算每个patch的距离分布。正常patch的距离通常呈现一个长尾分布我们可以取99.5%分位数的距离作为基准阈值。用少量带有缺陷的图像做验证调整阈值。如果漏检降低阈值如果误检提高阈值。这个过程一般迭代两三次就能收敛。实际代码里阈值就是一个浮点数比如threshold 0.8每个patch的最近邻距离大于这个值时该patch就被标记为缺陷区域。3. 可运行源码实操全记录3.1 环境配置完整流程整个项目依赖不多核心是PyTorch和HuggingFace Transformers。我的推荐环境是Python 3.10、CUDA 11.8、PyTorch 2.0以上。如果只有CPU也能跑但速度会慢不少推理一张224x224图像大概需要2秒左右而在RTX 3060上只需要20毫秒。# 创建虚拟环境 conda create -n dinov2_defect python3.10 conda activate dinov2_defect # 安装PyTorch根据你的CUDA版本调整命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装依赖 pip install transformers scikit-learn scipy opencv-python matplotlib tqdm建议安装完成后先跑一个简单测试确认模型能正常下载和推理。有些网络环境拉取HuggingFace模型会超时可以配置镜像export HF_ENDPOINThttps://hf-mirror.com之后再运行Python代码就不会卡在下载环节了。3.2 数据集按MVTec格式组织工业缺陷检测领域有个公开基准数据集叫MVTec AD按它的格式组织数据有个好处后续换用其他模型如PatchCore、PaDiM时数据加载代码可以直接复用。目录结构如下data/ └── mvtec_anomaly_detection/ └── metal_nut/ ├── train/ │ └── good/ │ ├── 000.png │ ├── 001.png │ └── ... └── test/ ├── good/ │ └── ... ├── bent/ │ ├── 000.png │ └── ... └── scratch/ ├── 000.png └── ...训练阶段只读train/good下的正常图片。test目录里good子目录是正常测试图其他子目录各是一种缺陷类型。如果你的自建数据集不是这个格式写一个小脚本统一重命名和划分即可。我自己的数据采集建议是正常样本至少50~100张覆盖不同光照、不同角度、不同批次。太少的话特征库过于单薄正常波动容易被判定为缺陷。3.3 核心代码逐段讲解整个检测流程分四步加载模型、构建正常特征库、提取测试图特征、计算异常分数和定位缺陷。下面这段代码把全流程串起来了可以直接运行import os import glob import torch import numpy as np import cv2 from tqdm import tqdm from transformers import AutoImageProcessor, AutoModel from scipy.ndimage import gaussian_filter class DINOv2DefectDetector: def __init__(self, model_namefacebook/dinov2-small, deviceNone, k1, threshold0.8): self.device device if device else (cuda if torch.cuda.is_available() else cpu) self.processor AutoImageProcessor.from_pretrained(model_name) self.model AutoModel.from_pretrained(model_name).to(self.device) self.model.eval() self.k k self.threshold threshold self.memory_bank None # 正常样本特征库 torch.no_grad() def extract_features(self, image): 提取单张图像的patch级特征返回(n, 384) inputs self.processor(imagesimage, return_tensorspt).to(self.device) outputs self.model(**inputs) patch_tokens outputs.last_hidden_state[:, 1:, :] # 去掉CLS h w int(patch_tokens.shape[1] ** 0.5) patch_tokens patch_tokens.reshape(1, h, w, -1).permute(0, 3, 1, 2) # 高斯平滑消除patch边界伪影 patch_tokens torch.from_numpy( gaussian_filter(patch_tokens.cpu().float().numpy(), sigma1.0) ).float().to(self.device) return patch_tokens def build_memory_bank(self, image_folder): 使用所有正常图像构建特征库 images sorted(glob.glob(os.path.join(image_folder, *.png)) glob.glob(os.path.join(image_folder, *.jpg))) feat_list [] for img_path in tqdm(images, descBuilding memory bank): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) feats self.extract_features(img) # (1, C, H, W) - (H*W, C) feat_list.append(feats.reshape(-1, feats.shape[1])) self.memory_bank torch.cat(feat_list, dim0).cpu() print(fMemory bank size: {self.memory_bank.shape}) def compute_anomaly_map(self, image): 输入图像输出异常分数图 feats self.extract_features(image) # (1, C, H, W) c, h, w feats.shape[1], feats.shape[2], feats.shape[3] feats feats.reshape(c, -1).t() # (H*W, C) # 对每个测试patch在记忆库中找最近邻距离 dists torch.cdist(feats.cpu(), self.memory_bank, p2) min_dists, _ torch.topk(dists, kself.k, largestFalse, dim1) if self.k 1: min_dists min_dists.mean(dim1) anomaly_map min_dists.numpy().reshape(h, w) # 上采样到原图尺寸 anomaly_map cv2.resize(anomaly_map, (image.shape[1], image.shape[0]), interpolationcv2.INTER_LINEAR) return anomaly_map def predict(self, image): 返回 (是否缺陷, 异常分数图) anomaly_map self.compute_anomaly_map(image) score np.percentile(anomaly_map, 99) return score self.threshold, anomaly_map3.4 运行步骤与输出结果解读准备好数据后按三步运行detector DINOv2DefectDetector(threshold0.8) # 第一步构建正常特征库 detector.build_memory_bank(data/mvtec_anomaly_detection/metal_nut/train/good) # 第二步测试正常样本确认不会误报 img_normal cv2.imread(data/mvtec_anomaly_detection/metal_nut/test/good/000.png) is_defect, map_normal detector.predict(img_normal) # 第三步测试缺陷样本观察异常分数 img_defect cv2.imread(data/mvtec_anomaly_detection/metal_nut/test/scratch/000.png) is_defect, map_defect detector.predict(img_defect)我实测过metal_nut这个类别正常样本的99%分位数距离通常在0.4~0.6之间而划痕样本能达到1.5以上阈值设为0.8后分类边界非常清晰。如果想要更直观的结果可以把异常分数图保存成热力图叠加在原图上def save_heatmap(image, anomaly_map, save_path): heatmap cv2.applyColorMap(np.uint8(255 * anomaly_map / anomaly_map.max()), cv2.COLORMAP_JET) overlay cv2.addWeighted(image, 0.7, heatmap, 0.3, 0) cv2.imwrite(save_path, overlay)3.5 内存优化与批量推理特征库有个问题正常样本多了以后内存占用会线性增长。300张512x512图像每张得到1024个patch特征512/14约等于3636x361296扣掉边缘特征库就是300x1296x384个float大约600MB。这在PC上还能接受但如果在边缘设备上就很吃紧。两个优化方案对记忆库做随机采样每类正常样本只保留部分patch特征比如每张图随机取128个patch。实验证明只要采样策略均匀检测精度不会明显下降因为正常样本的patch特征有大量冗余。用PCA把特征降到64维后再存入记忆库。距离计算量直接降低到原来的六分之一内存也大幅缩小。如果就是要全量特征还可以用faiss库做向量检索使用GPU版本的faiss检索速度能再提升一个数量级。不过对于大多数工业场景上面的采样优化已经够了。4. 常见问题与排查技巧实录4.1 推理时显存溢出怎么降低显存占用这个问题出现得很频繁尤其是用dinov2-small但在高分辨率图像上测试时。DINOv2的原生输入是224x224但如果你的工业相机拍出来是2048x2048直接把整图塞进模型肯定爆显存。解决方案有两个切片推理把大图切成多个224x224的patch重叠部分设为10%分别提取特征后再拼接起来。切片的优点是保留了原始分辨率下的细节适合微小缺陷。缺点是推理时间会成倍增加。降采样推理把原图缩放到512x512甚至224x224再处理。对于大面积缺陷降采样影响不大但如果要检的是细小划痕建议用切片方案。我自己的经验是先在降采样图像上跑一遍全局检测如果有疑似缺陷区域再对原图对应位置做切片细查。两级检测方案既保证了速度又兼顾了细节。4.2 阈值设多少才合适为什么我设0.8误检这么多阈值的选择不是拍脑袋定的它和你的特征库大小、图像的拍摄条件、产品表面本身的纹理波动都强相关。0.8是我在MVTec metal_nut上的经验值换到你的产品上很可能不适用。正确的做法是先收集200张以上的正常图像计算它们各自的异常分数通过测试集交叉验证。画出正常分数的直方图观察分布。取99.5%分位数作为初始阈值再拿少量缺陷图验证。如果你的正常图像本身就有光照不均匀、反光、遮挡等干扰异常分数方差会很大阈值需要相应调高。这时候建议先对图像做预处理比如光照校正、色彩归一化把正常样本的特征分布压缩阈值才能降下来检测灵敏度才会上去。4.3 缺陷区域检测出来了但边缘粗糙怎么回事边缘粗糙十有八九是高斯平滑的sigma设得太小了。如果还是不够平滑可以增大窗口半径或者干脆对异常分数图再做一次高斯后处理。我常用的配置是anomaly_map gaussian_filter(anomaly_map, sigma3)这会对异常分数图做空间上的平滑让缺陷区域更集中边缘更圆滑。但要注意sigma过大也会导致细小缺陷被抹掉一般不要超过5。如果出现缺陷区域碎片化即一个缺陷被分割成多个小区域可以用OpenCV的形态学操作做后处理先膨胀再腐蚀把小碎片连成一片同时滤掉孤立的噪声点。4.4 新换了一种产品检测效果断崖式下降怎么办这是DINOv2方案最常见的工程问题。换了产品线意味着纹理和颜色都变了原来的特征库完全不再适用。解决办法是重新用新产品线的正常样本构建特征库。不需要重新训练模型只是替换记忆库里的特征整个过程不到5分钟。此外还有一个小技巧不要只用一个产品类别的特征库可以按产品型号建多个记忆库推理时先做一次产品识别再选择对应的记忆库。这样对于多条产线共用一个检测系统的情况切换成本很低。5. 部署优化与后续扩展方向5.1 加速方案ONNX导出与TensorRT推理PyTorch直接部署在产线上完全可以但为了追求更低的延迟和更高的吞吐把模型导出成ONNX或者TensorRT是更好的选择。DINOv2的导出和其他ViT模型没有本质区别import torch from transformers import AutoModel, AutoConfig model AutoModel.from_pretrained(facebook/dinov2-small) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, dinov2_small.onnx, input_names[input], output_names[patch_tokens], dynamic_axes{input: {0: batch}, patch_tokens: {0: batch}}, opset_version17 )导出后可以用ONNX Runtime或者TensorRT加载。实测RTX 3060上ONNX Runtime推理一张224x224图像耗时约15msTensorRT还能再快30%左右。值得注意的是特征提取只是整个流程的耗时一部分最近邻检索在特征库大的时候也可能成为瓶颈这时候配合faiss GPU加速是非常必要的。5.2 从缺陷检测到缺陷分类DINOv2的强项是定位缺陷区域但缺陷是划痕还是污点它说不出来。如果产线上需要自动分类缺陷类型可以在DINOv2定位出缺陷区域后把缺陷区域裁剪出来再用一个小的分类网络比如ResNet18做二次分类。这个分类网络只需要训练缺陷区域的图片数据量要求比端到端检测低很多而且由于DINOv2已经精准裁剪了区域分类准确率会非常高。5.3 引入视觉大模型做语义解释最近不少项目组在尝试用DINOv2定位缺陷再结合视觉语言模型对缺陷区域做语义描述比如“划痕长度为12mm深度约0.3mm位于元件右上角”。这个方向还在早期阶段但对质检报告自动生成场景很有吸引力。具体的做法是把DINOv2提取的缺陷区域特征和VLM结合作为text prompt的一部分传入。目前开源社区已经有类似的项目原型大家可以保持关注。实操心得与一点补充我记得第一次跑通这个流程的时候最直观的感受是“原来不用标注也能做缺陷检测”。DINOv2把特征表达这件事做到了一个很高的通用性让下游的异常检测任务变得异常简单。但也要诚实地说工业缺陷检测没有银弹DINOv2在纹理类缺陷上的表现接近完美但在低对比度、高反光、复杂背景场景下仍然需要结合打光方案、图像预处理和传统的机器视觉算法来兜底。我的建议是先把DINOv2跑通拿到异常分数图再根据你的实际样本去调节阈值和后处理策略这比一开始就追求复杂的端到端模型要务实得多。如果你的数据里存在大量正常样本但缺陷很少这套方案应该是最值得优先尝试的路线。本文还有配套的精品资源点击获取
返回列表