Pytorch C++接口调用方法
文章目录
0 引言:
Python具有丰富的人工智能开发资源库,且基本上都是开源易获得的,极大的方便了人工智能开发者进行 研究工作。例如实现一整套深度学习算法,用C++编写需要写上千行,在python中只需要几十行。且在数据清洗、特征选取、模型优化和超参数设置等工作进行时,如果使用C++来实现会浪费大量的时间在编写和调试代码上。而Python作为一门解释型语言,编程时更加灵活,结合可视化工具可以方便人工智能开发者在开发的任何阶段灵活调整参数并实时运行,使开发者能专注于自身领域的内容。
但也是由于Python作为一门解释型语言,在运行时往往会有较慢的运行速度,如果要将Python语言编写的程序封装成电脑可以直接运行的可执行文件要将Python解释器和庞大的库一起打包,往往会具有较大的体积,这在实际的工程运用上是难以容忍的。因此,要让人工智能项目落地,将其转换成编译型语言代码,是十分有必要的。以下为Pytorch的C++工程化方法。**
1 Python部分
在工程化工作开始前,要准备好自己的训练网络和已经训练好的模型。训练时通常将模型保存为.pth格式,我们需要将其转换成C++可以加载的.pt格式。
1.1 训练网络加载
加载.pth模型训练时使用的网络,这里使用了git-hub上高星的segmentation_models_pytorch库。可以按照自己的训练网络进行更改。
import torch
import segmentation_models_pytorch as smp
model = smp.Unet(
decoder_channels=(512, 256, 128, 64, 32),
in_channels=3,
activation='sigmoid',
)
1.2 模型加载
将训练好的模型加载进网络,模型的模式设置为eval模式。有cuda的设备可以使用cudu读取模型。
checkpoint = torch.load(r"D:/data/unet/pytorch/beta1/best_model_1.pth",map_location=torch.device('cpu'))
#checkpoint = torch.load(r"D:/data/unet/pytorch/beta1/best_model_1.pth",map_location=torch.device('cuda'))
model.load_state_dict(checkpoint.state_dict())
model.eval()
Notes:模型有两种模式,model.train()和model.eval(),前者用于模型训练会启用Batch Normalization和Dropout,后者用于预测即用于验证、测试和预测,会关闭Batch Normalization和Dropout,以保证预测结果的一致性。
1.3 模型追踪
使用**torch.jit.trace()**追踪模型对张量的操作。需要给定一个与输入网络形状一样的示例张量,随机生成一个就行。
example = torch.rand(1,3,512,512)
traced_script_module = torch.jit.trace(model,example)
Notes: 个人理解。张量在模型里的变化是很复杂的,很难用函数和逻辑关系表达和转换模型,但是追踪张量的变化是比较简单的。网络就犹如火车轨道网络,经过训练得到模型,可以指导火车在轨道网络里具体的哪条轨道里的运行。如果轨道网络比较简单,取个极限就只有一条火车道,那么火车只需要沿着这一条轨道就能到达目的地,相当于张量只需要经过一个函数关系运算就能得到结果。但现在的神经网络内部结构十分复杂,这样这个火车轨道就被铺的丛横交错,同样要达到目的地,要经过成百上千次变轨,如果还用指定线路的方式去引导火车运行,就要指定若干复杂的路线。所以这时候指定火车运行路线的方式就不如直接追踪火车是怎么运行的,可以让火车以模型的方式在火车轨道网络里运行,追踪火车的运行状态,可以记录的指标有很多,速度、加速度、什么时候换轨、什么时候并轨等等,并将火车运行的变化状态记录下来,就可以指导其他火车在轨道网络里运行。
1.4 模型保存
将转换的script_module保存成.pt格式
traced_script_module.save("torch_script_eval.pt")
2 C++部分
2.0 必要库准备
这里使用了opencv4.3和LibTorch 1.81 CPU debug版本opencv需要有vc14运行实况。
2.1 VS工程建立
2.1.1 建立一个空项目
configuration 设置为debug x64模式
2.1.2 包含目录以及库配置
包含目录配置
添加libtorch和opencv对应的include目录
库目录配置
添加libtorch和opencv对应的lib目录
附加依赖项配置
连接所需的lib目录
可直接复制粘贴(cuda版本需自己另外配置)
opencv_world430d.lib
c10d.lib
c10.lib
torch.lib
torch_cpu.lib
fbgemm.lib
caffe2_detectron_ops.lib
caffe2_module_test_dynamic.lib
cpuinfo.lib
添加所需dll
(1)opencv类
./opencv/build/x64/vc14/bin 目录下的 opencv_world430d.dll
(2)libtorch类
./libtorch/lib目录下的 c10.dll torch_cpu.dll libiomp5md.dll fbgemm.dll asmjit.dll
将上述dll复制到工程对应的configuration目录
2.1.3 添加头文件
DemoPytorch.h代码
#pragma once
#include <iostream>
2.2 简单实现代码
2.2.1 引用的头文件
#include "DemoPytorch.h"
#include <iostream>
#include <memory>
#include <algorithm>
#include <stdio.h>
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <torch/torch.h>
#include <torch/script.h>
2.2.2 模型加载
按照pytorch官方提供的模型加载方法
需要用CUDA版本修改注释部分
torch::jit::script::Module module;
try {
module = torch::jit::load("./torch_script_eval.pt");
module.to(torch::kCPU); // set model to cpu mode
/*module.to(torch::kCUDA);*/ // set model to cuda mode
module.eval();
std::cout << "MODEL LOADED";
}
catch (const c10::Error& e) {
std::cerr << "error loading the model\n";
}
2.2.3 读取图片并进行格式转换
如果需要展示原图需要拷贝一份图片,因为进行数据操作的时候是直接在内存地址上进行的会更改原有的内存数据。
**Notes:**libtorch库提供了和pytorch使用方式基本一致的张量运算,适用广播机制,可以用于预加载训练数据。
// load img
cv::Mat img_original = cv::imread("./00011584_002.png",0);
cv::Mat img = cv::Mat(img_original);
// normalize
cv::resize(img, img, cv::Size(512, 512));
img.convertTo(img, CV_32FC1);
// img to tensor
torch::Tensor mean = torch::tensor({ 0.485,0.456,0.406 });
torch::Tensor std = torch::tensor({ 0.229, 0.224, 0.225 });
auto input_tensor = torch::from_blob(img.data, { 512,512,1 });
input_tensor = input_tensor / 255.0f;
input_tensor = input_tensor - mean;
input_tensor = input_tensor / std;
input_tensor = input_tensor.permute({ 2,0,1 });
input_tensor = input_tensor.to(torch::kCPU);
/*input_tensor = input_tensor.to(torch::kCUDA);*/
input_tensor = input_tensor.unsqueeze(0);
std::vector<torch::jit::IValue> input;
input.push_back(input_tensor);
2.2.4 进行模型预测并显示结果
调用模型的forward方法进行预测,并用detach方法将结果取出,如果张量在CUDA上计算需要进行to(torch::kCPU)将结果转换回CPU类型。
// pred begin
auto pred = module.forward(input).toTensor();
// pred tensor to mat
pred = pred.squeeze().detach();
pred = pred * 255;
pred = pred.to(torch::kU8);
pred = pred.to(torch::kCPU);
cv::Mat output_mat(cv::Size{ 512,512 }, CV_8UC1, pred.data_ptr());
// show result
cv::imshow("original img", img_original);
cv::imshow("mask", output_mat);
cv::waitKey(0);
cv::destroyWindow("original img");
cv::destroyWindow("mask");
结果展示:
2.2.5 整体代码
DemoPytorch.cpp代码
#include "DemoPytorch.h"
#include <iostream>
#include <memory>
#include <algorithm>
#include <stdio.h>
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <torch/torch.h>
#include <torch/script.h>
int main() {
// load model
torch::jit::script::Module module;
try {
module = torch::jit::load("./torch_script_eval.pt");
module.to(torch::kCPU); // set model to cpu mode
/*module.to(torch::kCUDA);*/ // set model to cuda mode
module.eval();
std::cout << "MODEL LOADED";
}
catch (const c10::Error& e) {
std::cerr << "error loading the model\n";
}
// load img
cv::Mat img_original = cv::imread("./00011584_002.png",0);
cv::Mat img = cv::Mat(img_original);
// normalize
cv::resize(img, img, cv::Size(512, 512));
img.convertTo(img, CV_32FC1);
// img to tensor
torch::Tensor mean = torch::tensor({ 0.485,0.456,0.406 });
torch::Tensor std = torch::tensor({ 0.229, 0.224, 0.225 });
auto input_tensor = torch::from_blob(img.data, { 512,512,1 });
input_tensor = input_tensor / 255.0f;
input_tensor = input_tensor - mean;
input_tensor = input_tensor / std;
input_tensor = input_tensor.permute({ 2,0,1 });
input_tensor = input_tensor.to(torch::kCPU);
/*input_tensor = input_tensor.to(torch::kCUDA);*/
input_tensor = input_tensor.unsqueeze(0);
std::vector<torch::jit::IValue> input;
input.push_back(input_tensor);
// pred begin
auto pred = module.forward(input).toTensor();
// pred tensor to mat
pred = pred.squeeze().detach();
pred = pred * 255;
pred = pred.to(torch::kU8);
pred = pred.to(torch::kCPU);
cv::Mat output_mat(cv::Size{ 512,512 }, CV_8UC1, pred.data_ptr());
// show result
cv::imshow("original img", img_original);
cv::imshow("mask", output_mat);
cv::waitKey(0);
cv::destroyWindow("original img");
cv::destroyWindow("mask");
return 0;
}