环境配置的过程包括:
配置pytorch1.4以及pytorch1.0虚拟环境
源码编译llvm
源码编译tvm
源码编译libtorch
具体内容:
配置pytorch1.4虚拟环境:
由于Tvm目前仅支持pytorch1.4的环境,因此模型的转换需要在1.4的环境下进行。
安装pytorch1.4虚拟环境,要求已经装过anaconda,anaconda安装可以参考:
conda create -n pytorch1.4 python=3.7
source activate pytorch1.4 #进入虚拟环境
conda install pytorch==1.4.0 torchvision cudatoolkit=10.0 -c pytorch #安装pytorch1.4
#然后安装tvm需要的包
pip install decorator
pip install antlr4-python3-runtime
以上是通过在线安装,在线安装可能比较慢,可以通过添加其它的镜像源进行安装,具体操作:
#显示已经存在的镜像源
conda config --show channels
#添加清华镜像源
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
# 【添加完毕一定要设置一下】设置搜索时显示通道地址
conda config --set show_channel_urls yes
#添加中科大镜像源
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/msys2/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/bioconda/
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/menpo/
# 【添加完毕一定要设置一下】设置搜索时显示通道地址
conda config --set show_channel_urls yes
#删除指定镜像源
conda config --remove channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
#还原原始镜像源
conda config --remove-key channels
配置pytorch1.0虚拟环境:
由于使用的libtorch是1.0的版本,因此要找到对应的pytorch1.0版本进行.pth模型转为.pt模型:
conda create -n pytorch1.0 python=3.7
source activate pytorch1.0 #进入虚拟环境
conda install pytorch==1.0.0 torchvision cudatoolkit=10.0 -c pytorch #安装pytorch1.0
源码编译llvm:
编译llvm用于cpu优化,可以选择不安装。
存在两种安装方式:手动安装、自动安装
手动安装:将llvm安装到指定位置
git clone https://gitee.com/mirrors/LLVM.git
cd LLVM && mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ../llvm
make -j4
自动安装:将llvm安装到系统里面(推荐此操作,后面有些过程不需要)
sudo apt search llvm
sudo apt install llvm-6.0
源码编译Tvm:
cd ~/../TVM #进入自己想要安装的文件内
git clone --recursive https://github.com/apache/incubator-tvm tvm
#切换到经验证有效的版本中,重要
git checkout 639358e8da602a6f43c7f08afd951fc4368ab421
cd tvm && mkdir build && cd build
## 从 tvm目录下的cmake 目录下将 config.cmake 拷贝到新建的 build 目录下,
## 然后打开config.cmake文件,进行下面操作
#如果是进行的手动编译llvm的,执行下面操作:
set(USE_CUDA OFF) -> set(USE_CUDA ON)
set(USE_LLVM OFF) -> set(USE_LLVM ~/LLVM/build/bin/llvm-config)
#~/LLVM/build/bin/llvm-config为编译llvm的地方
#如果是自动编译llvm,执行以下操作:
set(USE_CUDA OFF) -> set(USE_CUDA ON)
set(USE_LLVM OFF) ->set(USE_LLVM ON) #系统内自动寻找
set(USE_OPENMP none) ->set(USE_OPENMP gnu)
#然后进行编译
cmake ..
make -j8
之后再修改系统环境配置文件:
sudo gedit ~/.bashrc
export TVM_HOME=~/tvm #~/tvm编译地址
export PYTHONPATH=$TVM_HOME/python:$TVM_HOME/topi/python:${PYTHONPATH}
source ~/.bashrc
源码编译libtorch1.0:
libtorch是为了将python下的torch模型部署到c++中使用,必须要明确的是:源码编译的libtorch版本要和转换torch到libtorch所在的pytorch的版本一致,目前libtorch1.2或者1.4都存在内存泄漏问题,因此建议配置的libtorch和pytorch的环境为1.0
以下是源码编译libtorch1.0的环境
git clone --recursive git://github.com/pytorch/pytorch
cd pytorch
git checkout v1.0.0
git submodule sync
git submodule update --init --recursive
#然后创建一个pytorch1.0环境用于安装libtorch的相关python内容,目的是为了不和其它虚拟环境相冲突
conda create -n libtorch1.0 python=3.6
source activate libtorch1.0
conda install numpy pyyaml mkl==2019.1 mkl-include==2019.1 setuptools cmake cffi typing
conda install -c mingfeima mkldnn
conda install -c pytorch magma-cuda100
#然后在libtorch1.0的虚拟环境中继续编译安装libtorch
python setup.py clean
export CMAKE_PREFIX_PATH=${CONDA_PREFIX:-"$(dirname $(which conda))/../"}
python setup.py install
## 编译好的 libtorch 在 $PYTORCH_HOME/torch/lib/tmp_install 里面 或者 在 $PYTORCH_HOME/torch 里的lib bin include share test 共五个文件作为第三方库
将训练好的torch模型.pth转为libtorch模型,并部署在c++中使用.
torch模型.pth转libtorch模型.pt需要在pytorch1.0虚拟环境下进行,因此要先进入pytorch1.0虚拟环境。
执行以下python代码进行转换:
"""
进行模型转换,torch->onnx->tvm
环境要求:tvm转torch模型必须在pytorch1.4的环境下进行,1.4下可以进行torch->onnx->tvm,
同时也可以进行torch->libtorch转换,但是c++中源码编译的libtorch库要和torch->libtorch转换时使用的pytorch版本一致
由于c++工程中使用的libtorch库是1.0,因此torch->libtorch的过程也要在pytorch1.0环境下进行
"""
import numpy as np
from pynvml import * #pip install nvidia-ml-py3,显存监控
import torch.onnx
import torch.backends.cudnn as cudnn
from models_class.pose_resnet import *
import torchvision.transforms as transforms
from PIL import Image
# 加载torch模型进行测试
def load_torch_model_test(img_path,model_path):
# 初始化模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
cudnn.benchmark = True
model = get_pose_net()
model = model.cuda()
model.to(device)
# 加载模型权重
model.load_state_dict(torch.load(model_path)['state_dict'])
# 进行torch模型测试
test_trans = transforms.Compose([transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
img = Image.open(img_path).convert('RGB')
img = test_trans(img)
# img = Image.fromarray(np.expand_dims(img, 0)) # 扩张0维度
imgblob = img.unsqueeze(0) # tensor禁止进行维度删除
imgblob = torch.autograd.Variable(imgblob).cuda() # 对输入进行数据格式转换,np.array->tensor.cuda.float
torch.no_grad() # 不进行梯度计算
model.eval() # 使用验证模式
heatmap = model(imgblob)
print(heatmap)
return model
# 将torch模型转为libtorch模型
def gen_torch_to_libtorch(model_path, save_path):
# 初始化模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
cudnn.benchmark = True
#加载模型结构
# model = get_pose_net() #加载网络架构,不同的网络此处不同,此处用于关键点网络结构加载。
# 加载的原因是:训练模型的时候使用model.module.state_dict(),仅保存模型参数,因此需要加载模型结构
model = resnet50(class_num=2) #加载网络架构:slowfastnet
model.to(device)
model.cuda()
# # 加载模型权重
#第一种方式:
# model.load_state_dict(torch.load(model_path)['state_dict']) #训练时使用以下形式进行保存模型权重以及相关参数
# 使用原因:
# 保存模型的状态,可以设置一些参数,后续训练可以使用,例如中断后可以继续训练
# state = {'epoch': epoch + 1, # 保存的当前轮数
# 'state_dict': mymodel.state_dict(), # 训练好的参数
# 'optimizer': optimizer.state_dict(), # 优化器参数,为了后续的resume
# 'best_pred': best_pred # 当前最好的精度
# , ...., ...}
# 保存模型到checkpoint.pth.tar
# torch.save(state, ‘checkpoint.pth.tar’)
#加载模型参数
# checkpoint = torch.load('model_best.pth.tar')
# model.load_state_dict(checkpoint['state_dict']) # 模型参数
# optimizer.load_state_dict(checkpoint['optimizer']) # 优化参数
# epoch = checkpoint['epoch'] # epoch,可以用于更新学习率等
#第二种方式:
model.load_state_dict(torch.load(model_path))
#原因是:
#在训练的时候是已下面的形式进行保存了模型参数:
# 保存模型到checkpoint.pth.tar
# torch.save(model.module.state_dict(), ‘checkpoint.pth.tar’)
# 对应的加载模型方法为(这种方法需要先反序列化模型获取参数字典,因此必须先load模型,再load_state_dict):
# mymodel.load_state_dict(torch.load(‘checkpoint.pth.tar’))
# 第三种方式:
#model = torch.load(‘checkpoint.pth.tar’)
#原因是:
#在训练的时候是已下面的形式进行保存,将模型参数以及网络结构保存在一起:
# 保存
#torch.save(model,‘checkpoint.pth.tar’)
# 加载
# model = torch.load(‘checkpoint.pth.tar’)
#在进行torch转libtorch时,此种方式可能再设定GPU模式
#model.to(device)
#model.cuda()
# 将torch转为libtorch并保存
#不同的网络输入数据大小维度不同,要注意
# example = torch.rand(1, 3, 56, 56).cuda() # 任意设定一个输入,输入数据的大小为网络输入的大小[n,c,h,w]
example = torch.rand(1, 3, 1, 112, 112).cuda() # 任意设定一个输入,输入数据的大小为网络输入的大小[b,c,n,h,w]
traced_script_module = torch.jit.trace(model, example).eval()
traced_script_module.save(save_path)
if __name__ == "__main__":
#显存监控
GPU_USE=0
nvmlInit() #初始化
handle = nvmlDeviceGetHandleByIndex(GPU_USE) #获得指定GPU的handle
info_begin = nvmlDeviceGetMemoryInfo(handle) #获得显存信息
#路径设置
img_str ="/data_1/Working/project/torch-onnx-tvm/data/[email protected]"
model_path = "/data_1/Working/project/torch-onnx-tvm/libtorch/model/carkeypoints/carkeypoints.pth"
libtorch_save_path = "/data_1/Working/project/torch-onnx-tvm/libtorch/model/carkeypoints/carkeypoints.pt"
#进行转换
gen_torch_to_libtorch(model_path,libtorch_save_path)
# #输出显存使用mb
info_end = nvmlDeviceGetMemoryInfo(handle)
print("-"*15+"TORCH GPU MEMORY INFO"+"-"*15)
print(" Memory Total: "+str(info_end.total//(1024**2)))
print(" Memory Free: "+str(info_end.free//(1024**2)))
print(" Memory Used: "+str(info_end.used//(1024**2)-info_begin.used//(1024**2)))
print("-" * 40)
在以上转换torch模型.pth到libtorch模型.pt后,就可以在c++工程中使用:
在使用的时候需要将编译的libtorch1.0链接到工程中,即将$PYTORCH_HOME/torch/lib/tmp_install 里面的文件夹"include"、“lib”、“share” 放到工程链接的第三方库3rdparty/libtorch下,然后需要在
CMakeLists.txt文件中进行链接libtorch库:
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(libtorch)
set(CMAKE_CXX_STANDARD 14)
set(TORCH_ROOT ${
CMAKE_SOURCE_DIR}/3rdParty/libtorch) #libtorch所在的路径
set(Torch_DIR ${
TORCH_ROOT}/share/cmake/Torch) #设置libtorch路径
find_package(Torch REQUIRED) #自动寻找torch的包
find_package(OpenCV REQUIRED)
#message(STATUS " torch lib : ${TORCH_LIBRARIES} ")
include_directories(/usr/local/cuda/include)
#include_directories(${
TORCH_ROOT}/include)
link_directories(usr/local/cuda/lib64)
#link_directories(${
TORCH_ROOT}/lib)
add_executable(libtorch main.cpp carkeypoints.cpp)
target_link_libraries(libtorch ${
OpenCV_LIBS} ${
TORCH_LIBRARIES})
以使用车辆关键点检测为例介绍c++中部署libtorch模型的后处理:
common.h
#ifndef LIBTORCH_COMMON_H
#define LIBTORCH_COMMON_H
#include <torch/script.h> // One-stop header.
#include <opencv2/opencv.hpp>
#include "c10/cuda/CUDAException.h"
#include "c10/cuda/CUDAFunctions.h"
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <dirent.h>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sstream>
#include <iomanip>
#include <stdio.h>
using namespace cv;
using namespace torch;
using namespace std;
#include <iterator>
#include <algorithm>
#include <cuda_runtime.h>
#include <chrono>
#endif //LIBTORCH_COMMON_H
carkeypoints.h
#ifndef LIBTORCH_CARKEYPOINTS_H
#define LIBTORCH_CARKEYPOINTS_H
#include "common.h"
#define PROCESS_FAIL -1 // 执行失败
#define PROCESS_SUCCESS 0 // 执行正常
#define IMAGE_ERROR 1 // 图像错误
#define MODEL_ERROR 2 // 模型错误
#define DRAW_PIC_KPT
struct sCarkeypointOutput{
std::vector<std::pair<cv::Point,double>> vAllKeypoints;
};
struct sCarkeypointInput{
cv::Mat srcImage;
cv::Rect obj;
};
class carkeypoints{
public:
carkeypoints();
~carkeypoints();
static carkeypoints& ins();
int init(std::string& model_path,int GPUID);
int process(sCarkeypointInput& input,sCarkeypointOutput& output);
private:
std::shared_ptr<torch::jit::script::Module> kpt_net;
int width_ = 256;
int height_= 256;
cv::Scalar mean=cv::Scalar(0.485, 0.456, 0.406);
const float std[3]={
0.229, 0.224, 0.225};
};
#endif //LIBTORCH_CARKEYPOINTS_H
carkeypoints.cpp
/*torch模型pth 转为 libtorch模型pt 后 在c++中部署前向之 人体姿态估计-汽车关键点*/
///测试结果:显存占用670mb
///耗时:1.05s 效果较差
#include "carkeypoints.h"
//#define USE_TORCH
carkeypoints::carkeypoints() {
}
carkeypoints::~carkeypoints() {
}
carkeypoints& carkeypoints::ins() {
static thread_local carkeypoints obj;
return obj;
}
int carkeypoints::init(std::string &model_path,int GPUID) {
///设置gpu,有两种方式
///第一种:
int ret = setenv("CUDA_VISIBLE_DEVICES", std::to_string(GPUID).c_str(), 1);
if(ret !=0)
{
throw std::runtime_error("set CUDA_VISIBLE_DEVICES failed");
}
c10::cuda::set_device(GPUID);
// ///第二种:
// torch::DeviceType device_type; //设置Device类型
// device_type = torch::kCUDA; //torch::kCUDA and torch::kCPU
// torch::Device device(device_type, GPUID);
///加载模型
const std::string model = model_path+"/"+"carkeypoints.pt";
if(access(model.c_str(),F_OK)==-1)
{
return MODEL_ERROR;
}
kpt_net = torch::jit::load(model,torch::kCUDA); ///可以选择在cpu中加载模型也可以选择在GPU中加载模型,但是最后都要将加载的模型再放到GPU中
///或者
// kpt_net = torch::jit::load(model,torch::kCPU) ///使用CPU加载
///如果选择第一种设置gpu,则选择下面的方式进行将model导入GPU
kpt_net->to(at::kCUDA);
///如果选择第二种设置gpu,则选择下面的方式进行将model导入GPU
// kpt_net->to(device);
}
int carkeypoints::process(sCarkeypointInput &input, sCarkeypointOutput &output) {
cv::Mat car=input.srcImage(input.obj).clone();
// std::cout<<"size 0:"<<std::to_string(car.channels())<<std::endl;
cv::resize(car,car,cv::Size(width_,height_));
// cv::cvtColor(car,car,cv::COLOR_BGR2RGB); ///是否需要进行图片格式转换,视训练的时候而定,如果训练的时候进行了转换,那么在c++中也需要转换。无论在python还是c++中,cv读入的格式为BGR
car.convertTo(car,CV_32FC3,1.0/255); ///进行归一化
car=car-cv::Scalar(0.485, 0.456, 0.406); ///进行去均值
std::vector<cv::Mat> split_mat;
cv::split(car,split_mat);
split_mat[0]/=std[0];
split_mat[1]/=std[1];
split_mat[2]/=std[2];
cv::merge(split_mat,car);
// std::cout<<"size 1:"<<std::to_string(car.dims)<<std::endl;
///转为cpu内的tensor
auto car_tensor_cpu = torch::CPU(torch::kFloat32).tensorFromBlob(car.data,{
1,height_,width_,3});///图片的维度为:(n,h,w,c)
// std::cout<<"car tensor 0:"<<std::to_string(car_tensor_cpu.size(0))<<std::endl;
// std::cout<<"car tensor 1:"<<std::to_string(car_tensor_cpu.size(1))<<std::endl;
// std::cout<<"car tensor 2:"<<std::to_string(car_tensor_cpu.size(2))<<std::endl;
// std::cout<<"car tensor 3:"<<std::to_string(car_tensor_cpu.size(3))<<std::endl;
// std::cout<<"*******************************************"<<std::endl;
// auto car_tensor_cpu = torch::from_blob(car.data,{1,height_,width_,3},at::kFloat);///图片的维度为:(n,h,w,c)
///进行维度调整以适应网络输入
car_tensor_cpu=car_tensor_cpu.permute({
0,3,1,2}); ///此操作用于调整tensor的维度和torch保持一致,和keras中的transpose保持一致
// std::cout<<"car tensor 0:"<<std::to_string(car_tensor_cpu.size(0))<<std::endl;
// std::cout<<"car tensor 1:"<<std::to_string(car_tensor_cpu.size(1))<<std::endl;
// std::cout<<"car tensor 2:"<<std::to_string(car_tensor_cpu.size(2))<<std::endl;
// std::cout<<"car tensor 3:"<<std::to_string(car_tensor_cpu.size(3))<<std::endl;
///将tensor送到gpu内运算,和net在同一个设备内
auto car_tensor_gpu = torch::autograd::make_variable(car_tensor_cpu, false).to(at::kCUDA);
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
auto heat_map_tensor = kpt_net->forward({
car_tensor_gpu}).toTensor(); ///一个输出可以直接转为tensor ,获得的是在GPU上
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
std::chrono::duration<double > time_used = std::chrono::duration_cast<std::chrono::duration<double >>(t1-t0);
std::cout<<"forward use time: "<<std::to_string(time_used.count())<<" seconds"<<std::endl;
#ifdef USE_TORCH
///对输出的tensor进行处理使用两种方法
///第一种先获得热点坐标tensor然后在转为vector
auto heat_map_tensor_view = heat_map_tensor.contiguous().view({
heat_map_tensor.size(0),heat_map_tensor.size(1),heat_map_tensor.size(2)*heat_map_tensor.size(3)}).toBackend(at::Backend::CPU);
///.contiguous()是使得tensor的内存连续,然后才能view改变其形状,判断是否内存连续可以使用heat_map_tensor.is_contiguous()
/// 一般进行了维度改变的tensor.permute(),都需要进行contiguous
// auto heat_map_tensor_view = heat_map_tensor.contiguous().view({heat_map_tensor.size(0),heat_map_tensor.size(1),heat_map_tensor.size(2)*heat_map_tensor.size(3)});
// std::cout<<"heat_map_tensor_view:"<<std::to_string(heat_map_tensor_view.size(0))<<std::endl;
// std::cout<<"heat_map_tensor_view:"<<std::to_string(heat_map_tensor_view.size(1))<<std::endl;
// std::cout<<"heat_map_tensor_view:"<<std::to_string(heat_map_tensor_view.size(2))<<std::endl;
// auto max_pt = torch::max(heat_map_tensor_view[0],1); ///输出为元祖两维{1*c,1*c}={}
auto max_pt = torch::max(heat_map_tensor_view,2); ///输出为元祖两维{1*c,1*c}={}
auto score_tensor = std::get<0>(max_pt).view(-1); ///获得每个点的置信度,tensor type:float 需要从(1,8)->(8)
// auto score_tensor = std::get<0>(max_pt); ///获得每个点的置信度,需要从(1,8)->(8)
// std::cout<<score_tensor<<std::endl;
auto index_tensor = std::get<1>(max_pt).view(-1).to(kFloat);///获得每个最大点的位置index,tensor type:long 需要从(1,8)->(8),并且也要将type:kLong->kFloat
///如若使用python torch中的API:方法- torch::apiname
// for(size_t j=0;j<index_tensor.size(0);j++)
// {
// std::cout<<"index_tensor:"<<std::to_string(index_tensor[j].item().toInt())<<std::endl; ///输出显示每一个元素
// }
// std::cout<<index_tensor<<std::endl;
// auto y_float = index_tensor/heat_map_tensor.size(3); ///由于index_tensor type:kLong 因此返回的tensor类型为kLong,需要将其转换kFloat.
auto y_float = torch::div(index_tensor,heat_map_tensor.size(3));
/// 在c++中torch中的数据类型long.int.float.double变为kLong.kInt.kFloat.kDouble等,转换方式仅有tensor.to(kLong)没有 tensor.kLong()这种
// std::cout<<y_float<<std::endl;
auto index_max_y = torch::floor(y_float).to(kInt); ///获得每一个点的坐标值y
// std::cout<<index_max_y<<std::endl;
// auto index_max_y = (std::get<1>(max_pt)/heat_map_tensor.size(3)); ///获得每一个点的坐标值y
auto index_max_x = std::get<1>(max_pt).view(-1).to(kInt)-(index_max_y*heat_map_tensor.size(3)+1);///获得每个点的坐标值x
// std::cout<<index_max_x<<std::endl;
///将tensor转为vector,使用指针进行
std::vector<float> res_score(score_tensor.data<float>(),score_tensor.data<float>()+score_tensor.numel());
std::vector<int> res_index_x(index_max_x.data<int>(),index_max_x.data<int>()+index_max_x.numel());///index_max_x.numel()是指tensor所有元素的个数
std::vector<int> res_index_y(index_max_y.data<int>(),index_max_y.data<int>()+index_max_y.numel());
///计算关键点的位置
for(size_t i=0;i<res_score.size();i++)
{
output.vAllKeypoints.push_back({
cv::Point((int)(res_index_x[i]*4*input.obj.width/width_+0.5)+input.obj.x,(int)(res_index_y[i]*4*input.obj.height/height_+0.5)+input.obj.y),res_score[i]});
}
#else
///第二种先直接将tensor->mat,然后再对MAT直行操作
///以下这一步必须进行,将tensor数据从gpu转到cpu中
heat_map_tensor = heat_map_tensor.contiguous().toBackend(at::Backend::CPU);///tensor地址可能不连续需要将数据地址放在一个连续块,然后再转到cpu中
std::vector<cv::Mat> kpt_mat;
for(int i=0;i<heat_map_tensor.size(1);i++)
{
kpt_mat.push_back(cv::Mat(heat_map_tensor.size(2),heat_map_tensor.size(3),CV_32FC1,heat_map_tensor.data<float>()+i*heat_map_tensor.size(2)*heat_map_tensor.size(3))); ///heat_map_tensor.data<float>()是一个tensor的float指针
}
double minVal,maxVal;
cv::Point minLoc,maxLoc;
for(int i=0;i<kpt_mat.size();i++)
{
cv::minMaxLoc(kpt_mat[i],&minVal,&maxVal,&minLoc,&maxLoc);
maxLoc.x = (int)(maxLoc.x*4*input.obj.width/width_+0.5)+input.obj.x;
maxLoc.y = (int)(maxLoc.y*4*input.obj.height/height_+0.5)+input.obj.y;
output.vAllKeypoints.push_back({
maxLoc,maxVal});
}
#endif
#ifdef DRAW_PIC_KPT
for(auto& kp:output.vAllKeypoints)
{
cv::circle(input.srcImage,kp.first,3,cv::Scalar(0,255,255),-1);
}
#endif
}
main.cpp
int main(int argc, const char* argv[]){
int gpuid=0;
std::string model_path = "/data_1/Working/project/torch-onnx-tvm/libtorch/model/carkeypoints";
std::string data_path = "/data_1/Working/project/torch-onnx-tvm/libtorch/data/[email protected]";
carkeypoints::ins().init(model_path,gpuid);
sCarkeypointInput input;
sCarkeypointOutput output;
cv::Mat src = cv::imread(data_path);
input.srcImage = src;
input.obj = cv::Rect(0,0,src.cols,src.rows);
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
carkeypoints::ins().process(input,output);
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
std::chrono::duration<double > time_used = std::chrono::duration_cast<std::chrono::duration<double >>(t1-t0);
std::cout<<"use time: "<<std::to_string(time_used.count())<<" seconds"<<std::endl;
// cv::imshow("src",input.srcImage);
// cv::waitKey();
}
以使用slowFast视频分类为例介绍c++中部署libtorch模型的后处理:
lightVideoClassify.h
enum eLightStatusLabel {
eLightConstant = 0, //车灯状态恒定
eLightTwinkle = 1, //车灯状态闪烁
eOtherLightStatus = 99 //其他
};
typedef struct Turnning_Light_Output
{
eLightStatusLabel lightStatus = eOtherLightStatus; // 0: light off , 1: light on
float confidence = 0.0;
}Turnning_Light_Output;
class VIRlightVideoClassify {
public:
VIRlightVideoClassify(){
};
~VIRlightVideoClassify(){
};
static VIRlightVideoClassify &ins();
int init(const std::string& modelPath, const int& gpuID);
int process(std::vector<cv::Mat>& inputMats, Turnning_Light_Output& output);
private:
const std::string m_modelFile = "/lightVideoClassify/lightVideoClassify.pt";
int newHeight_ = 56;
int newWidth_ = 56;
int mGPUID;
std::shared_ptr<torch::jit::script::Module> module;
at::Tensor m_result_tensor;
};
lightVideoClassify.cpp
//
// Created by em on 20-7-28.
//
#include "lightVideoClassify.h"
VIRlightVideoClassify& VIRlightVideoClassify::ins() {
static thread_local VIRlightVideoClassify obj;
return obj;
}
int VIRlightVideoClassify::init(const std::string &modelPath, const int &gpuID) {
const std::string netFilePath = modelPath + m_modelFile;
if (access(netFilePath.c_str(), F_OK) == -1) // file or dir is not exists
{
return MODEL_ERROR;
}
int ret = setenv("CUDA_VISIBLE_DEVICES", std::to_string(gpuID).c_str(), 1);
if(ret !=0)
throw std::runtime_error("set CUDA_VISIBLE_DEVICES failed");
c10::cuda::set_device(gpuID);
module = torch::jit::load(netFilePath, torch::kCPU);
module->to(at::kCUDA); // put model to gpu
return PROCESS_SUCCESS;
}
int VIRlightVideoClassify::process(std::vector<cv::Mat> &inputMats, Turnning_Light_Output &output) {
std::vector<cv::Mat> imgs;
for(int i = 0; i < inputMats.size(); i++){
cv::Mat tmp;
cv::resize(inputMats[i],tmp,cv::Size(newWidth_,newHeight_));
cv::cvtColor(tmp, tmp, cv::COLOR_BGR2RGB);
cv::Mat tmp1;
tmp.convertTo(tmp1, CV_32F);
cv::Scalar mean(128.0, 128.0, 128.0);
tmp1 -= mean;
vector<cv::Mat> images;
cv::split(tmp1, images);
images[0] /= 128.0;
images[1] /= 128.0;
images[2] /= 128.0;
cv::merge(images, tmp1);
imgs.push_back(tmp1);
}
auto img_tensor_1 = torch::CPU(torch::kFloat32).tensorFromBlob(imgs[0].data, {
1, newHeight_, newWidth_, 3 });
for(int j = 1; j < imgs.size(); j++){
auto img_tensor_2 = torch::CPU(torch::kFloat32).tensorFromBlob(imgs[j].data, {
1, newHeight_, newWidth_, 3 });
img_tensor_1 = torch::cat({
img_tensor_1,img_tensor_2},0);
}
img_tensor_1 = img_tensor_1.permute({
3,0,1,2 });
img_tensor_1 = img_tensor_1.unsqueeze(0);
auto img_var_1 = torch::autograd::make_variable(img_tensor_1, false).to(at::kCUDA);
at::Tensor output_image = module->forward({
img_var_1}).toTensor();
output_image = output_image.contiguous().toBackend(at::Backend::CPU);///tensor.size()=(1,2)
///输出查看tensor内容:
// std::cout<<"output_image numel:"<<std::to_string(output_image.numel())<<std::endl;
// std::cout<<"output_image dim :"<<std::to_string(output_image.dim())<<std::endl;
// std::cout<<"output_image size 0:"<<std::to_string(output_image.size(0))<<std::endl;
// std::cout<<"output_image size 1:"<<std::to_string(output_image.size(1))<<std::endl;
// std::cout<<"output_image.sizes():"<<output_image.sizes()<<std::endl;
// std::cout<<"output_image:"<<output_image<<std::endl;
///以下是模型输出后处理
///第一种方式:先将输出tensor转为vector,然后再使用c++内的API对vector寻找最大值
std::vector<float > out_res(output_image.data<float>(),output_image.data<float>()+output_image.numel());
// std::cout<<"out_res:"<<out_res<<std::endl;
int max_index = std::max_element(out_res.begin(),out_res.end())-out_res.begin();
output.lightStatus = (eLightStatusLabel)max_index;
output.confidence = out_res[max_index];
///第二种方式:直接使用tensor的API计算tensor的最大值
// auto max_res = torch::max(output_image,1);
// auto max_index = std::get<1>(max_res).item<float>();
// auto max_val = std::get<0>(max_res).item<float>(); ///tensor.item<type>()是将单个tensor值转为类型为type的c++格式值
// output.lightStatus = (eLightStatusLabel)max_index;
// output.confidence = max_val;
return PROCESS_SUCCESS;
}
libtorch模型的后处理可以使用torch下的api进行操作,最后对结果进行转换,也可以直接将前向结果通过指针进行转换为c++可操作的数据结构,libtorch的后处理还包括分类、分割、检测、追踪等,后面介绍。
tvm模型具有占用显存少,能比torch模型降低40%,以及提高速度。
进行转换前需要先进入pytorch1.4的环境,然后运行以下脚本。
converte_torch2onnx2tvm.py
"""
进行模型转换,torch->onnx->tvm
环境要求:tvm转torch模型必须在pytorch1.4的环境下进行,1.4下可以进行torch->onnx->tvm,
同时也可以进行torch->libtorch转换,但是c++中源码编译的libtorch库要和torch->libtorch转换时使用的pytorch版本一致
由于c++工程中使用的libtorch库是1.0,因此torch->libtorch的过程也要在pytorch1.0环境下进行
"""
import sys
#如果将编译的tvm添加到系统环境变量中不起作用,需要在此进行再次系统添加
sys.path.insert(0,'/data_1/software/use_software/tvm/tvm/topi/python/')
sys.path.insert(0,'/data_1/software/use_software/tvm/tvm/python/')
sys.path.insert(0,'/data_1/software/use_software/tvm/tvm/nnvm/')
import numpy as np
from pynvml import * #pip install nvidia-ml-py3,显存监控
import torch.onnx
import torch.backends.cudnn as cudnn
from models_class.pose_resnet import *
import torchvision.transforms as transforms
import onnx #需要pip 安装
import tvm #不需要pip安装,使用源码编译的
from tvm import relay
from PIL import Image
# 加载torch模型进行测试
def load_torch_model_test(img_path,model_path):
# 初始化模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
cudnn.benchmark = True
model = get_pose_net()
model = model.cuda()
model.to(device)
# 加载模型权重
model.load_state_dict(torch.load(model_path)['state_dict'])
# 进行torch模型测试
test_trans = transforms.Compose([transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
img = Image.open(img_path).convert('RGB')
img = test_trans(img)
# img = Image.fromarray(np.expand_dims(img, 0)) # 扩张0维度
imgblob = img.unsqueeze(0) # tensor禁止进行维度删除
imgblob = torch.autograd.Variable(imgblob).cuda() # 对输入进行数据格式转换,np.array->tensor.cuda.float
torch.no_grad() # 不进行梯度计算
model.eval() # 使用验证模式
heatmap = model(imgblob)
# print(heatmap)
return model
# 将torch模型转为libtorch模型
def gen_torch_to_libtorch(model_path, save_path):
# 初始化模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
cudnn.benchmark = True
model = get_pose_net()
model.to(device)
model = model.cuda()
# 加载模型权重
model.load_state_dict(torch.load(model_path)['state_dict'])
# 将torch转为libtorch并保存
example = torch.rand(1, 3, 256, 256).cuda() # 任意设定一个输入,输入数据的大小为网络输入的大小
traced_script_module = torch.jit.trace(model, example).eval()
traced_script_module.save(save_path)
# 将torch模型转为tvm模型
def gen_torch_to_tvm_model(torch_path, save_libpath, save_graph_json_path, save_param_path):
# 初始化模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
cudnn.benchmark = True
model = get_pose_net()
model.to(device)
model = model.cuda()
# 加载模型权重
model.load_state_dict(torch.load(torch_path)['state_dict'])
# 将torch转为libtorch
example = torch.rand(1, 3, 256, 256).cuda() # 任意设定一个输入,输入数据的大小为网络输入的大小
traced_script_module = torch.jit.trace(model, example).eval()
input_name = 'input_1'
shape_list = [(input_name, example.shape)] # 此处与onnx转tvm不同
mod, params = relay.frontend.from_pytorch(traced_script_module, shape_list)
target = 'cuda' # cpu 版本改成 llvm
target_host = 'llvm'
ctx = tvm.gpu(0)
with relay.build_config(opt_level=1):
graph, lib, params = relay.build(mod,
target=target,
target_host=target_host,
params=params)
# 下面的函数导出我们需要的动态链接库 地址可以自己定义
print("Output model files")
lib.export_library(save_libpath)
# 下面的函数导出我们神经网络的结构,使用json文件保存
with open(save_graph_json_path, 'w') as fo:
fo.write(graph)
# 下面的函数中我们导出神经网络模型的权重参数
with open(save_param_path, 'wb') as fo:
fo.write(relay.save_param_dict(params))
# 将torch模型转为onnx模型
def gen_torch_to_onnx_model(model,save_onnx_path):
# 转 ONNX 模型
example = torch.randn(1, 3, 256, 256).cuda() #输入图像大小,自行设定
input_name = ['input_1']
output_name = ['output_1']
torch_out = torch.onnx.export(model=model, # model being run
args=example, # model input (or a tuple for multiple inputs)
f=save_onnx_path,
input_names=input_name,
output_names=output_name,
verbose=True) # store the trained parameter weights inside the model file # 带参数输出)
print("onnx convert ok")
# 将onnx模型转为tvm模型
def gen_onnx_to_tvm_model(onnx_path,save_libpath,save_graph_json_path,save_param_path):
# 加载onnx模型, 转为TVM
# onnx_model = onnx.load('/data_1/Working/project/torch-onnx-tvm/torch_py/res/torch_onnx_tvm/onnx/carkeypoints.onnx')
onnx_model = onnx.load(onnx_path)
target = 'cuda' # cpu 版本改成 llvm
target_host = 'llvm'
# target = tvm.target.cuda()
input_name = 'input_1' # 默认和输入一致这个格式,在onnx转tvm时
shape_dict = {
input_name: (1, 3, 256, 256)}
mod, params = relay.frontend.from_onnx(onnx_model, shape_dict) #容易出错
with relay.build_config(opt_level=1):
graph, lib, params = relay.build_module.build(mod,
target=target,
target_host=target_host,
params=params)
# 下面的函数导出我们需要的动态链接库 地址可以自己定义
print("Output model files")
# libpath = "/data_1/Working/project/torch-onnx-tvm/res/torch_onnx_tvm/tvm/head_tail.so"
lib.export_library(save_libpath) # tvm的动态库
# 下面的函数导出我们神经网络的结构,使用json文件保存
# graph_json_path = "/data_1/Working/project/torch-onnx-tvm/res/torch_onnx_tvm/tvm/head_tail.json"
with open(save_graph_json_path, 'w') as fo:
fo.write(graph)
# 下面的函数中我们导出神经网络模型的权重参数
# param_path = "/data_1/Working/project/torch-onnx-tvm/res/torch_onnx_tvm/tvm/head_tail.params"
with open(save_param_path, 'wb') as fo:
fo.write(relay.save_param_dict(params))
# 加载tvm模型进行测试
def load_tvm_model_test(libpath,graph_json_path,param_path,image_data=None):
loaded_json = open(graph_json_path).read()
loaded_lib = tvm.runtime.load_module(libpath)
loaded_params = bytearray(open(param_path, "rb").read())
ctx = tvm.gpu(0)
m = graph_runtime.create(loaded_json, loaded_lib, ctx)
m.load_params(loaded_params)
# Set inputs
dtype = 'float32'
if image_data:
data_tvm = tvm.nd.array(image_data.astype(dtype)) # 任意设置一个数据进行测试
else:
data_tvm = tvm.nd.array((np.random.uniform(size=(1,3,256,256))).astype(dtype)) #任意设置一个数据进行测试
input_name = 'input_1'
m.set_input(input_name, data_tvm)
# Execute
m.run()
# Get outputs
tvm_output = m.get_output(0)
print(tvm_output)
if __name__ == "__main__":
os.system("nvcc -V") # 检查nvcc是否起作用
#显存监控
GPU_USE=0
nvmlInit() #初始化
handle = nvmlDeviceGetHandleByIndex(GPU_USE) #获得指定GPU的handle
info_begin = nvmlDeviceGetMemoryInfo(handle) #获得显存信息
#路径设置
img_str ="/data_1/Working/project/torch-onnx-tvm/torch_py/data/[email protected]"
model_path = "/data_1/Working/project/torch-onnx-tvm/torch_py/models/checkpoint_model_epoch_47_acc_0.949421536636013.pth"
onnx_save_path = "/data_1/Working/project/torch-onnx-tvm/torch_py/res/torch_onnx_tvm/onnx/carkeypoints.onnx"
libpath = "/data_1/Working/project/torch-onnx-tvm/torch_py/res/torch_onnx_tvm/tvm/carkeypoints.so" # tvm的动态库
graph_json_path = "/data_1/Working/project/torch-onnx-tvm/torch_py/res/torch_onnx_tvm/tvm/carkeypoints.json"
param_path = "/data_1/Working/project/torch-onnx-tvm/torch_py/res/torch_onnx_tvm/tvm/carkeypoints.params"
libtorch_save_path = "/data_1/Working/project/torch-onnx-tvm/libtorch/model/carkeypoints/carkeypoints.pt"
# gen_torch_to_libtorch(model_path,libtorch_save_path)
#加载torch模型
model = load_torch_model_test(img_str,model_path)
#将torch模型转为onnx模型
gen_torch_to_onnx_model(model,onnx_save_path)
#将onnx模型转为tvm模型 ,使用pycharm运行会报错,链接不到nvcc,需要在终端运行
gen_onnx_to_tvm_model(onnx_save_path,libpath,graph_json_path,param_path)
#加载tvm模型进行测试
load_tvm_model_test(libpath,graph_json_path,param_path)
# #输出显存使用mb
info_end = nvmlDeviceGetMemoryInfo(handle)
print("-"*15+"TORCH GPU MEMORY INFO"+"-"*15)
print(" Memory Total: "+str(info_end.total//(1024**2)))
print(" Memory Free: "+str(info_end.free//(1024**2)))
print(" Memory Used: "+str(info_end.used//(1024**2)-info_begin.used//(1024**2)))
print("-" * 40)
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数