Répertoire des articles de la série
Chapitre 1 : Création de la DLL de la bibliothèque de liens dynamiques de Visual Studio 2019
Chapitre deux : Débogage des DLL de la bibliothèque de liens dynamiques VS
Chapitre 3 : Configuration de l'environnement OpenCV VS2019
Chapitre 4 : Modèle pytorch de déploiement C++ Libtorch
Chapitre 5 : Modèle pytorch de déploiement C++ onnxruntime
Table des matières
Répertoire des articles de la série
1. Comment déployer pytorch en C++ ?
Deux, configuration onnxruntime
2. Configuration VS2019 sur nxruntime
2.1 Configurer le répertoire VC++
2.2 Configurer l'éditeur de liens
2.3 Configuration des variables d'environnement onnxruntime
3. Convertir le modèle pytorch en onnx
Quatrièmement, l'utilisation de onnxruntime en C++
Cinq, l'utilisation de onnxruntime en python
avant-propos
Environnement : studio visuel 2019 ; OpenCV4.5.5 ; pytorch1.8 ; onnxruntime1.8.1 ;
1. Comment déployer pytorch en C++ ?
Le modèle pytorch est déployé en C++.Le chapitre précédent utilise la version correspondante de Libtorch de pytorch pour le déploiement. En fait, il peut être plus pratique de passer au déploiement onnx. La grande différence dans la précision de la segmentation sémantique avant est due au problème de prétraitement des données. Généralement, les images doivent être standardisées et converties en RVB avant d'être entrées dans le réseau. L'avantage du déploiement onnx est que le format intermédiaire Microsoft est compatible avec diverses plates-formes et est plus pratique. Si la plate-forme de déploiement dispose d'un GPU, il peut être préférable d'utiliser tensorRT.
Deux, configuration onnxruntime
Remarque : Faites attention à la version lorsque le modèle pt est transféré vers onnx, et la version onnxruntime correspond
1. Téléchargez onnxruntime
Site officiel : Galerie NuGet | Accueil
Le lien onnxruntime-win-x64-1.8.1 est le suivant :
2. Configuration VS2019 sur nxruntime
2.1 Configurer le répertoire VC++
Configurez d'abord le répertoire d'inclusion et le répertoire de la bibliothèque, correspondant à la même méthode que opencv.
2.2 Configurer l'éditeur de liens
Ajoutez toutes les bibliothèques en tant que dépendances, entrez le répertoire lib dans cmd, utilisez la commande dir /b *.lib>1.txt pour générer un répertoire et copiez-le pour l'utiliser.
2.3 Configuration des variables d'environnement onnxruntime
Copiez toutes les DLL dans le répertoire Release ou Debug.
3. Convertir le modèle pytorch en onnx
Remarque : modèle d'entrée BCHW, mode d'inférence model.eval(), version de sortie opset_version=11
import torch
x = torch.randn(1, 3, 512, 512, device="cpu")
model = torch.load('best_model.pth', map_location=torch.device('cpu'))
model.eval()
input_names = ["input"]
output_names = ["output"]
torch.onnx.export(model, x, "GlandUnet.onnx", verbose=True, input_names=input_names, output_names=output_names, opset_version=11)
Quatrièmement, l'utilisation de onnxruntime en C++
Remarque : 1. Assurez-vous de faire attention au prétraitement avant que l'image ne soit entrée dans le modèle, qu'elle soit standardisée, qu'elle soit convertie en RVB
2. La sortie d'inférence de onnxruntime en C++ est la première adresse du tableau, qui peut être extraite par un pointeur pour générer Mat
/****************************************
@brief : 分割onnxruntime
@input : 图像
@output : 掩膜
*****************************************/
void SegmentAIONNX(Mat& imgSrc, int width, int height)
{
模型信息/
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "OnnxModel");
Ort::SessionOptions session_options;
session_options.SetIntraOpNumThreads(1);
#ifdef _WIN32
const wchar_t* model_path = L"GlandUnet.onnx";
#else
const char* model_path = "RedUnet.onnx";
#endif
Ort::Session session(env, model_path, session_options);
Ort::AllocatorWithDefaultOptions allocator;
size_t num_input_nodes = session.GetInputCount(); //batchsize
size_t num_output_nodes = session.GetOutputCount();
const char* input_name = session.GetInputName(0, allocator);
const char* output_name = session.GetOutputName(0, allocator);
auto input_dims = session.GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape(); //输入输出维度
auto output_dims = session.GetOutputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
std::vector<const char*> input_names{ input_name };
std::vector<const char*> output_names = { output_name };
输入处理//
Mat imgBGR = imgSrc; //输入图片预处理
Mat imgBGRresize;
resize(imgBGR, imgBGRresize, Size(input_dims[3], input_dims[2]), InterpolationFlags::INTER_CUBIC);
Mat imgRGBresize = imgBGRresize;
//cvtColor(imgBGRresize, imgRGBresize, COLOR_BGR2RGB); //smp未转RGB
Mat resize_img;
imgRGBresize.convertTo(resize_img, CV_32F, 1.0 / 255); //divided by 255转float
cv::Mat channels[3]; //分离通道进行HWC->CHW
cv::split(resize_img, channels);
std::vector<float> inputTensorValues;
float mean[] = { 0.485f, 0.456f, 0.406f }; //
float std_val[] = { 0.229f, 0.224f, 0.225f };
for (int i = 0; i < resize_img.channels(); i++) //标准化ImageNet
{
channels[i] -= mean[i]; // mean均值
channels[i] /= std_val[i]; // std方差
}
for (int i = 0; i < resize_img.channels(); i++) //HWC->CHW
{
std::vector<float> data = std::vector<float>(channels[i].reshape(1, resize_img.cols * resize_img.rows));
inputTensorValues.insert(inputTensorValues.end(), data.begin(), data.end());
}
Ort::MemoryInfo memoryInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);
vector<Ort::Value> inputTensors;
inputTensors.push_back(Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(), inputTensorValues.size(), input_dims.data(), input_dims.size()));
//clock_t startTime, endTime; //计算推理时间
//startTime = clock();
auto outputTensor = session.Run(Ort::RunOptions{ nullptr }, input_names.data(), inputTensors.data(), 1, output_names.data(), 1); // 开始推理
//endTime = clock();
打印模型信息/
//printf("Using Onnxruntime C++ API\n");
//printf("Number of inputs = %zu\n", num_input_nodes);
//printf("Number of output = %zu\n", num_output_nodes);
//std::cout << "input_name:" << input_name << std::endl;
//std::cout << "output_name: " << output_name << std::endl;
//std::cout << "input_dims:" << input_dims[0] << input_dims[1] << input_dims[2] << input_dims[3] << std::endl;
//std::cout << "output_dims:" << output_dims[0] << output_dims[1] << output_dims[2] << output_dims[3] << std::endl;
//std::cout << "The run time is:" << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << std::endl;
//输出处理//
float* mask_ptr = outputTensor[0].GetTensorMutableData<float>(); //outtensor首地址
vector< unsigned char >results(512 * 512);
for (int i = 0; i < 512 * 512; i++)
{
if (mask_ptr[i] >= 0.5)
{
results[i] = 0;
}
else
{
results[i] = 255;
}
}
unsigned char* ptr = &results[0];
Mat mask = Mat(output_dims[2], output_dims[3], CV_8U, ptr);
resize(mask, imgSrc, Size(imgBGR.cols, imgBGR.rows));
//原图展示分割结果//
//cvtColor(imgSrc, imgSrc, COLOR_GRAY2BGR);
//Mat imgAdd;
//addWeighted(imgBGR, 1, imgSrc, 0.3, 0, imgAdd);
}
Cinq, l'utilisation de onnxruntime en python
# -*- coding:utf-8 -*-
import cv2
import numpy as np
import onnxruntime as ort
import imgviz
import time
class_names = ['_background_', 'conjunctiva_area']
### 定义一些数据前后处理的工具
def preprocess(input_data):
# convert the input data into the float32 input
img_data = input_data.astype('float32')
# normalize
mean_vec = np.array([0.485, 0.456, 0.406])
stddev_vec = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(img_data.shape).astype('float32')
for i in range(img_data.shape[0]):
norm_img_data[i, :, :] = (img_data[i, :, :] / 255 - mean_vec[i]) / stddev_vec[i]
# add batch channel
norm_img_data = norm_img_data.reshape(1, 3, 512, 512).astype('float32')
return norm_img_data
def softmax(x):
x = x.reshape(-1)
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum(axis=0)
def postprocess(result):
return softmax(np.array(result)).tolist()
session = ort.InferenceSession('GlandUnet.onnx')
img0 = cv2.imread('test.bmp')
h0, w0 = img0.shape[0:2]
img = cv2.cvtColor(img0, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, [512, 512])
image_data = np.array(img).transpose(2, 0, 1) # HWC->CHW
input_data = preprocess(image_data)
time_start = time.time() # 记录开始时间
raw_result = session.run([], {'input': input_data})
time_end = time.time() # 记录结束时间
time_sum = time_end - time_start # 计算的时间差为程序的执行时间,单位为秒/s
print(time_sum)
# label_result = np.argmax(raw_result, dim=1) # 缺argmax
# out = np.squeeze(raw_result)
# result_img = np.array(out, dtype=np.uint8)
# result_img = cv2.resize(result_img, (w0, h0))
out1 = raw_result[0][0]
cv2.imshow('2', out1[1])
cv2.waitKey(0)
les références
https://onnxruntime.ai/
https://github.com/leimao/ONNX-Runtime-Inference/blob/main/src/inference.cpp
Image opencv format HWC au format CHW - Programmeur recherché