使用Onnruntime实现Padim/Patchcore推理

Train.py

python 复制代码
from pathlib import Path
from anomalib.data import Folder
from anomalib.models import Patchcore
from anomalib.engine import Engine
def main():
    # 1. 配置数据集
    datamodule = Folder(
        name="bottledataset",
        root=Path("D:/bottle"),  # 数据集根目录
        normal_dir="good",  # 正常样本文件夹名
        abnormal_dir="defect",  # 异常样本文件夹名
        train_batch_size=32,
        eval_batch_size=32,
        num_workers=4,
    )
    # 2. 定义 PatchCore 模型
    model = Patchcore(
        backbone="wide_resnet50_2",  # 特征提取骨干网络
        layers=["layer2", "layer3"],  # 提取的层
        pre_trained=True,  # 使用预训练权重
        num_neighbors=9  # 核心参数:邻居数量
    )
    # 3. 配置引擎并开始训练
    engine = Engine(
        max_epochs=1,  # PatchCore 通常 1 个 epoch 即可收敛
        accelerator="auto",  # 自动检测并使用 GPU/CPU
        devices=1
    )
    engine.fit(model=model, datamodule=datamodule)
    # 4. 训练结束后,保存模型权重
    engine.trainer.save_checkpoint("patchcore_model.ckpt", weights_only=False)
if __name__ == '__main__':
    main()

Predict.py

python 复制代码
from anomalib.engine import Engine
from anomalib.models import Patchcore
import numpy as np
model = Patchcore(
    backbone="wide_resnet50_2"
)
engine = Engine()
predictions = engine.predict(
    model=model,
    ckpt_path="./results/Patchcore/bottledataset/latest/weights/lightning/model.ckpt",
    data_path="./test.png"
)
for p in predictions:
    print(
        "score:",
        p.pred_score
    )
    print(
        "label:",
        p.pred_label
    )
    print(
        "mask:",
        p.anomaly_map
    )

Export

python 复制代码
from anomalib.engine import Engine
from anomalib.deploy import ExportType
from anomalib.models import Patchcore
model = Patchcore()
engine = Engine()
engine.export(
    model=model,
    ckpt_path="./results/Patchcore/bottledataset/latest/weights/lightning/model.ckpt",
    export_type=ExportType.ONNX,
    input_size=(256,256)
)

Deploy.py

python 复制代码
import cv2
import numpy as np
import onnxruntime as ort
import time
# 1. 配置路径
image_path = "test.png"
model_path = "model.onnx"
# 2. 初始化 ONNX Runtime 会话
options = ort.SessionOptions()
options.log_severity_level = 3  # ORT_LOGGING_LEVEL_INFO
session = ort.InferenceSession(model_path, options, providers=['CPUExecutionProvider'])
# 3. 读取图像并预处理
original_image = cv2.imread(image_path)
if original_image is None:
    raise ValueError(f"Error: Could not read image at {image_path}")
original_height, original_width = original_image.shape[:2]
inp_height, inp_width = 256, 256
# BGR 转 RGB
rgb_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)
# Resize 并归一化到 [0, 1]
resized = cv2.resize(rgb_image, (inp_width, inp_height))
resized = resized.astype(np.float32) / 255.0
# HWC 转 CHW (NCHW 格式)
input_data = np.transpose(resized, (2, 0, 1))  # Shape: (3, H, W)
input_data = np.expand_dims(input_data, axis=0)  # Shape: (1, 3, H, W)
# 4. 执行推理并计时
input_name = session.get_inputs()[0].name
start_time = time.time()
results = session.run(None, {input_name: input_data})
end_time = time.time()
inference_time_ms = (end_time - start_time) * 1000
# 5. 解析输出结果
# 假设输出顺序为: pred_score, pred_label, anomaly_map, pred_mask
# 为了更稳健,也可以通过遍历 session.get_outputs() 匹配名称
output_names = [out.name for out in session.get_outputs()]
result_dict = dict(zip(output_names, results))
score = result_dict.get("pred_score", np.array([0.0]))[0]
label = bool(result_dict.get("pred_label", np.array([False]))[0])
anomaly_map = result_dict.get("anomaly_map")
pred_mask = result_dict.get("pred_mask")
# 6. 解析 anomaly_map 形状
if anomaly_map is not None:
    map_dims = anomaly_map.shape
    map_h, map_w = 0, 0
    if len(map_dims) == 4 and map_dims[0] == 1 and map_dims[1] == 1:
        map_h, map_w = map_dims[2], map_dims[3]
    elif len(map_dims) == 3 and map_dims[0] == 1:
        map_h, map_w = map_dims[1], map_dims[2]
    else:
        total = anomaly_map.size
        map_h = int(np.sqrt(total))
        map_w = map_h
        if map_h * map_w != total:
            map_w = total // map_h
    anomaly_map = anomaly_map.reshape(map_h, map_w)
else:
    raise ValueError("anomaly_map not found in model outputs")
# 7. 后处理:生成热力图并叠加
# Resize 到原始图像尺寸
anomaly_resized = cv2.resize(anomaly_map, (original_width, original_height), interpolation=cv2.INTER_LINEAR)
# Min-Max 归一化到 [0, 1]
min_val, max_val = np.min(anomaly_resized), np.max(anomaly_resized)
if max_val - min_val > 1e-6:
    anomaly_norm = (anomaly_resized - min_val) / (max_val - min_val)
else:
    anomaly_norm = anomaly_resized.copy()
# 转为 8-bit 灰度图并应用 JET 伪彩色
anomaly_gray = (anomaly_norm * 255).astype(np.uint8)
heatmap = cv2.applyColorMap(anomaly_gray, cv2.COLORMAP_JET)
# 热力图与原图融合 (权重 0.5)
overlay = cv2.addWeighted(heatmap, 0.5, original_image, 0.5, 0)
# 8. 叠加 pred_mask 轮廓
if pred_mask is not None:
    # 将 bool 数组转为 uint8 掩膜
    mask_mat = (pred_mask.reshape(map_h, map_w).astype(np.uint8)) * 255
    mask_resized = cv2.resize(mask_mat, (original_width, original_height))
    # 创建红色半透明图层并只在 mask 区域叠加
    mask_color = np.zeros_like(original_image, dtype=np.uint8)
    mask_color[:, :, 2] = 255  # 红色通道
    blended = cv2.addWeighted(overlay, 1.0, mask_color, 0.3, 0)
    # 使用 mask 作为掩膜,将 blended 覆盖到 overlay 上
    overlay = np.where(mask_resized[:, :, np.newaxis] == 255, blended, overlay)
# 9. 拼接图像并输出结果
result_overlay_image = overlay.copy()
img_gray_to_color = cv2.cvtColor(mask_resized, cv2.COLOR_GRAY2BGR) if pred_mask is not None else np.zeros_like(
    original_image)
result_image = np.hstack([original_image, result_overlay_image, img_gray_to_color])
# 打印结果
print(f"推理耗时: {inference_time_ms:.2f} ms")
print(f"异常分数: {score:.4f}")
print(f"异常判定: {'异常' if label else '正常'}")
# 保存并显示结果
cv2.imwrite("result.png", result_image)
cv2.imshow("demo", result_image)
cv2.waitKey(5000)
cv2.destroyAllWindows()

Deploy.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string image_path = "test.png";
            string model_path = "padim.onnx";
            DateTime dt1 = DateTime.Now;
            DateTime dt2 = DateTime.Now;
            Mat resultOverlayImage;         
            SessionOptions options = new SessionOptions();
            options.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO;
            options.AppendExecutionProvider_CPU(0); ;
            InferenceSession onnx_session = new InferenceSession(model_path, options); 
            Tensor<float> input_tensor;
            List<NamedOnnxValue> input_container = new List<NamedOnnxValue>();
            IDisposableReadOnlyCollection<DisposableNamedOnnxValue> result_infer;
            int inpHeight = 256, inpWidth = 256;
            Mat originalImage = new Mat(image_path);
            int originalWidth = originalImage.Cols;
            int originalHeight = originalImage.Rows;
            Mat rgbImage = new Mat();
            Cv2.CvtColor(originalImage, rgbImage, ColorConversionCodes.BGR2RGB);
            Mat resized = new Mat();
            Cv2.Resize(rgbImage, resized, new OpenCvSharp.Size(inpWidth, inpHeight));
            resized.ConvertTo(resized, MatType.CV_32FC3, 1.0 / 255.0);
            int height = inpHeight;
            int width = inpWidth;
            Mat[] channels = Cv2.Split(resized);  
            List<float> dataList = new List<float>();
            for (int c = 0; c < 3; c++)
            {
                float[] channelData = new float[height * width];
                System.Runtime.InteropServices.Marshal.Copy(channels[c].Data, channelData, 0, height * width);
                dataList.AddRange(channelData);
            }
            float[] inputData = dataList.ToArray();
            input_tensor = new DenseTensor<float>(inputData, new[] { 1, 3, height, width });
            input_container.Clear();
            input_container.Add(NamedOnnxValue.CreateFromTensor("input", input_tensor));
            dt1 = DateTime.Now;
            result_infer = onnx_session.Run(input_container);
            dt2 = DateTime.Now;
            var pred_score_tensor = result_infer.FirstOrDefault(x => x.Name == "pred_score")?.AsTensor<float>();
            var pred_label_tensor = result_infer.FirstOrDefault(x => x.Name == "pred_label")?.AsTensor<bool>();
            var anomaly_map_tensor = result_infer.FirstOrDefault(x => x.Name == "anomaly_map")?.AsTensor<float>();
            var pred_mask_tensor = result_infer.FirstOrDefault(x => x.Name == "pred_mask")?.AsTensor<bool>();
            float score = pred_score_tensor.First(); 
            bool label = pred_label_tensor?.First() ?? false;
            // 解析 anomaly_map:形状通常为 [1, 1, H, W] 或 [1, H, W]
            var dimensions = anomaly_map_tensor.Dimensions.ToArray();
            int mapH = dimensions.Length >= 3 ? dimensions[dimensions.Length - 2] : 0;
            int mapW = dimensions.Length >= 2 ? dimensions[dimensions.Length - 1] : 0;
            float[] mapData = anomaly_map_tensor.ToArray();
            // 确保是二维数据,如果有多余维度则 reshape
            if (dimensions.Length == 4 && dimensions[0] == 1 && dimensions[1] == 1)
            {
                // 已经是 [1,1,H,W] 直接使用
                mapH = dimensions[2];
                mapW = dimensions[3];
            }
            else if (dimensions.Length == 3 && dimensions[0] == 1)
            {
                // [1,H,W] 的情况
                mapH = dimensions[1];
                mapW = dimensions[2];
            }
            else
            {
                // 若形状不符合预期,尝试平铺后重新组织
                int total = mapData.Length;
                mapH = (int)Math.Sqrt(total);
                mapW = mapH;
                if (mapH * mapW != total) mapW = total / mapH;
            }
            // 将 anomaly_map 转为 Mat (CV_32FC1)
            Mat anomalyMat = Mat.FromPixelData(mapH, mapW, MatType.CV_32FC1, mapData);
            // ------------------ 后处理 ------------------
            // 1. 将异常图 resize 到原始图像尺寸
            Mat anomalyResized = new Mat();
            Cv2.Resize(anomalyMat, anomalyResized, new OpenCvSharp.Size(originalWidth, originalHeight), interpolation: InterpolationFlags.Linear);
            // 2. Min-Max 归一化到 [0,1] 范围
            double minVal, maxVal;
            Cv2.MinMaxLoc(anomalyResized, out minVal, out maxVal);
            Mat anomalyNorm = new Mat();
            if (maxVal - minVal > 1e-6)
            {
                anomalyResized.ConvertTo(anomalyNorm, MatType.CV_32FC1, 1.0 / (maxVal - minVal), -minVal / (maxVal - minVal));
            }
            else
            {
                anomalyNorm = anomalyResized.Clone();
            }
            Mat anomalyGray = new Mat();
            anomalyNorm.ConvertTo(anomalyGray, MatType.CV_8UC1, 255.0);
            // 4. 应用 JET 伪彩色生成热力图
            Mat heatmap = new Mat();
            Cv2.ApplyColorMap(anomalyGray, heatmap, ColormapTypes.Jet);
            // 5. 热力图与原图融合(权重 0.5 热力图 + 0.5 原图)
            Mat originalBGR = originalImage.Clone();
            Mat overlay = new Mat();
            Cv2.AddWeighted(heatmap, 0.5, originalBGR, 0.5, 0, overlay);
            Mat maskResized = new Mat();
            // 叠加 pred_mask 轮廓(二值掩膜)
            if (pred_mask_tensor != null)
            {
                bool[] maskData = pred_mask_tensor.ToArray();
                // 假设 mask 形状与 anomaly_map 相同,同样 resize 到原图大小
                Mat maskMat = new Mat(mapH, mapW, MatType.CV_8UC1);
                for (int i = 0; i < maskData.Length; i++)
                    maskMat.Set<byte>(i / mapW, i % mapW, maskData[i] ? (byte)255 : (byte)0);
                maskResized = new Mat();
                Cv2.Resize(maskMat, maskResized, new OpenCvSharp.Size(originalWidth, originalHeight));
                // 创建红色半透明图层
                Mat maskColor = new Mat(originalHeight, originalWidth, MatType.CV_8UC3, new Scalar(0, 0, 255));
                // 先计算全图加权(无 mask)
                Mat blended = new Mat();
                Cv2.AddWeighted(overlay, 1.0, maskColor, 0.3, 0, blended);
                // 将 mask 区域从 blended 复制到 overlay 中
                blended.CopyTo(overlay, maskResized);
            }
            resultOverlayImage = overlay.Clone();
            string resultText = $"推理耗时: {(dt2 - dt1).TotalMilliseconds:F2} ms\r\n";
            resultText += $"异常分数: {score:F4}\r\n";
            resultText += $"异常判定: {(label ? "异常" : "正常")}";
            Mat imgGrayToColor = new Mat();
            Cv2.CvtColor(maskResized, imgGrayToColor, ColorConversionCodes.GRAY2BGR);
            Mat[] imagesToConcat = { originalImage, resultOverlayImage, imgGrayToColor};
            Mat resultImage = new Mat();
            Cv2.HConcat(imagesToConcat, resultImage);
            Console.WriteLine(resultText);
            Cv2.ImWrite("result.png",resultImage);
            Cv2.ImShow("demo", resultImage);
            Cv2.WaitKey(5000);
        }
    }
}

Deplpy.cpp

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <chrono>
#include <cmath>
#include <algorithm>
#include <onnxruntime_cxx_api.h>
#include <opencv2/opencv.hpp>
int main() {
    std::string image_path = "test.png";
    std::string model_path = "model.onnx";
    // 1. 初始化 ONNX Runtime 会话
    Ort::Env env(ORT_LOGGING_LEVEL_INFO, "ConsoleApp");
    Ort::SessionOptions session_options;
    std::wstring modelPath = std::wstring(model_path.begin(), model_path.end());
    session_options.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
    Ort::Session session_(env, modelPath.c_str(), session_options);
    // 2. 读取图像并预处理
    cv::Mat originalImage = cv::imread(image_path);
    if (originalImage.empty()) {
        std::cerr << "Error: Could not read image." << std::endl;
        return -1;
    }
    int originalWidth = originalImage.cols;
    int originalHeight = originalImage.rows;
    int inpHeight = 256, inpWidth = 256;
    // BGR 转 RGB
    cv::Mat rgbImage;
    cv::cvtColor(originalImage, rgbImage, cv::COLOR_BGR2RGB);
    // Resize 并归一化到 [0, 1]
    cv::Mat resized;
    cv::resize(rgbImage, resized, cv::Size(inpWidth, inpHeight));
    resized.convertTo(resized, CV_32FC3, 1.0 / 255.0);
    // HWC 转 CHW (NCHW 格式)
    std::vector<float> inputData(3 * inpHeight * inpWidth);
    std::vector<cv::Mat> channels(3);
    cv::split(resized, channels);
    for (int c = 0; c < 3; ++c) {
        std::memcpy(inputData.data() + c * inpHeight * inpWidth,
            channels[c].data,
            inpHeight * inpWidth * sizeof(float));
    }
    // 3. 创建输入 Tensor 并推理
    std::array<int64_t, 4> input_shape = { 1, 3, inpHeight, inpWidth };
    auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
        memory_info, inputData.data(), inputData.size(),
        input_shape.data(), input_shape.size()
    );
    const char* input_names[] = { "input" };
    const char* output_names[] = { "pred_score", "pred_label", "anomaly_map", "pred_mask" };
    auto start_time = std::chrono::high_resolution_clock::now();
    auto output_tensors = session_.Run(
        Ort::RunOptions{ nullptr },
        input_names, &input_tensor, 1,
        output_names, 4
    );
    auto end_time = std::chrono::high_resolution_clock::now();
    double inference_time_ms = std::chrono::duration<double, std::milli>(end_time - start_time).count();
    // 4. 解析输出结果
    float* pred_score_data = output_tensors[0].GetTensorMutableData<float>();
    float score = pred_score_data[0];
    bool* pred_label_data = output_tensors[1].GetTensorMutableData<bool>();
    bool label = pred_label_data[0];
    // 解析 anomaly_map
    float* map_data = output_tensors[2].GetTensorMutableData<float>();
    auto map_dims = output_tensors[2].GetTensorTypeAndShapeInfo().GetShape();
    int mapH = 0, mapW = 0;
    if (map_dims.size() == 4 && map_dims[0] == 1 && map_dims[1] == 1) {
        mapH = static_cast<int>(map_dims[2]);
        mapW = static_cast<int>(map_dims[3]);
    }
    else if (map_dims.size() == 3 && map_dims[0] == 1) {
        mapH = static_cast<int>(map_dims[1]);
        mapW = static_cast<int>(map_dims[2]);
    }
    else {
        int total = static_cast<int>(map_dims.back()); // fallback
        mapH = static_cast<int>(std::sqrt(total));
        mapW = mapH;
        if (mapH * mapW != total) mapW = total / mapH;
    }
    // 将 float 数据包装为 OpenCV Mat
    cv::Mat anomalyMat(mapH, mapW, CV_32FC1, map_data);
    // 5. 后处理:生成热力图并叠加
    cv::Mat anomalyResized;
    cv::resize(anomalyMat, anomalyResized, cv::Size(originalWidth, originalHeight), 0, 0, cv::INTER_LINEAR);
    // Min-Max 归一化
    double minVal, maxVal;
    cv::minMaxLoc(anomalyResized, &minVal, &maxVal);
    cv::Mat anomalyNorm;
    if (maxVal - minVal > 1e-6) {
        anomalyResized.convertTo(anomalyNorm, CV_32FC1, 1.0 / (maxVal - minVal), -minVal / (maxVal - minVal));
    }
    else {
        anomalyNorm = anomalyResized.clone();
    }
    cv::Mat anomalyGray;
    anomalyNorm.convertTo(anomalyGray, CV_8UC1, 255.0);
    // 应用 JET 伪彩色
    cv::Mat heatmap;
    cv::applyColorMap(anomalyGray, heatmap, cv::COLORMAP_JET);
    // 热力图与原图融合 (0.5, 0.5)
    cv::Mat overlay;
    cv::addWeighted(heatmap, 0.5, originalImage, 0.5, 0, overlay);
    // 6. 叠加 pred_mask 轮廓
    cv::Mat maskResized;
    bool* mask_data = output_tensors[3].GetTensorMutableData<bool>();
    cv::Mat maskMat(mapH, mapW, CV_8UC1);
    for (int i = 0; i < mapH * mapW; ++i) {
        maskMat.data[i] = mask_data[i] ? 255 : 0;
    }
    cv::resize(maskMat, maskResized, cv::Size(originalWidth, originalHeight));
    // 创建红色半透明图层并只在 mask 区域叠加
    cv::Mat maskColor(originalHeight, originalWidth, CV_8UC3, cv::Scalar(0, 0, 255));
    cv::Mat blended;
    cv::addWeighted(overlay, 1.0, maskColor, 0.3, 0, blended);
    blended.copyTo(overlay, maskResized);
    // 7. 拼接图像并输出结果
    cv::Mat resultOverlayImage = overlay.clone();
    cv::Mat imgGrayToColor;
    cv::cvtColor(maskResized, imgGrayToColor, cv::COLOR_GRAY2BGR);
    std::vector<cv::Mat> imagesToConcat = { originalImage, resultOverlayImage, imgGrayToColor };
    cv::Mat resultImage;
    cv::hconcat(imagesToConcat, resultImage);
    std::cout << "推理耗时: " << std::fixed << std::setprecision(2) << inference_time_ms << " ms\n";
    std::cout << "异常分数: " << std::fixed << std::setprecision(4) << score << "\n";
    std::cout << "异常判定: " << (label ? "异常" : "正常") << std::endl;
    cv::imwrite("result.png", resultImage);
    cv::imshow("demo", resultImage);
    cv::waitKey(5000);
    return 0;
}

CMakeLists.txt

bash 复制代码
cmake_minimum_required(VERSION 3.18)
project(Patch)
set("OpenCV_DIR" "E:\\Opencv\\opencv_vs\\build")
set("ONNXRUNTIME_DIR" "E:\\Onnxruntime\\cpu\\1.15")
set(OpenCV_INCLUDE_DIRS ${OpenCV_DIR}\\include)
set(OpenCV_LIB_DIRS ${OpenCV_DIR}\\x64\\vc16\\lib) 
set(OpenCV_LIBS "opencv_world480d.lib" "opencv_world480.lib")    
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(${OpenCV_INCLUDE_DIRS}) 
link_directories(${OpenCV_LIB_DIRS})  
find_package(OpenCV QUIET)	
link_libraries(${OpenCV_LIBS})
add_executable(Patch Deploy.cpp)
target_compile_features(Patch PRIVATE cxx_std_14)
find_library(PATH ${ONNXRUNTIME_DIR})
target_include_directories(Patch PRIVATE "${ONNXRUNTIME_DIR}/include")
target_link_libraries(Patch "${ONNXRUNTIME_DIR}/lib/onnxruntime.lib")
相关推荐
前端H2 小时前
Biome & Rolldown:Rust 工具链接管前端基建
开发语言·前端·rust
天天进步20152 小时前
UI-TARS 源码解析 #4:UI-TARS 与传统 RPA 的区别:为什么它不是简单的坐标点击脚本?
开发语言
运维行者_2 小时前
广域网性能监控:分布式IT架构下的链路质量保障
开发语言·网络·分布式·后端·架构·数据库架构
梅雅达编程笔记2 小时前
零基础学 Python 第7章 | 字典 dict:键值对存储
开发语言·python·beautifulsoup·numpy·pandas
灯澜忆梦2 小时前
GO---map函数
开发语言·前端·后端·golang
北冥you鱼3 小时前
Go语言与默克尔树:区块链数据完整性的基石
开发语言·golang·区块链
库克克3 小时前
【C++】类和对象--this指针详解
java·开发语言·c++
JackieZhengChina3 小时前
image-viewer-js 开源图片查看插件完整教程
开发语言·javascript·开源
光影少年3 小时前
react的View/Text/Image/ScrollView 常用组件注意事项
开发语言·前端·javascript·react.js·前端框架