U_NET 网络onnx部署

第一步是导出ONNX 

import os
import cv2
import archs_true
import onnxruntime
import torch
from albumentations import Compose
from albumentations.augmentations import transforms
from torch.utils.data import DataLoader





def pth_2onnx():
    # 创建 UNet 模型
    model = archs_true.UNet(num_classes=4)

    # 导入模型参数
    model_state_dict = torch.load("models/turbine_unet_random_15/model1000epoch_e-5.pth",
                                  map_location=lambda storage, loc: storage)
    model.load_state_dict(model_state_dict)

    # 将模型转换为 CPU 格式
    device = torch.device("cpu")
    model.to(device)
    model.eval()  # 进入评估模式

    # 创建虚拟输入(dummy input)
    dummy_input = torch.randn(1, 3, 800, 800, device=device)

    # 定义输入和输出的名称
    input_names = ["input"]
    output_names = ["output"]

    # 将模型转换为 ONNX 格式
    torch.onnx.export(model, dummy_input, "model.onnx", opset_version=12,verbose=False,  do_constant_folding=False, input_names=input_names,
                      output_names=output_names)

if __name__ == '__main__':
    pth_2onnx()

第二步进行C++部署

#include <fstream>
#include <iostream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <onnxruntime_cxx_api.h>
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>
#include<opencv2/dnn/dnn.hpp>
#include <algorithm>
#include "torch/script.h"
#include "torch/torch.h"

using namespace std;
using namespace cv;

//输入数据的长宽、通道数
static const int INPUT_H = 800;
static const int INPUT_W = 800;
static const int INPUT_CHANNELS =  3;
static const int OUTPUT_SIZE = 3 * 800 * 800;

int findMax(int a, int b, int c, int d) {
    int index = 0;
    int max_val = a;
    if (b > max_val) {
        max_val = b;
        index = 1;
    }
    if (c > max_val) {
        max_val = c;
        index = 2;
    }
    if (d > max_val) {
        max_val = d;
        index = 3;
    }
    return index;
}


cv::Mat flattenImage(const cv::Mat& image) {

    // 将图像 reshape 成一维向量
    cv::Mat flattened = image.reshape(1, image.rows * image.cols * 3 );

    // 转置矩阵,使得每个像素值都在一行中
    flattened = flattened.t();

    return flattened;
}

void test();

int main() {
    std::string imgpath = "121.png";
    cv::Mat test_image = cv::imread(imgpath);
    //cout << test_image;
    //cout << test_image << endl;
    cv::Scalar mean, std;
    mean[0] = 0.485 * 255;
    mean[1] = 0.456 * 255;
    mean[2] = 0.406 * 255;
    std[0] = 0.229 * 255;
    std[1] = 0.224 * 255;
    std[2] = 0.225 * 255;
    //cv::meanStdDev(test_image, mean, std);
    //cout << flattenedImage.size() << endl;
    //cout << test_image.channels() << endl;
    cout << mean << std << endl;
    //float m_mean = mean[0];
    //float m_std = std[0];
    utils::logging::setLogLevel(utils::logging::LOG_LEVEL_ERROR);//设置OpenCV只输出错误日志
    Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "U_NET");
    Ort::SessionOptions session_options;
    session_options.SetIntraOpNumThreads(1);//设置线程数
    session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);//启用模型优化策略

    //CUDA option set
    OrtCUDAProviderOptions cuda_option;
    cuda_option.device_id = 0;
    cuda_option.arena_extend_strategy = 0;
    cuda_option.cudnn_conv_algo_search = OrtCudnnConvAlgoSearchExhaustive;
    cuda_option.gpu_mem_limit = SIZE_MAX;
    cuda_option.do_copy_in_default_stream = 1;
    //CUDA 加速
    //session_options.SetGraphOptimizationLevel() 函数用于设置在使用 ORT 库执行模型时应用的图优化级别。
    //ORT_ENABLE_ALL 选项启用所有可用的优化,这可以导致更快速和更高效的执行。
    session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
    //OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);
    session_options.AppendExecutionProvider_CUDA(cuda_option);

#ifdef _WIN32//预处理器指令,用于在编译源代码时判断当前编译环境是否是WIndows环境
    /*
    wchar_t和普通的char之间的区别比较大。主要有以下几个方面:
    1.存储大小:wchar_t类型通常占用两个字节,而char类型通常占用一个字节。这是因为wchar_t类型用于表示Unicode字符集中的字符,
    每个字符需要占用更多的存储空间。
    2.字符集:wchar_t类型用于表示Unicode字符集中的字符,而char类型则用于表示ASCII字符集中的字符。
    Unicode字符集可以表示更广泛的字符集,包括中文、日文、韩文等,而ASCII字符集只能表示英文字母、数字和少量的符号。
    3.字符串表示方式:由于wchar_t类型占用更多的存储空间,
    因此在表示字符串时通常需要使用特殊的字符串类型,如const wchar_t*表示一个指向宽字符字符串的常量指针,
    而普通的char*则表示一个指向普通字符串的指针。
    4.字符串处理函数:由于wchar_t类型和普通的char类型在存储大小和字符集上有所不同,
    因此在处理字符串时通常需要使用不同的字符串处理函数。例如,wcslen()函数用于计算宽字符字符串的长度,
    而strlen()函数用于计算普通字符串的长度。
    */
    const wchar_t* model_path = L"model.onnx"; //表示一个指向常量宽字符字符串L"U_Net.onnx"的指针
#else
    const char* model_path = "yolov5s.onnx";
#endif

    // 第一个参数env是一个ORT环境对象,用于管理ORT会话的资源和配置。
    // 第二个参数model_path是一个指向ONNX模型文件的路径的常量指针,用于指定ORT会话要加载的模型。
    // 第三个参数session_options是一个ORT会话选项对象,用于配置ORT会话的选项和优化策略。
    Ort::Session session(env, model_path, session_options);
    // print model input layer (node names, types, shape etc.)
    Ort::AllocatorWithDefaultOptions allocator;//通过使用Ort::AllocatorWithDefaultOptions类,可以方便地进行内存分配和管理,而无需手动实现内存分配和释放的细节。

    // print number of model input nodes
    size_t num_input_nodes = session.GetInputCount();
    // 输入输出节点的名称
    std::vector<const char*> input_node_names = { "input" };
    std::vector<const char*> output_node_names = { "output" };

    //输出数据的大小
    const size_t input_tensor_size = 1 * INPUT_CHANNELS * INPUT_H * INPUT_W;

    //输入张量
    /*
        目前对于数据预处理方面有一些问题,首先是进行正则化,正则化之后除以255进行归一化
    */

    std::vector<float> input_tensor_values(input_tensor_size);
    //先把第一行的B提取完再将第二行的G
    for (int c = 0; c < 3; c++)
    {
        for (int i = 0; i < 800; i++)
        {
            for (int j = 0; j < 800; j++)
            {
                float pix = test_image.ptr<uchar>(i)[j * 3 +  c];//转换通道,输入onnx模型的图片通道顺序是RGB,但是opencv存储默认是BGR
                pix = (pix - mean[c]) / std[c];
                input_tensor_values[c * 800 * 800 + i * 800 + size_t(j)] = pix / 255.0;//归一化
                //cout << pix / 255.0 << endl;
            }
        }
    }
    //cv::Mat srcimg;
    //cv::cvtColor(test_image, test_image, cv::COLOR_BGR2RGB);
    //test_image.convertTo(srcimg, CV_32F, 1.0 / 255.0); // 将像素值转换为范围在0到1之间的浮点数
    //Mat blob = dnn::blobFromImage(test_image, 1.0 / 255, Size(800,800), Scalar(), true);//true代表进行翻转
    //输入张量的几个维度
    std::vector<int64_t> input_node_dims = { 1, 3, 800, 800 };

    auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    //auto memory_info = Ort::MemoryInfo::CreateGpu(OrtDeviceAllocator, OrtMemTypeDefault);
    Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), input_node_dims.size());

    std::vector<Ort::Value> ort_inputs;
    ort_inputs.push_back(std::move(input_tensor));//右值引用,避免不必要的拷贝和内存分配操作

    std::vector<Ort::Value> output_tensors;

    output_tensors = session.Run(Ort::RunOptions{ nullptr }, input_node_names.data(), ort_inputs.data(), input_node_names.size(), output_node_names.data(), output_node_names.size());

    // Get pointer to output tensor float values
    const float* rawOutput = output_tensors[0].GetTensorData<float>();
    //generate proposals
    std::vector<int64_t> outputShape = output_tensors[0].GetTensorTypeAndShapeInfo().GetShape();

    //通过输出数据构造张量
    torch::Tensor tensor = torch::from_blob((void*)rawOutput, { 1, 4, 800, 800 });
    int dim = 1;
    torch::Tensor output2 = torch::softmax(tensor, dim);
    //已经正确
    //torch::print(output2);
    torch::Tensor _, output_max;
    std::tie(_, output_max) = torch::max(output2, dim);
    //torch::print(output_max);
    std::vector<int64_t> shape1 = output_max.sizes().vec();
    std::cout << shape1[0] << " " << shape1[1] << " " << shape1[2];
    //torch::Tensor output_visual_unique = output_max.unique();
    torch::Tensor out1, out2,out3;
    
    //判断各类0 2 3 的数目经比对几乎无差别
    std::tie(out1, out2, out3) = torch::_unique2(output_max,true,false, true);
    //torch::print(out1);
    //torch::print(out2);
    torch::print(out3);

    torch::Tensor output_visual1 = torch::zeros({ 1, 800, 800 }, torch::kCPU);
    output_visual1.masked_fill_(output_max == torch::Scalar(3), torch::Scalar(255.0));

    torch::Tensor output_visual2 = torch::zeros({ 1, 800, 800 }, torch::kCPU);
    output_visual2.masked_fill_(output_max == torch::Scalar(2), torch::Scalar(255.0));

    torch::Tensor output_visual3 = torch::zeros({ 1, 800, 800 }, torch::kCPU);
    output_visual3.masked_fill_(output_max == torch::Scalar(1), torch::Scalar(255.0));

    std::vector<torch::Tensor> output_visual_tensors = { output_visual1, output_visual2, output_visual3 };
    torch::Tensor output_visual = torch::cat(output_visual_tensors, 0);

    //进行转置
    torch::Tensor output_visual_transposed = output_visual.permute({ 1, 2, 0 });
    //变成连续存储
    torch::Tensor contiguous_tensor = output_visual_transposed.contiguous();
    // 获取张量的数据指针和形状信息
    // 注意Mat和tensor在内存中不一定是连续存储的,Mat一般是行连续存储
    float* data_ptr = contiguous_tensor.data_ptr<float>();
    std::vector<int64_t> shape = contiguous_tensor.sizes().vec();
    //Mat dstimg1(800, 800, CV_8UC3);
    //int num = 0;
    //for (int64_t i = 0; i < shape[0]; ++i) {
    //    for (int64_t j = 0; j < shape[1]; ++j) {
    //        for (int64_t k = 0; k < shape[2]; ++k) {
    //            // 使用索引访问元素
    //            float value = data_ptr[i * shape[1]*shape[2] + j * shape[2] + k];
    //            if (value == 255)
    //            {
    //                dstimg1.at<cv::Vec3b>(i, j)[k] = 255;
    //            }
    //        }
    //    }
    //}
    //cout << num << endl;
    //cv::imwrite("dst.jpg", dstimg1);
    //数据维度是对的800 * 800 * 3
    std::cout << static_cast<int>(shape[0]) << "    " << static_cast<int>(shape[1])<< "  " << static_cast<int>(shape[2]) << std::endl;
    // 创建 OpenCV Mat 对象,并从数据指针恢复图像
    cv::Mat image(static_cast<int>(shape[0]), static_cast<int>(shape[1]), CV_32FC3, data_ptr);
    // 将图像转换为 8 位无符号整数类型
    cv::Mat image_uint8;
    image.convertTo(image_uint8, CV_8UC3);
    cv::imwrite("resnew.png", image_uint8);
    system("pause");
    return 0;
}

void test() {
    torch::Tensor input = torch::rand({ 2, 3 });
    int dim = 1;
    torch::Tensor output = torch::softmax(input, dim);
}

这里边主要踩坑点,第一个是Mat和libtorch中的Tensor并非连续存储,进行数据转换的时候一定要注意,第二个是转换RRRGGGBBB的时候进行了正则化,对每个通道进行单独计算,可以采用opencv的库函数进行,libtorch的部署参考网上教程,注意添加环境变量后记得重启生效,不重启一般报错缺少dll

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值