【深度学习】【OnnxRuntime】【C++】模型转化、环境搭建以及模型部署的详细教程

CSDN 2024-10-18 16:35:06 阅读 55

【深度学习】【OnnxRuntime】【C++】模型转化、环境搭建以及模型部署的详细教程

提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论

文章目录

【深度学习】【OnnxRuntime】【C++】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转onnxWindows平台搭建依赖环境安装onnxruntime安装OpenCV简化部署

ONNXRuntime调用onnx模型ONNXRuntime推理核心流程ONNXRuntime推理代码

总结


前言

ONNXRuntime是微软推出的一款高性能的机器学习推理引擎框架,用户可以非常便利的用其运行一个onnx模型,专注于加速机器学习模型的预测阶段。ONNXRuntime设计目的是为了提供一个高效的执行环境,使机器学习模型能够在各种硬件上快速执行,支持多种运行后端包括CPU,GPU,TensorRT,DML等,使得开发者可以灵活选择最适合其应用场景的硬件平台。

ONNXRuntime是对ONNX模型最原生的支持。

读者可以通过学习【OnnxRuntime部署】系列学习文章目录的C++篇* 的内容,系统的学习OnnxRuntime部署不同任务的onnx模型。


模型转换–pytorch转onnx

Pytorch模型转ONNX并推理的步骤如下:

将PyTorch预训练模型文件( .pth 或 .pt 格式)转换成ONNX格式的文件(.onnx格式),这一转换过程在PyTorch环境中进行。将转换得到的 .onnx 文件随后作为输入,调用ONNXRuntime的C++ API来执行模型的推理。

博主使用AlexNet图像分类(五种花分类)进行演示,需要安装pytorch环境,对于该算法的基础知识,可以参考博主【AlexNet模型算法Pytorch版本详解】博文

<code>conda create --name AlexNet python==3.10

conda activate AlexNet

# 根据自己主机配置环境

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 假设模型转化出错则降级为指定1.16.1版本

pip install onnx==1.16.1

然后把训练模型好的AlexNet.pth模型转成AlexNet.onnx模型,pyorch2onnx.py转换代码如下:

import torch

from model import AlexNet

model = AlexNet(num_classes=5)

weights_path = "./AlexNet.pth"

# 加载模型权重

model.load_state_dict(torch.load(weights_path))

# 模型推理模式

model.eval()

model.cpu()

# 虚拟输入数据

dummy_input1 = torch.randn(1, 3, 224, 224)

# 模型转化函数

torch.onnx.export(model, (dummy_input1), "AlexNet.onnx", verbose=True, opset_version=11)

【AlexNet.pth百度云链接,提取码:ktq5 】直接下载使用即可。


Windows平台搭建依赖环境

安装onnxruntime

官网下载安装文件地址,根据自己的情况选择合适的版本【官方说明】。

博主的cuda版本是11.8,cudnn版本是8.9.7,因此选择OnnxRuntime v1.15.0版本。

cmd命令框下输入nvcc -V 查询cuda版本;

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\vxx.x\include\cudnn_version.h 查看cuda版本.

在assert下选择onnxruntime-win-x64-gpu-1.15.0.zip下载

双击运行解压后即可:

打开VS 2019:新建新项目---->空项目---->配置项目---->项目路径以及勾选“将解决方案和项目放在同一目录中---->点击创建。

在解决方案–>源文件–>右键添加新建项。这里暂时可以默认空着不做处理。

配置onnxruntime:项目---->属性。假设没有新建cpp文件,空项目的属性页就不会存在C/C++这一项目。

添加附加包含目录:Release | x64---->C/C+±—>常规---->附加包含目录。

<code>D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\include

链接器:Release | x64---->链接器---->常规---->附加库目录。

<code>D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\lib

链接器:Release | x64---->链接器---->输入---->附加依赖项。

在D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\lib下找到附加依赖项的文件(.lib文件)。

<code>onnxruntime.lib

onnxruntime_providers_cuda.lib

onnxruntime_providers_shared.lib

安装OpenCV

官网下载安装文件地址,博主使用opencv-4.8.0-windows.exe版本

双击运行解压后即可,博主重命名为opencv4.8.0:

直接下载的opencv-4.8.0-windows.exe部分功能不完整,如读取视频这类功能,假如需要完整的功能则需要自己编译,参考windows10下opencv4.8.0-cpu C++版本源码编译教程,这里博主也提供了编译好的opencv4.8.0-cpu.rar【百度网盘,提取码:22r3】。目录格式仿造着opencv-4.8.0-windows.exe的目录格式。

添加附加包含目录:Release | x64---->C/C++—>常规---->附加包含目录。

<code>D:\C++_demo\opencv4.8.0\build\include

链接器:Release | x64---->链接器---->常规---->附加库目录。

<code>D:\C++_demo\opencv4.8.0\build\x64\vc16\lib

链接器:Release | x64---->链接器---->输入---->附加依赖项。

<code>opencv_world480.lib

简化部署

在Release x64模式下测试时,需要将OnnxRuntime所需的.dll文件,以及OpenCV的.dll文件复制到自己项目的Release下。

D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\lib

D:\C++_demo\opencv4.8.0\build\x64\vc16\bin

===>

D:\C++_demo\onnxruntime_onnx\x64\Release

没有Release目录时,需要在Release | x64模式下运行一遍代码,代码部分在下面提供,读者可以先行新建文件复制代码。

将所有的.dll文件和.exe文件放在同一个目录下可以简化应用程序的部署过程。用户无需手动配置环境变量或安装额外的组件即可运行程序。


ONNXRuntime调用onnx模型

ONNXRuntime推理核心流程

初始化ONNXRuntime环境

通常涉及到创建一个 Ort::Env 对象,它包含了线程池和其他运行时设置。

<code>Ort::Env env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "AlexNet-onnx");

Ort::Env参数 日志严重性级别 (logging severity level) 环境名称 (environment name)
作用 决定了哪些级别的日志信息将被记录下来,运行时提供了几个预定义的宏来表示不同的日志级别。 主要用于标识特定的环境实例,尤其是在多线程或多进程环境中可以帮助追踪日志信息来源。
内容 ORT_LOGGING_LEVEL_FATAL:仅记录致命错误;ORT_LOGGING_LEVEL_ERROR:记录错误信息;ORT_LOGGING_LEVEL_WARNING:记录警告信息;ORT_LOGGING_LEVEL_INFO:记录信息性消息;ORT_LOGGING_LEVEL_VERBOSE:记录详细的信息,包括调试信息。 字符串

设置会话选项

通常包括配置优化器级别、线程数和设备(GPU/CPU)使用等。

Ort::SessionOptions session_options;

session_options.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);

session_options.SetIntraOpNumThreads(4);

OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);

OrtSessionOptionsAppendExecutionProvider_CPU(session_options, 1);

会换选项 优化器级别 线程数 设备使用
函数 SetGraphOptimizationLevel SetIntraOpNumThreads SetExecutionMode
作用 在模型加载到ONNXRuntime之前对其进行图优化的过程,提高执行效率 设置每个运算符内部执行时的最大线程数 CUDA/CPU设备选择,CUDA优先级设为0,CPU优先级设为1,优先尝试使用CUDA执行。
参数 ORT_ENABLE_BASIC:基本的图优化; ORT_DISABLE_ALL:禁用所有优化;ORT_ENABLE_EXTENDED:启用扩展优化;ORT_ENABLE_ALL:启用所有优化。 整型 session_options:用于配置会话选项; 整型:优先级值,数值越低优先级越高。

加载模型并创建会话

加载预训练的ONNX模型文件,使用运行时环境、会话选项和模型创建一个Ort::Session对象。

Ort::Session session_(env, modelPath.c_str(), session_options);

Ort::Session参数 Ort::Env model_path session_options
内容 ONNX 运行时环境对象 模型的位置或者模型的二进制数据 会话选项

获取模型输入输出信息

从Ort::Session对象中获取模型输入和输出的详细信息,包括数量、名称、类型和形状。

Ort::AllocatorWithDefaultOptions allocator;

int input_nodes_num = session_.GetInputCount();

int output_nodes_num = session_.GetOutputCount();

auto input_name = session_.GetInputNameAllocated(i, allocator);

auto output_name = session_.GetOutputNameAllocated(i, allocator);

session_.GetInputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();

session_.GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();

预处理输入数据

对输入数据进行颜色空间转换,尺寸缩放、标准化以及形状维度扩展操作。

cv::cvtColor(image, rgb, cv::COLOR_BGR2RGB);

cv::resize(rgb, blob, cv::Size(input_w, input_h));

blob.convertTo(blob, CV_32F);

blob = blob / 255.0;

cv::subtract(blob, cv::Scalar(0.485, 0.456, 0.406), blob);

cv::divide(blob, cv::Scalar(0.229, 0.224, 0.225), blob);

cv::Mat timg = cv::dnn::blobFromImage(blob);

这部分不是OnnxRuntime核心部分,根据任务需求不同,代码略微不同。

推理准备

创建输入和输出张量,这些张量是用于存储推理数据的内存块,分配内存给这些张量,以准备数据输入。

std::array<int64_t, 4> input_shape_info{ -- --> 1, 3, input_h, input_w };

auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);

Ort::Value input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, timg.ptr<float>(), tpixels, input_shape_info.data(), input_shape_info.size());

函数 Ort::MemoryInfo::CreateCpu Ort::Value::CreateTensor
作用 ONNX Runtime运行一个模型时,用于描述内存分配的信息,包括内存的位置(CPU 或 GPU)以及内存的具体类型(固定内存或常规内存) 封装了张量数据,可以作为模型推理的输入或输出。
参数1 OrtDeviceAllocator:默认的分配器类型,它用于分配设备(CPU或GPU)上的内存;OrtArenaAllocator:使用内存池来分配内存,可以减少内存分配和释放的开销,提高内存操作的效率;OrtCustomAllocator:自定义内存分配器。 用于分配和管理张量数据的内存。
参数2 OrtMemTypeCPUInput:表示任何非CPU使用的CPU内存,用于模型的输入,数据将由非CPU执行器(GPU)使用;OrtMemTypeCPUOutput: 由非CPU输出的、CPU可访问的内存,用于模型的输出,确保数据在CPU上可用;OrtMemTypeCPU:通常与 OrtMemTypeCPUOutput 相同,指CPU可访问的内存;OrtMemTypeDefault:表示执行器的默认内存分配器,用于分配内存时没有特别指定其他类型时使用。 张量的形状
参数3 ----- 张量的大小
参数4 ----- 张量的指针
参数6 ----- 张量的维度数
参数3 ----- 张量的数据类型

执行推理

调用Ort::Session::Run方法,传入输入张量、输出张量和其他必要的参数,执行推理。

ort_outputs = session_.Run(Ort::RunOptions{ nullptr }, inputNames.data(), &input_tensor_, 1, outNames.data(), outNames.size());

session_.Run参数 run_options input_names input_values input_count output_names output_count
含义 是否进行性能分析、是否仅执行到达特定输出的最小子图等,通常是默配置。 输入节点名称数组 用于存储模型的输入数据Ort::Value 输入数量 输出节点名称数组 输出数量

后处理推理结果

推理完成后,从输出张量中获取结果数据,根据需要对结果进行后处理,以获得最终的预测结果。

const float* pdata = ort_outputs[0].GetTensorMutableData<float>();

cv::Mat prob(num, nc, CV_32F, (float*)pdata);

cv::minMaxLoc(prob, &minv, &maxv, &minL, &maxL);

这部分不是OnnxRuntime核心部分,根据任务需求不同,代码基本不同。


ONNXRuntime推理代码

需要配置flower_classes.txt文件存储五种花的分类标签,并将其放置到工程目录下(推荐)。

daisy

dandelion

roses

sunflowers

tulips

这里需要将AlexNet.onnx放置到工程目录下(推荐),并且将以下推理代码拷贝到新建的cpp文件中,并执行查看结果。

#include "onnxruntime_cxx_api.h"

#include "cpu_provider_factory.h"

#include <opencv2/opencv.hpp>

#include <fstream>

// 加载标签文件获得分类标签

std::string labels_txt_file = "D:/C++_demo/onnxruntime_onnx/flower_classes.txt";

std::vector<std::string> readClassNames();

std::vector<std::string> readClassNames()

{

std::vector<std::string> classNames;

std::ifstream fp(labels_txt_file);

if (!fp.is_open())

{

printf("could not open file...\n");

exit(-1);

}

std::string name;

while (!fp.eof())

{

std::getline(fp, name);

if (name.length())

classNames.push_back(name);

}

fp.close();

return classNames;

}

int main(int argc, char** argv) {

// 预测的目标标签数

std::vector<std::string> labels = readClassNames();

// 测试图片

cv::Mat image = cv::imread("D:/C++_demo/onnxruntime_onnx/sunflowers.jpg");

cv::imshow("输入图", image);

// 初始化ONNXRuntime环境

Ort::Env env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "AlexNet-onnx");

// 设置会话选项

Ort::SessionOptions session_options;

// 优化器级别:基本的图优化级别

session_options.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);

// 线程数:4

session_options.SetIntraOpNumThreads(4);

// 设备使用优先使用GPU而是才是CPU

std::cout << "onnxruntime inference try to use GPU Device" << std::endl;

OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);

OrtSessionOptionsAppendExecutionProvider_CPU(session_options, 1);

// onnx训练模型文件

std::string onnxpath = "D:/C++_demo/onnxruntime_onnx/AlexNet.onnx";

std::wstring modelPath = std::wstring(onnxpath.begin(), onnxpath.end());

// 加载模型并创建会话

Ort::Session session_(env, modelPath.c_str(), session_options);

// 获取模型输入输出信息

int input_nodes_num = session_.GetInputCount();// 输入节点输

int output_nodes_num = session_.GetOutputCount();// 输出节点数

std::vector<std::string> input_node_names;// 输入节点名称

std::vector<std::string> output_node_names;// 输出节点名称

Ort::AllocatorWithDefaultOptions allocator;

// 输入图像尺寸

int input_h = 0;

int input_w = 0;

// 获取模型输入信息

for (int i = 0; i < input_nodes_num; i++) {

// 获得输入节点的名称并存储

auto input_name = session_.GetInputNameAllocated(i, allocator);

input_node_names.push_back(input_name.get());

// 显示输入图像的形状

auto inputShapeInfo = session_.GetInputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();

int ch = inputShapeInfo[1];

input_h = inputShapeInfo[2];

input_w = inputShapeInfo[3];

std::cout << "input format: " << ch << "x" << input_h << "x" << input_w << std::endl;

}

// 获取模型输出信息

int num = 0;

int nc = 0;

for (int i = 0; i < output_nodes_num; i++) {

// 获得输出节点的名称并存储

auto output_name = session_.GetOutputNameAllocated(i, allocator);

output_node_names.push_back(output_name.get());

// 显示输出结果的形状

auto outShapeInfo = session_.GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();

num = outShapeInfo[0];

nc = outShapeInfo[1];

std::cout << "output format: " << num << "x" << nc << std::endl;

}

// 预处理输入数据

cv::Mat rgb, blob;

// 默认是BGR需要转化成RGB

cv::cvtColor(image, rgb, cv::COLOR_BGR2RGB);

// 对图像尺寸进行缩放

cv::resize(rgb, blob, cv::Size(input_w, input_h));

blob.convertTo(blob, CV_32F);

// 对图像进行标准化处理

blob = blob / 255.0;// 归一化

cv::subtract(blob, cv::Scalar(0.485, 0.456, 0.406), blob);// 减去均值

cv::divide(blob, cv::Scalar(0.229, 0.224, 0.225), blob);//除以方差

// CHW-->NCHW 维度扩展

cv::Mat timg = cv::dnn::blobFromImage(blob);

std::cout << timg.size[0] << "x" << timg.size[1] << "x" << timg.size[2] << "x" << timg.size[3] << std::endl;

// 占用内存大小,后续计算是总像素*数据类型大小

size_t tpixels = input_h * input_w * 3;

std::array<int64_t, 4> input_shape_info{ 1, 3, input_h, input_w };

// 准备数据输入

auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);

Ort::Value input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, timg.ptr<float>(), tpixels, input_shape_info.data(), input_shape_info.size());

// 模型输入输出所需数据(名称及其数量),模型只认这种类型的数组

const std::array<const char*, 1> inputNames = { input_node_names[0].c_str() };

const std::array<const char*, 1> outNames = { output_node_names[0].c_str() };

// 模型推理

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

try {

ort_outputs = session_.Run(Ort::RunOptions{ nullptr }, inputNames.data(), &input_tensor_, 1, outNames.data(), outNames.size());

}

catch (std::exception e) {

std::cout << e.what() << std::endl;

}

// 1x5 获取输出数据并包装成一个cv::Mat对象,为了方便后处理

const float* pdata = ort_outputs[0].GetTensorMutableData<float>();

cv::Mat prob(num, nc, CV_32F, (float*)pdata);

// 后处理推理结果

cv::Point maxL, minL;// 用于存储图像分类中的得分最小值索引和最大值索引(坐标)

double maxv, minv;// 用于存储图像分类中的得分最小值和最大值

cv::minMaxLoc(prob, &minv, &maxv, &minL, &maxL);

int max_index = maxL.x;// 获得最大值的索引,只有一行所以列坐标既为索引

std::cout << "label id: " << max_index << std::endl;

// 在测试图像上加上预测的分类标签

cv::putText(image, labels[max_index], cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 255), 2, 8);

cv::imshow("输入图像", image);

cv::waitKey(0);

// 释放资源

session_options.release();

session_.release();

return 0;

}

图片正确预测为向日葵:


总结

尽可能简单、详细的介绍了pytorch模型到onnx模型的转化,C++下OnnxRuntime环境的搭建以及onnx模型的OnnxRuntime部署。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。