C# OpenvinoSharp使用RAD进行缺陷检测

开源项目地址:https://github.com/longkukuhi/RAD

检测效果:

此处展示的是C#中OpenvinoSharp加载onnx模型并配合构建的多层特征记忆库进行缺陷检测的实测效果,大家可以参考文后的Python转换及推理代码,进行C#代码编写

文件功能详解

1. dataset.py

作用:定义各个数据集的 Dataset 类,并封装数据增强方法。

主要类/函数

  • get_data_transforms(size, isize, mean_train, std_train):返回训练/测试时使用的图像变换组合(resize → ToTensor → center crop → normalize)。

  • get_strong_transforms:更强的数据增强(随机裁剪、翻转、色彩抖动),主要用于某些需要增强的场景。

  • MVTecDataset:加载 MVTec AD 数据集,支持 train/test 阶段,自动匹配图像与对应的 ground truth 掩码。

  • RealIADDataset:加载 Real‑IAD 数据集,根据 JSON 描述文件组织图像和掩码路径。

  • LOCODataset:加载 LOCO 数据集(逻辑与结构异常检测)。

  • 其他数据集类:包括 InsPLADDataset、AeBADDataset、MVTecDRAEMDataset(用于合成异常训练)、MVTecSimplexDataset(使用 simplex 噪声生成异常样本)等。

相互关系:其他脚本(如构建记忆库、评估)通过该类获取数据。


2. build_bank_multilayer.py

作用 :为 MVTec AD、VisA 或 3D‑ADAM 构建多层特征记忆库

关键步骤

  • 解析命令行参数,包括数据路径、要处理的类别列表、DINOv3 模型名称与权重、图像尺寸、批大小、要提取的层索引(如 3 6 9 11)、输出文件路径。

  • 调用 get_data_transforms 获得图像变换,用 ImageFolder 读取每个类别的 train 目录(所有图像均为正常样本),并将所有类别的正常样本合并为一个 ConcatDataset

  • 使用 IndexedDataset 包装,使每次返回样本时同时返回索引,便于后续索引操作。

  • 加载 DINOv3 模型并设置为评估模式。

  • 调用 build_memory_bank_multilayer 函数:

    • 遍历数据集,对每批图像调用 encoder.get_intermediate_layers(..., return_class_token=True),获得指定层的 patch token 和 CLS token。

    • 将每一层的特征分别收集到列表中,最后用 torch.cat 合并为完整的特征矩阵。

    • 返回层索引列表、CLS 特征列表(每个元素形状 [N, C])和 patch 特征列表(每个元素形状 [N, L, C])。

  • 保存结果到指定的 .pth 文件。

输出 :一个包含 layerscls_bankspatch_banks 三个字段的字典文件。


3. build_realiad_bank_multilayer.py

作用 :与 build_bank_multilayer.py 完全相同,但专门针对 Real‑IAD 数据集。

差异点

  • 使用 RealIADDataset 而非 ImageFolder 读取数据。

  • 命令行参数中的默认类别列表为 Real‑IAD 的 30 个类别。

  • 数据路径指向 Real‑IAD 的根目录。


4. rad_mvtec_visa_3dadam.py

作用:对 MVTec AD、VisA 或 3D‑ADAM 进行异常检测评估。

主要流程

  • 解析参数:数据路径、记忆库路径、DINOv3 配置、保存目录、KNN 参数(k_image)、图像级分数聚合方式(max_ratio)、是否使用位置感知记忆库(use_positional_bank)、层融合权重(layer_weights)等。

  • 准备测试数据:为每个类别创建对应的 Dataset(MVTecDataset 或 ImageFolder),并构建 DataLoader。

  • 加载 DINOv3 模型和预先构建的记忆库(包含多层 CLS 与 patch 特征)。

  • 对每个类别调用 eval_patch_knn_multilayer_cluster 函数:

    1. 提取测试图像的指定层特征(patch tokens 和 CLS tokens)。

    2. 使用最高层的 CLS 特征与记忆库中所有 CLS 特征计算相似度,选出最相似的 k_image 个训练样本。

    3. 对于每一层,将测试 patch 特征与选出的训练样本的 patch 特征进行余弦相似度计算,得到每个 patch 的异常分数(1 - 最大相似度)。若启用 use_positional_bank,则每个 patch 只与训练样本中对应位置邻域内的 patch 比较。

    4. 将各层的异常分数按权重融合,得到最终 patch 级异常分数,重塑为特征图并上采样到指定大小(resize_mask)。

    5. 应用高斯平滑,与 ground truth 对齐后收集像素级和图像级的预测分数。

    6. 计算各类指标:AUROC(图像/像素)、AP、F1、AUPRO(PRO 曲线下面积)。

  • 记录每个类别的结果,输出均值。

可视化 :若指定 vis_dir,会为部分测试样本生成包含输入图像、最近邻训练图像、异常热图、GT 掩码的对比图。


5. rad_real_iad.py

作用 :针对 Real‑IAD 的评估脚本,逻辑与 rad_mvtec_visa_3dadam.py 几乎一致。

差异点

  • 使用 RealIADDataset 加载测试数据。

  • 默认参数值根据 Real‑IAD 特点调整(如 k_image=900)。

  • 函数 eval_patch_knn_multilayer_cluster 内部完全相同(因为特征提取和检索逻辑与数据集无关)。


6. utils.py

作用:提供各种辅助函数,供评估脚本和可能的训练脚本使用。

主要函数

  • 指标计算roc_auc_scoreaverage_precision_score(实际调用 sklearn)、f1_score_max(通过 PR 曲线寻找最佳阈值)、compute_pro(计算 PRO 曲线下面积,用于像素级评估)。

  • 图像处理

    • min_max_normcvt2heatmapshow_cam_on_image:将异常图转换为彩色热图并叠加在原图上。

    • get_gaussian_kernel:生成高斯平滑卷积核,用于对异常图进行平滑。

  • 评估函数

    • evaluation_batchevaluation_batch_maeevaluation_uniad:用于不同模型的批量评估(可能用于对比实验)。

    • save_eval_visualizationsave_eval_visualization_masked:保存评估时的可视化结果。

  • 损失与梯度操作global_cosineglobal_cosine_focal 等,可能用于需要训练的场景(但本项目核心无需训练)。

  • 学习率调度WarmCosineScheduler

文件之间的相互关系

下图概括了各模块的依赖关系:

text

复制代码
                    ┌─────────────────┐
                    │   dataset.py    │
                    └────────┬────────┘
                             │ 提供 Dataset 类
           ┌─────────────────┼─────────────────┐
           ▼                 ▼                 ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│build_bank_multilayer│ │build_realiad_bank_ │ │rad_mvtec_visa_3dadam│
│.py                 │ │multilayer.py       │ │.py                 │
│(MVTec/VisA/3D-ADAM)│ │(Real-IAD)          │ │(MVTec/VisA/3D-ADAM)│
└─────────┬──────────┘ └─────────┬──────────┘ └─────────┬──────────┘
          │ 读取正常样本           │ 读取正常样本          │ 加载记忆库 & 测试样本
          ▼                       ▼                       │
    ┌─────────────────────────────────────────────────────┘
    │ 调用 DINOv3 提取特征
    ▼
┌────────────────────┐      ┌────────────────────┐
│   memory bank      │─────▶│   rad_real_iad.py  │
│   (.pth 文件)       │      │   (Real-IAD)       │
└────────────────────┘      └────────────────────┘
                                   │ 加载记忆库 & 测试样本
                                   ▼
                            ┌────────────────┐
                            │    utils.py    │
                            └────────────────┘
                             提供指标计算、可视化等
  • 数据集层dataset.py 定义了所有数据集的加载方式,被所有其他脚本使用。

  • 记忆库构建层build_bank_multilayer.pybuild_realiad_bank_multilayer.py 依赖 DINOv3 模型和 dataset.py,生成记忆库文件。

  • 评估层rad_mvtec_visa_3dadam.pyrad_real_iad.py 加载记忆库文件、DINOv3 模型和测试数据,调用 utils.py 中的函数计算指标和可视化。

  • 工具层utils.py 为评估提供底层支持。

注意事项

复制代码
使用 pip install -r requirements.txt安装 遇到错误时,可以将requirements.txt文件替换成下面内容

核心科学计算库:放宽版本以适配 Python 3.10 和 NumPy 2.x

matplotlib>=3.5.0

numpy>=1.24.0

opencv_python_headless>=4.6.0

pandas>=1.5.0

Pillow>=9.0.0

scikit_image>=0.19.0

scikit-learn>=1.2.0

scipy>=1.9.0

工具库

tabulate>=0.9.0

tqdm>=4.64.0

ptflops>=0.7

timm>=0.9.0

PyTorch 系列:移除具体的 CUDA 后缀,让 pip 自动匹配或从官方源安装

注意:torch 和 torchvision 建议单独安装,或者在这里只写基础版本

torch>=2.0.0

torchvision>=0.15.0

如果要导出onnx,可以使用下面脚本(项目中没有给出pth转onnx的方法)

"export_onnx.py"

import torch

from dinov3.hub.backbones import load_dinov3_model

加载 DINOv3 模型(与 RAD 使用的相同)

model = load_dinov3_model(

'dinov3_vits16',

pretrained_weight_path='./weights/dinov3_vits16_pretrain_lvd1689m-08c60483.pth'

)

model.eval()

包装模型,使其 forward 返回指定层的 patch tokens 和 cls token

class DINOv3Wrapper(torch.nn.Module):

def init(self, model, layer_indices):

super().init()

self.model = model

self.layer_indices = layer_indices

def forward(self, x):

x: [N, C, H, W]

intermediates = self.model.get_intermediate_layers(

x,

n=self.layer_indices,

return_class_token=True,

reshape=False,

norm=True

)

patch_tokens = []

cls_tokens = []

for patch, cls in intermediates:

patch_tokens.append(patch) # [N, L, C]

cls_tokens.append(cls) # [N, C]

将所有层的输出拼接成一个大的输出(或返回 tuple)

这里返回两个列表,但 ONNX 要求固定数量的输出,因此我们将它们展平

方案:将 patch 和 cls 按顺序拼接为一个 tensor 列表,并分别命名

outputs = []

for i, (p, c) in enumerate(zip(patch_tokens, cls_tokens)):

outputs.append(p) # patch token for layer i

outputs.append(c) # cls token for layer i

return outputs

要导出的层(与构建记忆库时一致)

layer_indices = [3, 6, 9, 11]

wrapper = DINOv3Wrapper(model, layer_indices)

准备 dummy 输入(需与训练时预处理一致:448x448,归一化方式不变)

dummy_input = torch.randn(1, 3, 448, 448)

定义输出名称

output_names = []

for idx in layer_indices:

output_names.append(f'patch_{idx}')

output_names.append(f'cls_{idx}')

动态轴:batch 维度可变

dynamic_axes = {'input': {0: 'batch_size'}}

for name in output_names:

dynamic_axes[name] = {0: 'batch_size'}

导出 ONNX

torch.onnx.export(

wrapper,

dummy_input,

"dinov3_multilayer.onnx",

input_names=['input'],

output_names=output_names,

dynamic_axes=dynamic_axes,

opset_version=14, # 推荐 14 以上

do_constant_folding=True,

export_params=True

)

print("ONNX 模型导出成功!")

如果要将bank pth转bin格式(方便C#调用),可以使用以下脚本

"bank_pth2bin.py"

import torch

import numpy as np

import struct

加载记忆库

bank = torch.load('./bank/mvtec_dinov3_vits16_multilayer36911_448_bank.pth', map_location='cpu')

layers = bank['layers'] # [3,6,9,11]

cls_banks = bank['cls_banks'] # list of [N, C]

patch_banks = bank['patch_banks'] # list of [N, L, C]

def save_array_as_bin(arr, filename):

"""

将 numpy 数组保存为 bin 文件,格式:

int32\] 维度数量 (ndim) \[int32\] 每个维度的大小 (重复 ndim 次) \[float32\] 所有数据(按行主序,即 C-order) """ with open(filename, 'wb') as f: # 写入维度数 f.write(struct.pack('i', arr.ndim)) # 写入每个维度的长度 for dim in arr.shape: f.write(struct.pack('i', dim)) # 写入数据(注意:numpy 默认 C-order,直接 tobytes() 即可) f.write(arr.astype(np.float32).tobytes()) # 保存每个层 for i, layer in enumerate(layers): cls_arr = cls_banks\[i\].numpy() # \[N, C

patch_arr = patch_banks[i].numpy() # [N, L, C]

save_array_as_bin(cls_arr, f'./bin/cls_layer{layer}.bin')

save_array_as_bin(patch_arr, f'./bin/patch_layer{layer}.bin')

保存层索引(简单保存为 int 列表)

with open('layers.bin', 'wb') as f:

f.write(struct.pack('i', len(layers)))

for layer in layers:

f.write(struct.pack('i', layer))

print("已转换为 .bin 文件")

onnx测试脚本

"test_onnx_openvino2.py"

import numpy as np

import os

import struct

import cv2

import matplotlib.pyplot as plt

from PIL import Image

import torchvision.transforms as transforms

import sys

import time

兼容新旧 OpenVINO 导入

try:

from openvino.runtime import Core, Tensor

except ImportError:

from openvino import Core, Tensor

-------------------------- 二进制记忆库读取 --------------------------

def read_2d_array_from_bin(file_path):

with open(file_path, 'rb') as f:

ndim = struct.unpack('i', f.read(4))[0]

if ndim != 2:

raise ValueError(f"期望二维数组,实际维度 {ndim}")

rows = struct.unpack('i', f.read(4))[0]

cols = struct.unpack('i', f.read(4))[0]

data = np.frombuffer(f.read(), dtype=np.float32).reshape(rows, cols)

print(f"读取 {file_path}: 形状 {data.shape}")

return data

def read_3d_array_from_bin(file_path):

with open(file_path, 'rb') as f:

ndim = struct.unpack('i', f.read(4))[0]

if ndim != 3:

raise ValueError(f"期望三维数组,实际维度 {ndim}")

d1 = struct.unpack('i', f.read(4))[0]

d2 = struct.unpack('i', f.read(4))[0]

d3 = struct.unpack('i', f.read(4))[0]

data = np.frombuffer(f.read(), dtype=np.float32).reshape(d1, d2, d3)

print(f"读取 {file_path}: 形状 {data.shape}")

return data

def read_layers_from_bin(file_path):

with open(file_path, 'rb') as f:

count = struct.unpack('i', f.read(4))[0]

layers = []

for _ in range(count):

layers.append(struct.unpack('i', f.read(4))[0])

print(f"读取 {file_path}: 层索引 {layers}")

return layers

-------------------------- 图像预处理 --------------------------

def preprocess_image(image_path, input_size=448):

transform = transforms.Compose([

transforms.Resize((512, 512)),

transforms.ToTensor(),

transforms.CenterCrop(input_size),

transforms.Normalize(mean=[0.485, 0.456, 0.406],

std=[0.229, 0.224, 0.225])

])

img = Image.open(image_path).convert('RGB')

img_tensor = transform(img)

img_np = img_tensor.numpy()

img_np = np.expand_dims(img_np, axis=0).astype(np.float32)

return img_np

-------------------------- 工具函数 --------------------------

def normalize(vec):

norm = np.linalg.norm(vec) + 1e-8

return vec / norm

def resize_bilinear(src, dst_h, dst_w):

src_h, src_w = src.shape

scale_x = (src_w - 1) / (dst_w - 1) if dst_w > 1 else 0

scale_y = (src_h - 1) / (dst_h - 1) if dst_h > 1 else 0

dst = np.zeros((dst_h, dst_w), dtype=np.float32)

for y in range(dst_h):

fy = y * scale_y

y0 = int(fy)

y1 = min(y0 + 1, src_h - 1)

wy1 = fy - y0

wy0 = 1 - wy1

for x in range(dst_w):

fx = x * scale_x

x0 = int(fx)

x1 = min(x0 + 1, src_w - 1)

wx1 = fx - x0

wx0 = 1 - wx1

v00 = src[y0, x0]

v01 = src[y0, x1]

v10 = src[y1, x0]

v11 = src[y1, x1]

v0 = v00 * wx0 + v01 * wx1

v1 = v10 * wx0 + v11 * wx1

dst[y, x] = v0 * wy0 + v1 * wy1

return dst

def gaussian_kernel(size=5, sigma=1.0):

ax = np.linspace(-(size // 2), size // 2, size)

xx, yy = np.meshgrid(ax, ax)

kernel = np.exp(-0.5 * (xx**2 + yy**2) / sigma**2)

kernel = kernel / np.sum(kernel)

return kernel

def apply_gaussian_blur(anomaly_map, kernel_size=5, sigma=1.0):

from scipy.ndimage import convolve

kernel = gaussian_kernel(kernel_size, sigma)

return convolve(anomaly_map, kernel, mode='reflect')

-------------------------- 主推理类 --------------------------

class RADInference:

def init(self, onnx_path, bank_dir, k_image=150, layer_weights=None):

print("初始化 RADInference...")

self.k_image = k_image

加载 ONNX 模型

print(f"加载 ONNX 模型: {onnx_path}")

self.core = Core()

model = self.core.read_model(onnx_path)

self.compiled_model = self.core.compile_model(model, "CPU")

self.infer_request = self.compiled_model.create_infer_request()

print("模型加载成功,输出名称列表:")

for out in self.compiled_model.outputs:

print(f" {out.any_name}")

加载记忆库

print(f"加载记忆库: {bank_dir}")

layers_path = os.path.join(bank_dir, "layers.bin")

self.layers = read_layers_from_bin(layers_path)

self.num_layers = len(self.layers)

self.cls_banks = []

self.patch_banks = []

for layer in self.layers:

cls_path = os.path.join(bank_dir, f"cls_layer{layer}.bin")

patch_path = os.path.join(bank_dir, f"patch_layer{layer}.bin")

self.cls_banks.append(read_2d_array_from_bin(cls_path)) # [N, C]

self.patch_banks.append(read_3d_array_from_bin(patch_path)) # [N, L, C]

预归一化 patch 记忆库(加速后续计算)

print("预归一化 patch 记忆库...")

self.patch_banks_norm = []

for pb in self.patch_banks:

norms = np.linalg.norm(pb, axis=2, keepdims=True) + 1e-8

pb_norm = pb / norms

self.patch_banks_norm.append(pb_norm)

层权重

if layer_weights is None:

self.layer_weights = np.ones(self.num_layers) / self.num_layers

else:

self.layer_weights = np.array(layer_weights) / sum(layer_weights)

其他常量

self.embed_dim = self.cls_banks[0].shape[1] # 384

self.num_patches = self.patch_banks[0].shape[1] # 784

self.patch_h = self.patch_w = int(np.sqrt(self.num_patches)) # 28

print(f"初始化完成: 层数={self.num_layers}, 嵌入维度={self.embed_dim}, patch数={self.num_patches}")

def infer(self, image_path, output_size=256, apply_smoothing=True):

print("\n===== 开始推理 =====")

start_total = time.time()

1. 预处理

print("步骤1: 图像预处理...")

t = time.time()

input_data = preprocess_image(image_path)

print(f" 耗时: {time.time()-t:.2f}s")

2. 创建 Tensor 并设置输入

print("步骤2: 创建输入 Tensor...")

t = time.time()

input_tensor = Tensor(input_data)

self.infer_request.set_input_tensor(0, input_tensor)

print(f" 耗时: {time.time()-t:.2f}s")

3. 推理

print("步骤3: 执行推理...")

t = time.time()

self.infer_request.infer()

print(f" 耗时: {time.time()-t:.2f}s")

4. 获取各层输出

print("步骤4: 获取输出张量...")

t = time.time()

patch_list = []

cls_list = []

for idx in self.layers:

patch_key = f'patch_{idx}'

cls_key = f'cls_{idx}'

patch_tensor = self.infer_request.get_tensor(patch_key)

cls_tensor = self.infer_request.get_tensor(cls_key)

patch_list.append(patch_tensor.data) # [1, L, C]

cls_list.append(cls_tensor.data) # [1, C]

print(f" 耗时: {time.time()-t:.2f}s")

5. 图像级检索(使用最高层 CLS)

print("步骤5: 图像级检索...")

t = time.time()

query_cls = cls_list[-1][0] # [C]

norm_query_cls = normalize(query_cls)

cls_bank_global = self.cls_banks[-1] # [N, C]

计算余弦相似度

cls_norms = np.linalg.norm(cls_bank_global, axis=1) + 1e-8

similarities = np.dot(cls_bank_global, norm_query_cls) / cls_norms

topk_indices = np.argsort(similarities)[-self.k_image:][::-1]

print(f" 最高层检索完成,top-{self.k_image} 索引: {topk_indices[:5]}...")

print(f" 耗时: {time.time()-t:.2f}s")

6. 逐层 patch-KNN(优化版本)

print("步骤6: 逐层 patch-KNN...")

fused_scores = np.zeros(self.num_patches)

for li in range(self.num_layers):

layer_start = time.time()

print(f" 处理层 {self.layers[li]}...")

test_patch = patch_list[li][0] # [L, C]

归一化测试 patch

test_patch_norm = np.zeros_like(test_patch)

for p in range(self.num_patches):

test_patch_norm[p] = normalize(test_patch[p])

从预归一化的记忆库中取出 top-k 个训练样本的 patch 特征 [k, L, C]

bank_patch_norm = self.patch_banks_norm[li][topk_indices] # [k, L, C]

重塑为 [k*L, C]

kL = self.k_image * self.num_patches

bank_flat = bank_patch_norm.reshape(kL, self.embed_dim) # [117600, 384]

k_actual = len(topk_indices)

kL_actual = k_actual * self.num_patches

bank_flat = bank_patch_norm.reshape(kL_actual, self.embed_dim)

对每个查询 patch 计算与所有 bank patch 的相似度

layer_scores = np.zeros(self.num_patches)

for p in range(self.num_patches):

q = test_patch_norm[p] # [384]

sims = np.dot(bank_flat, q) # [kL]

max_sim = np.max(sims)

layer_scores[p] = 1.0 - max_sim

可选:每 100 个打印一次进度

if (p + 1) % 200 == 0:

print(f" 已处理 {p+1}/{self.num_patches} patches")

fused_scores += self.layer_weights[li] * layer_scores

print(f" 层 {self.layers[li]} 处理完成,分数范围 [{layer_scores.min():.4f}, {layer_scores.max():.4f}], 耗时 {time.time()-layer_start:.2f}s")

7. 重塑为 28x28

patch_map = fused_scores.reshape(self.patch_h, self.patch_w)

8. 上采样

print("步骤8: 上采样...")

t = time.time()

anomaly_map = resize_bilinear(patch_map, output_size, output_size)

print(f" 耗时: {time.time()-t:.2f}s")

9. 高斯平滑

if apply_smoothing:

print("步骤9: 高斯平滑...")

t = time.time()

anomaly_map = apply_gaussian_blur(anomaly_map, kernel_size=5, sigma=1.0)

print(f" 耗时: {time.time()-t:.2f}s")

10. 图像级分数

img_score = np.max(anomaly_map)

print(f"步骤10: 图像级分数 = {img_score:.4f}")

print(f"===== 推理结束,总耗时 {time.time()-start_total:.2f}s =====\n")

return anomaly_map, img_score

def release(self):

pass

-------------------------- 可视化 --------------------------

def visualize(image_path, anomaly_map, img_score, save_path=None):

img = cv2.imread(image_path)

if img is None:

print(f"无法读取图像: {image_path}")

return

img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

归一化异常图

am_norm = (anomaly_map - anomaly_map.min()) / (anomaly_map.max() - anomaly_map.min() + 1e-8)

am_uint8 = (am_norm * 255).astype(np.uint8)

生成热图

heatmap = cv2.applyColorMap(am_uint8, cv2.COLORMAP_JET)

heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)

调整大小

h, w = img.shape[:2]

heatmap_resized = cv2.resize(heatmap, (w, h), interpolation=cv2.INTER_LINEAR)

叠加

alpha = 0.5

overlay = cv2.addWeighted(img, 1 - alpha, heatmap_resized, alpha, 0)

显示

plt.figure(figsize=(15, 5))

plt.subplot(1, 4, 1)

plt.imshow(img)

plt.title("Original Image")

plt.axis('off')

plt.subplot(1, 4, 2)

plt.imshow(heatmap_resized)

plt.title("Anomaly Heatmap")

plt.axis('off')

plt.subplot(1, 4, 3)

plt.imshow(overlay)

plt.title(f"Overlay (score={img_score:.4f})")

plt.axis('off')

plt.subplot(1, 4, 4)

plt.imshow(am_norm, cmap='jet')

plt.title("Raw Anomaly Map")

plt.axis('off')

plt.tight_layout()

if save_path:

plt.savefig(save_path, dpi=150, bbox_inches='tight')

print(f"可视化结果已保存至 {save_path}")

plt.show()

-------------------------- 主程序 --------------------------

if name == "main":

参数配置

onnx_file = "dinov3_multilayer.onnx"

bank_directory = "bin"

test_image = "000.png"

output_vis = "result.png"

检查文件是否存在

if not os.path.exists(onnx_file):

print(f"错误: ONNX 文件不存在: {onnx_file}")

sys.exit(1)

if not os.path.exists(bank_directory):

print(f"错误: 记忆库目录不存在: {bank_directory}")

sys.exit(1)

if not os.path.exists(test_image):

print(f"错误: 测试图像不存在: {test_image}")

sys.exit(1)

try:

初始化

rad = RADInference(onnx_file, bank_directory, k_image=150)

推理

anomaly_map, score = rad.infer(test_image, output_size=256, apply_smoothing=True)

可视化

visualize(test_image, anomaly_map, score, save_path=output_vis)

print("程序正常结束。")

except Exception as e:

print(f"发生错误: {e}")

import traceback

traceback.print_exc()

相关推荐
小浪花a1 小时前
计算机二级python-jieba库
开发语言·python
小和尚同志2 小时前
还有人在问 Skills 是啥?感觉和 prompt 一样
人工智能·aigc
星和月2 小时前
人工智能与神经网络
人工智能
骇客野人2 小时前
自己手搓磁盘清理工具(JAVA版)
java·开发语言
清风徐来QCQ2 小时前
Java笔试总结一
java·开发语言
InCerry2 小时前
C# .NET 周刊|2026年2月4期
c#·.net周报·.net周刊
lly2024062 小时前
《jEasyUI 转换 HTML 表格为数据网格》
开发语言
田里的水稻2 小时前
ubuntu22.04_构建openclaw开发框架
运维·人工智能·python
萧曵 丶2 小时前
LangChain Model IO 提示词模版(Python版)
开发语言·python·langchain