【深度学习】【onnxruntime】C++调用onnx
【深度学习】【onnxruntime】C++环境搭建
提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论
文章目录
- 【深度学习】【onnxruntime】C++环境搭建
- 前言
- Windows平台安装onnxruntime
- Windows平台安装OpenCV
- onnxruntime调用onnx模型
- 简单验证
- 调用onnx模型
- 总结
前言
OpenCV是一个基于BSD许可发行的跨平台计算机视觉和机器学习软件库(开源),可以运行在Linux、Windows、Android和Mac OS操作系统上。可以将pytorch中训练好的模型使用ONNX导出,再使用onnxruntime直接进行加载使用。
本章将讲述如何使用搭建c++下onnxruntime的运行环境,不再依赖再使用opencv中的dnn模块直接进行加载使用,因为opencv的dnn模块在执行速度上受限。
Windows平台安装onnxruntime
官网下载安装文件地址,根据自己的情况选择合适的版本,博主使用ONNX Runtime v1.15.0版本。
在assert下选择onnxruntime-win-x64-gpu-1.15.0.zip下载
双击运行解压后即可:
打开VS 2019:新建新项目---->空项目---->配置项目---->项目路径以及勾选“将解决方案和项目放在同一目录中---->点击创建。
在解决方案–>源文件–>右键添加新建项。这里暂时可以默认空着不做处理。
设置onnxruntime路径:项目---->属性。假设没有新建cpp文件,空项目的属性页就不会存在C/C++这一项目。
添加附加包含目录:Release | x64---->C/C+±—>常规---->附加包含目录。
D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\include
链接器:Release | x64---->链接器---->常规---->附加库目录。
D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\lib
链接器:Release | x64---->链接器---->输入---->附加依赖项。
在D:\C++_demo\onnxruntime-win-x64-gpu-1.15.0\lib下找到附加依赖项的文件。
onnxruntime.lib
onnxruntime_providers_cuda.lib
onnxruntime_providers_shared.lib
Windows平台安装OpenCV
官网下载安装文件地址,博主使用opencv-4.8.0-windows.exe版本
双击运行解压后即可,博主重命名为opencv4.8.0:
添加附加包含目录:Release | x64---->C/C+±—>常规---->附加包含目录。
D:\C++_demo\opencv4.8.0\build\include
链接器:Release | x64---->链接器---->常规---->附加库目录。
D:\C++_demo\opencv4.8.0\build\x64\vc16\lib
链接器:Release | x64---->链接器---->输入---->附加依赖项。
opencv_world480.lib
onnxruntime调用onnx模型
在Release x64模式下测试,将onnxruntime的onnxruntime.dll、onnxruntime_providers_cuda.dll和onnxruntime_providers_shared.dll,以及opencv的opencv_world480.dll文件复制到自己项目的Release下。
没有Release目录时,需要在Release | x64模式下运行一遍代码,代码部分在下面提供,读者可以先行新建文件复制代码。
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
简单验证
这里简单验证一下onnxruntime和opencv是否安装成功,适用于包括博主在内的许多对c++不熟悉的人来说,代码完成了简单的图像的读取与显示。
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src = imread("./animal-1.jpg");
//没有图像输入
if (src.empty()) {
printf("....\n");
return -1;
}
//namedWindow("输入窗口", WINDOW_FREERATIO);
imshow("输入窗口", src);
waitKey(0);
destroyAllWindows();
return 0;
}
调用onnx模型
将PFNet.onnx拷贝到项目路径下。
将python版本的onnxruntime转化成对应的c++版本的,发现输出的效果完全一致,onnx模型可以作为c++的接口来供其他应用调用。
#include "onnxruntime_cxx_api.h"
#include <opencv2/opencv.hpp>
#include <fstream>
cv::Mat transformation(const cv::Mat& image, const cv::Size& targetSize, const cv::Scalar& mean, const cv::Scalar& std) {
cv::Mat resizedImage;
//图片尺寸缩放
cv::resize(image, resizedImage, targetSize, 0, 0, cv::INTER_AREA);
cv::Mat normalized;
resizedImage.convertTo(normalized, CV_32F);
cv::subtract(normalized / 255.0, mean, normalized);
cv::divide(normalized, std, normalized);
return normalized;
}
int main(int argc, char** argv) {
cv::Scalar mean(0.485, 0.456, 0.406); // 均值
cv::Scalar std(0.229, 0.224, 0.225); // 标准差
cv::Mat frame = cv::imread("D:/C++_demo/onnxruntime_onnx/animal-1.jpg");
std::string onnxpath = "D:/C++_demo/onnxruntime_onnx/PFNet.onnx";
std::wstring modelPath = std::wstring(onnxpath.begin(), onnxpath.end());
Ort::SessionOptions session_options;
Ort::Env env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "PFNet.onnx");
// 设定单个操作(op)内部并行执行的最大线程数,可以提升速度
session_options.SetIntraOpNumThreads(20);
session_options.SetGraphOptimizationLevel(ORT_ENABLE_EXTENDED);
std::cout << "onnxruntime inference try to use GPU Device" << std::endl;
// 是否使用GPU
OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0);
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;
}
// 获得输出信息 多输出
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();
int ch = outShapeInfo[1];
int output_h = outShapeInfo[2];
int output_w = outShapeInfo[3];
std::cout << "output format: " << ch << "x" << output_h << "x" << output_w << std::endl;
}
// 图象预处理 - 格式化操作
int64 start = cv::getTickCount();
cv::Mat rgbImage;
cv::cvtColor(frame, rgbImage, cv::COLOR_BGR2RGB);
cv::Size targetSize(input_w, input_h);
// 对原始图像resize和归一化
cv::Mat normalized = transformation(rgbImage, targetSize, mean, std);
cv::Mat blob = cv::dnn::blobFromImage(normalized);
size_t tpixels = input_w * input_h * 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, blob.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*, 3> outNames = { output_node_names[0].c_str(),output_node_names[1].c_str(),output_node_names[2].c_str() };
std::vector<Ort::Value> ort_outputs;
try {
ort_outputs = session_.Run(Ort::RunOptions{ nullptr }, inputNames.data(), &input_tensor_, inputNames.size(), outNames.data(), outNames.size());
}
catch (std::exception e) {
std::cout << e.what() << std::endl;
}
// 选择最后一个输出作为最终的mask
const float* mask_data = ort_outputs[2].GetTensorMutableData<float>();
auto outShape = ort_outputs[2].GetTensorTypeAndShapeInfo().GetShape();
int num_cn = outShape[1];
int out_h = outShape[2];
int out_w = outShape[3];
int step = out_h * out_w;
// 逐像素判断是背景还是前景
cv::Mat result = cv::Mat::zeros(cv::Size(out_w, out_h), CV_8UC1);
for (int row = 0; row < out_h; row++) {
for (int col = 0; col < out_w; col++) {
float c1 = mask_data[row * out_w + col];
if (c1 > 0.5) {
result.at<uchar>(row, col) = 255;
}
else {
result.at<uchar>(row, col) = 0;
}
}
}
cv::Mat mask, binary;
cv::resize(result, mask, cv::Size(frame.cols, frame.rows));
cv::threshold(mask, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
float t = (cv::getTickCount() - start) / static_cast<float>(cv::getTickFrequency());
std::cout << "Total Testing Time : " << t << std::endl;
cv::imshow("mask", binary);
cv::waitKey(0);
// 释放资源
session_options.release();
session_.release();
return 0;
}
这里只推理一张图片,因此加载GPU比较费时。
session_options.SetIntraOpNumThreads可以有效提升网络的推理速度,这是opencv调用onnx模型进行推理所不具备的优势
总结
尽可能简单、详细的介绍C++下onnxruntime调用ONNX模型的流程。