news 2026/9/6 11:52:33

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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的骨干网络是ViT(Vision Transformer)。以dinov2_vits14为例,输入图像会被切成一连串14x14像素的patch,每个patch经过线性映射变成384维的token。这些token经过多层Transformer编码器之后,每一层都保留了对局部区域的语义和纹理表征。缺陷检测用的就是这些patch-level的token特征。

这里有一个很关键的细节:我们取的是最后一层的CLS token还是所有patch token?CLS 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(images=image, return_tensors="pt").to(device) with torch.no_grad(): outputs = model(**inputs, output_hidden_states=False) # last_hidden_state shape: (1, num_patches+1, 384),去掉CLS token patch_tokens = outputs.last_hidden_state[:, 1:, :] return patch_tokens

这里输出的patch_tokens形状是(1, 256, 384),其中256是16x16的patch网格(224x224输入除以14),384是特征维度。每张图都会被压缩成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特征向量的最近邻距离(通常用k=1的欧氏距离,也可以用k=5后取平均)。距离越大,说明这个区域和正常样本越不像,也就是缺陷的概率越高。

整体异常分数可以取所有patch中最大距离,也可以用99%分位数。我倾向于用99%分位数,原因是单点噪声会产生极大距离,如果取最大距离,会把一个像素级的噪声放大成整图异常,导致误检。

阈值的确定分两步走:

  1. 用训练集(都是正常样本)做一次交叉验证,计算每个patch的距离分布。正常patch的距离通常呈现一个长尾分布,我们可以取99.5%分位数的距离作为基准阈值。
  2. 用少量带有缺陷的图像做验证,调整阈值。如果漏检,降低阈值;如果误检,提高阈值。这个过程一般迭代两三次就能收敛。

实际代码里,阈值就是一个浮点数,比如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 python=3.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_ENDPOINT=https://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_name="facebook/dinov2-small", device=None, k=1, threshold=0.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(images=image, return_tensors="pt").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(), sigma=1.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, desc="Building 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, dim=0).cpu() print(f"Memory 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, p=2) min_dists, _ = torch.topk(dists, k=self.k, largest=False, dim=1) if self.k > 1: min_dists = min_dists.mean(dim=1) anomaly_map = min_dists.numpy().reshape(h, w) # 上采样到原图尺寸 anomaly_map = cv2.resize(anomaly_map, (image.shape[1], image.shape[0]), interpolation=cv2.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_map

3.4 运行步骤与输出结果解读

准备好数据后,按三步运行:

detector = DINOv2DefectDetector(threshold=0.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约等于36,36x36=1296,扣掉边缘),特征库就是300x1296x384个float,大约600MB。这在PC上还能接受,但如果在边缘设备上就很吃紧。

两个优化方案:

  1. 对记忆库做随机采样,每类正常样本只保留部分patch特征,比如每张图随机取128个patch。实验证明,只要采样策略均匀,检测精度不会明显下降,因为正常样本的patch特征有大量冗余。
  2. 用PCA把特征降到64维后再存入记忆库。距离计算量直接降低到原来的六分之一,内存也大幅缩小。

如果就是要全量特征,还可以用faiss库做向量检索,使用GPU版本的faiss,检索速度能再提升一个数量级。不过对于大多数工业场景,上面的采样优化已经够了。

4. 常见问题与排查技巧实录

4.1 推理时显存溢出,怎么降低显存占用

这个问题出现得很频繁,尤其是用dinov2-small但在高分辨率图像上测试时。DINOv2的原生输入是224x224,但如果你的工业相机拍出来是2048x2048,直接把整图塞进模型肯定爆显存。

解决方案有两个:

  1. 切片推理:把大图切成多个224x224的patch,重叠部分设为10%,分别提取特征后再拼接起来。切片的优点是保留了原始分辨率下的细节,适合微小缺陷。缺点是推理时间会成倍增加。
  2. 降采样推理:把原图缩放到512x512甚至224x224再处理。对于大面积缺陷,降采样影响不大;但如果要检的是细小划痕,建议用切片方案。

我自己的经验是:先在降采样图像上跑一遍全局检测,如果有疑似缺陷区域,再对原图对应位置做切片细查。两级检测方案既保证了速度,又兼顾了细节。

4.2 阈值设多少才合适,为什么我设0.8误检这么多

阈值的选择不是拍脑袋定的,它和你的特征库大小、图像的拍摄条件、产品表面本身的纹理波动都强相关。0.8是我在MVTec metal_nut上的经验值,换到你的产品上很可能不适用。

正确的做法是:

  1. 先收集200张以上的正常图像,计算它们各自的异常分数(通过测试集交叉验证)。
  2. 画出正常分数的直方图,观察分布。
  3. 取99.5%分位数作为初始阈值,再拿少量缺陷图验证。

如果你的正常图像本身就有光照不均匀、反光、遮挡等干扰,异常分数方差会很大,阈值需要相应调高。这时候建议先对图像做预处理,比如光照校正、色彩归一化,把正常样本的特征分布压缩,阈值才能降下来,检测灵敏度才会上去。

4.3 缺陷区域检测出来了,但边缘粗糙,怎么回事

边缘粗糙十有八九是高斯平滑的sigma设得太小了。如果还是不够平滑,可以增大窗口半径,或者干脆对异常分数图再做一次高斯后处理。我常用的配置是:

anomaly_map = gaussian_filter(anomaly_map, sigma=3)

这会对异常分数图做空间上的平滑,让缺陷区域更集中,边缘更圆滑。但要注意,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_version=17 )

导出后可以用ONNX Runtime或者TensorRT加载。实测RTX 3060上,ONNX Runtime推理一张224x224图像耗时约15ms,TensorRT还能再快30%左右。值得注意的是,特征提取只是整个流程的耗时一部分,最近邻检索在特征库大的时候也可能成为瓶颈,这时候配合faiss GPU加速是非常必要的。

5.2 从缺陷检测到缺陷分类

DINOv2的强项是定位缺陷区域,但缺陷是划痕还是污点?它说不出来。如果产线上需要自动分类缺陷类型,可以在DINOv2定位出缺陷区域后,把缺陷区域裁剪出来,再用一个小的分类网络(比如ResNet18)做二次分类。这个分类网络只需要训练缺陷区域的图片,数据量要求比端到端检测低很多,而且由于DINOv2已经精准裁剪了区域,分类准确率会非常高。

5.3 引入视觉大模型做语义解释

最近不少项目组在尝试用DINOv2定位缺陷,再结合视觉语言模型对缺陷区域做语义描述,比如“划痕长度为12mm,深度约0.3mm,位于元件右上角”。这个方向还在早期阶段,但对质检报告自动生成场景很有吸引力。具体的做法是把DINOv2提取的缺陷区域特征和VLM结合,作为text prompt的一部分传入。目前开源社区已经有类似的项目原型,大家可以保持关注。

实操心得与一点补充

我记得第一次跑通这个流程的时候,最直观的感受是:“原来不用标注也能做缺陷检测”。DINOv2把特征表达这件事做到了一个很高的通用性,让下游的异常检测任务变得异常简单。但也要诚实地说,工业缺陷检测没有银弹,DINOv2在纹理类缺陷上的表现接近完美,但在低对比度、高反光、复杂背景场景下,仍然需要结合打光方案、图像预处理和传统的机器视觉算法来兜底。我的建议是:先把DINOv2跑通,拿到异常分数图,再根据你的实际样本去调节阈值和后处理策略,这比一开始就追求复杂的端到端模型要务实得多。如果你的数据里存在大量正常样本但缺陷很少,这套方案应该是最值得优先尝试的路线。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 5:39:33

AI短剧制作平台哪家好?灵栩栩等主流工具

先给结论 AI短剧制作平台怎么选,核心看四件事:能否覆盖从剧本到成片的完整工作流、能否保证角色与场景一致性、能否支持多集并行与批量量产、以及单集生成成本是否可控。综合这四点,三七互娱旗下广州灵七智能科技有限公司的「灵栩栩」&#x…

作者头像 李华
网站建设 2026/9/6 9:25:04

EKF+BP神经网络融合:提升模型失配下状态估计精度的实战指南

如果你正在处理传感器数据融合、机器人定位或自动驾驶中的轨迹跟踪问题,一定会遇到一个核心挑战:如何在充满噪声的观测数据中,准确地估计出系统的真实状态?传统的卡尔曼滤波(KF)及其扩展版本(EK…

作者头像 李华
网站建设 2026/9/3 6:36:01

单片机毕业设计-基于单片机与蓝牙通讯的输液状态监测终端设计与开发 基于 STM32 或 51 单片机的液位‑滴速‑温度多参数输液监护装置设计(024005)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/6 6:17:47

Zod :让 TypeScript 在运行时也能检查数据

前言 TypeScript 能够在编码和编译阶段帮助我们发现类型错误,但它无法保证程序在运行时接收到的数据一定符合类型定义。 例如,接口声明返回的是一个用户对象: interface User {name: string;age: number; }但后端实际返回的数据可能是&#x…

作者头像 李华
网站建设 2026/9/4 16:33:05

OpenClaw实战:从零部署个人AI Agent与Skill开发指南

如果你最近在关注 AI Agent 方向,应该已经注意到一个现象:开源社区里突然冒出一批“个人 AI 助手”项目,它们不做模型训练,不搞复杂算法,却能在很短时间里把几十个大模型、十几类业务工具和一个聊天入口整合成真正能干…

作者头像 李华
网站建设 2026/9/5 11:08:04

论文AI率过高怎么办?2026年亲测20款免费降AI率工具,教你降AIGC避坑

说实话,现在写论文最闹心的早就不是查重率了,而是那个刺眼的“AIGC疑似度”。以前怕撞内容,现在怕被系统认定“不像活人写的”。好多同学后台吐槽:“明明是我一个字一个字敲出来的,怎么也被判AI?”或是“熬…

作者头像 李华