【深度学习】【OpenVINO】【C++】模型转化、环境搭建以及模型部署的详细教程
提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论
文章目录
前言
Intel OpenVINO(Open Visual Inference and Neural Network Optimization)是由英特尔推出的一款开源深度学习推理优化库,专门针对英特尔的CPU、GPU、FPGA、ASIC等硬件进行优化。它能够将深度学习模型转换为优化的推理引擎,从而在保持精度的同时提高推理速度和效率。OpenVINO专注于优化和加速机器学习模型的推理阶段,特别是对于大规模部署和实时应用场合。OpenVINO的设计目的是为了提供一个高度优化的执行环境,利用英特尔硬件的特性来实现极致的性能优化。
OpenVINO是对 Intel 硬件最原生的支持。
模型转换--pytorch转onnx
Pytorch模型转ENGINE并推理的步骤如下:
- 将PyTorch预训练模型文件( .pth 或 .pt 格式)转换成ONNX格式的文件(.onnx格式),这一转换过程在PyTorch环境中进行。
- 将转换得到的 .onnx 文件再次转换成ENGINE格式的文件(.engine格式),这一转换过程在安装的TensorRT版本的bin目录下通过trtexec.exe转化而成。
- 将转换得到的 .engine文件随后作为输入,调用TensorRT的C++ API来执行模型的推理。
博主使用AlexNet图像分类(五种花分类)进行演示,需要安装pytorch环境,对于该算法的基础知识,可以参考博主【AlexNet模型算法Pytorch版本详解】博文
bash
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转换代码如下:
bash
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平台搭建依赖环境
安装OpenVINO
官网下载安装文件地址,根据自己的情况选择合适的版本。
推荐优先选择的2024.4版本,和博主一致。
选择w_openvino_toolkit_windows_2024.4.0.16579.c3152d32c9c_x86_64下载,双击运行解压后即可:
在C++工程的目录下新建一个OpenVINO2024.4.0(推荐根据当前版本命名) ,将runtime文件夹中 include、lib 、bin和version.txt 复制到OpenVINO2024.4.0。
打开VS 2019:新建新项目---->空项目---->配置项目---->项目路径以及勾选"将解决方案和项目放在同一目录中---->点击创建。
在解决方案-->源文件-->右键添加新建项。这里暂时可以默认空着不做处理。
配置openvino:项目---->属性。假设没有新建cpp文件,空项目的属性页就不会存在C/C++这一项目。
添加附加包含目录:Release | x64---->C/C+±--->常规---->附加包含目录。
xml
D:\C++_demo_openvino\OpenVINO2024.4.0\include
链接器:Release | x64---->链接器---->常规---->附加库目录。
xml
D:\C++_demo_openvino\OpenVINO2024.4.0\lib\intel64\Release
链接器:Release | x64---->链接器---->输入---->附加依赖项。
在D:\C++_demo_openvino\OpenVINO2024.4.0\lib\intel64\Release下找到附加依赖项的文件(.lib文件),这里根据需求添加。
xml
openvino.lib
openvino_c.lib
openvino_onnx_frontend.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++--->常规---->附加包含目录。
xml
D:\C++_demo_openvino\opencv4.8.0\build\include
链接器:Release | x64---->链接器---->常规---->附加库目录。
xml
D:\C++_demo_openvino\opencv4.8.0\build\x64\vc16\lib
链接器:Release | x64---->链接器---->输入---->附加依赖项。
xml
opencv_world480.lib
简化部署
在Release x64模式下测试时,需要将TensorRT所需的.dll文件,以及OpenCV的.dll文件复制到自己项目的Release下。
xml
D:\C++_demo_openvino\OpenVINO2024.4.0\bin\intel64\Release
D:\C++_demo_openvino\opencv4.8.0\build\x64\vc16\bin
===>
D:\C++_demo_openvino\tensorrt\x64\Release
没有Release目录时,需要在Release | x64模式下运行一遍代码,代码部分在下面提供,读者可以先行新建文件复制代码。
将所有的.dll文件和.exe文件放在同一个目录下可以简化应用程序的部署过程。用户无需手动配置环境变量或安装额外的组件即可运行程序。
OpenVINO调用onnx模型
OpenVINO推理核心流程
初始化运行时核心
负责模型的初始化以及设备的管理。
cpp
ov::Core core;
编译模型
在指定的设备(GPU/CPU)上准备一个模型以供执行。
cpp
ov::CompiledModel model = core.compile_model(modelPath, "CPU", config);
ov::CompiledModel model = core.compile_model(modelPath, "GPU", config);
ov::CompiledModel model = core.compile_model(modelPath, "AUTO", config);
compile_model参数 | model | device_name | properties |
---|---|---|---|
内容 | 指定需要编译的模型 | 指定用于执行推理的设备类型 | 进一步配置模型的执行环境包括各种配置选项,如多线程数、日志级别等 |
创建推理请求
封装了模型的输入和输出,以及执行推理所需的所有资源。
cpp
ov::InferRequest request = model.create_infer_request();
获取模型输入输出信息
从ov::InferRequest对象中获取模型输入和输出的详细信息,包括数量、名称、类型和形状。
cpp
const std::vector<ov::Output<const ov::Node>> inputs = model.inputs();
const std::vector<ov::Output<const ov::Node>> outputs = model.outputs();
auto input_name = inputs[i].get_any_name();
auto output_name = outputs[i].get_any_name();
ov::Tensor input_tensor = request.get_input_tensor(i);
ov::Tensor output_tensor = request.get_output_tensor(i);
ov::Shape input_shape = input_tensor.get_shape();
ov::Shape output_shape = output_tensor.get_shape();
预处理输入数据
对输入数据进行颜色空间转换,尺寸缩放、标准化以及形状维度扩展操作。
cpp
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 tensor = cv::dnn::blobFromImage(blob);
这部分不是OpenVINO核心部分,根据任务需求不同,代码略微不同。
设置输入
将数据写入张量中,具体的操作取决于张量的数据类型和形状。
cpp
int image_size = input_w * input_h;
float* data = input_tensor.data<float>();
for (size_t row = 0; row < input_h; row++) {
for (size_t col = 0; col < input_w; col++) {
for (size_t c = 0; c < ch; c++) {
data[image_size * c + row * input_w + col] = blob.at<Vec3f>(row, col)[c];
}
}
}
执行推理
执行推理,前向传播过程,将输入数据传递给模型并生成输出结果。
cpp
request.infer();
后处理推理结果
推理完成后,从输出张量中获取结果数据,根据需要对结果进行后处理,以获得最终的预测结果。
cpp
cv::Mat prob(num, cnum, CV_32F, (float*)output_tensor.data());
cv::minMaxLoc(probmat, &minv, &maxv, &minL, &maxL);
这部分不是OpenVINO核心部分,根据任务需求不同,代码基本不同。
OpenVINO推理代码
需要配置flower_classes.txt文件存储五种花的分类标签,并将其放置到工程目录下(推荐)。
xml
daisy
dandelion
roses
sunflowers
tulips
这里需要将AlexNet.onnx放置到工程目录下(推荐),并且将以下推理代码拷贝到新建的cpp文件中,并执行查看结果。
cpp
#include <openvino/openvino.hpp>
#include <opencv2/opencv.hpp>
#include <fstream>
using namespace cv;
using namespace std;
// 加载标签文件获得分类标签
std::string labels_txt_file = "D:/C++_demo_openvino/openvino/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_openvino/openvino/sunflowers.jpg");
cv::imshow("输入图", image);
// 初始化运行时核心
ov::Core core;
// 查询支持硬件设备
vector<string> availableDevices = core.get_available_devices();
for (int i = 0; i < availableDevices.size(); i++) {
printf("supported device name : %s \n", availableDevices[i].c_str());
std::cout << availableDevices[i] << std::endl;
}
// onnx训练模型文件
std::string modelPath = "D:/C++_demo_openvino/openvino/AlexNet.onnx";
// 编译模型,不做额外配置
ov::CompiledModel model = core.compile_model(modelPath, "AUTO");
// 获取实际执行的设备列表
std::string execution_devices = model.get_property(ov::device::priorities);
std::cout << "Execution devices: " << execution_devices << std::endl;
// 创建推理请求
ov::InferRequest request = model.create_infer_request();
// 获取模型输入信息
size_t input_h = 0;
size_t input_w = 0;
size_t ch = 0;
ov::Tensor input_tensor;
const std::vector<ov::Output<const ov::Node>> inputs = model.inputs();
for (size_t i = 0; i < inputs.size(); ++i) {
auto input_name = inputs[i].get_any_name();
input_tensor = request.get_input_tensor(i);
ov::Shape input_shape = input_tensor.get_shape();
input_h = input_shape[2];
input_w = input_shape[3];
ch = input_shape[1];
std::cout << "NCHW:" << input_shape[0] << "x" << input_shape[1] << "x" << input_h << "x" << input_w << std::endl;
}
// 获取模型输出信息
size_t num = 0;
size_t cnum = 0;
ov::Tensor output_tensor;
const std::vector<ov::Output<const ov::Node>> outputs = model.outputs();
for (size_t i = 0; i < outputs.size(); ++i) {
auto output_name = outputs[i].get_any_name();
output_tensor = request.get_output_tensor(i);
ov::Shape output_shape = output_tensor.get_shape();
num = output_shape[0];
cnum = output_shape[1];
std::cout << "NC:" << output_shape[0] << "x" << output_shape[1]<< 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); //除以方差
// 设置输入:转化成tensor
int image_size = input_w * input_h;
float* data = input_tensor.data<float>();
// HWC => NCHW
for (size_t row = 0; row < input_h; row++) {
for (size_t col = 0; col < input_w; col++) {
for (size_t c = 0; c < ch; c++) {
data[image_size * c + row * input_w + col] = blob.at<Vec3f>(row, col)[c];
}
}
}
// 模型推理
request.infer();
// 1x5 获取输出数据并包装成一个cv::Mat对象,为了方便后处理
cv::Mat prob(num, cnum, CV_32F, (float*)output_tensor.data());
// 后处理推理结果
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);
return 0;
}
图片正确预测为向日葵:
总结
尽可能简单、详细的介绍了pytorch模型到onnx模型的转化,C++下OpenVINO环境的搭建以及onnx模型的OpenVINO部署。