目录
“Windows配置pytorch转onnx转ncnn转android设备” 如何配置环境设置,可以参考这篇如何配置
一、pytorch下生成的预训练文件->onnx
1.预训练文件
2.转化代码
3.简化onnx文件模型
二、onnx文件转ncnn
1.找到onnx2ncnn.exe
2.ncnn2mem.exe
三、部署到手机
总结
在pytorch下训练自己的模型,生成.pth文件或者.pkl文件
注:.pth文件是指存储网络结构的参数
.pkl文件是指存储网络结构和网络参数
这里我使用的是.pth文件,我使用.pkl文件在转化的时候出现了一些错误,所以选择使用了pth文件
下面附有转换代码,并标注出在何处需要修改为自定义的名字
import numpy as np
import torch
from PIL import Image
import onnx
import vgg # 1.修改为自己的模型的源代码文件
src_image = 'cat.jpg' # 2.图片名字
input_size = 32 # 3.模型输入尺寸,与模型和训练集有关
mean_vals = torch.tensor([0.5070751592371323, 0.48654887331495095, 0.4409178433670343], dtype=torch.float32).view(3,1,1)
std_vals = torch.tensor([0.2673342858792401, 0.2564384629170883, 0.27615047132568404], dtype=torch.float32).view(3,1,1) # 3.改为相应数据集的均值和方差
###################prepare model#################
infer_device = torch.device('cpu')
res_model = vgg.vgg16_bn() #4.加载预训练模型的源代码结构文件
state_dict = torch.load('model'.pth', map_location=lambda storage, loc: storage)
#5.改成自己的预训练模型文件名字 model.pth
#res_model.load_state_dict({k.replace('vgg_model.',''):v for k,v in state_dict.items()})
#这一句代码加不加要视情况而定 我在下面会说什么情况加这一句
res_model.to(infer_device)
res_model.eval()
#################prepare test image################
pil_im = Image.open(src_image).convert('RGB')
pil_im_resize = pil_im.resize((input_size, input_size), Image.BILINEAR)
origin_im_tensor =
torch.ByteTensor(torch.ByteStorage.from_buffer(pil_im_resize.tobytes()))
origin_im_tensor = origin_im_tensor.view(input_size, input_size, 3)
origin_im_tensor = origin_im_tensor.transpose(0, 1).transpose(0, 2).contiguous()
origin_im_tensor = (origin_im_tensor.float()/255 - mean_vals)/std_vals
origin_im_tensor = origin_im_tensor.unsqueeze(0)
##################test###########################
with torch.no_grad():
pred = res_model(origin_im_tensor.to(infer_device))
predidx = torch.argmax(pred, dim=1)
##################export###############
output_onnx = 'output_onnx_name.onnx' #6.修改成自己的输出名字
x = origin_im_tensor
print("==> Exporting model to ONNX format at '{}'".format(output_onnx))
input_names = ["input0"]
output_names = ["output0"]
torch_out = torch.onnx._export(res_model, x, output_onnx, export_params=True, verbose=False, input_names=input_names, output_names=output_names)
##################test onnx###############
print("==> Loading and checking exported model from '{}'".format(output_onnx))
onnx_model = onnx.load(output_onnx)
onnx.checker.check_model(onnx_model) # assuming throw on error
print("==> Passed")
注:代码中有一处语句为注释也就是在导入预训练参数到自己的网络结构中时,如果出现“Unexpected key(s) in state_dict: vgg_model.conv1.weight, vgg_model.bn1”,说明自己训练的模型文件名字和现在结构匹配不上,需要讲"vgg_model."置换为" (空格)这个要看你出错的文件中是不是"vgg_model"这个名字,不是的话,换成你自己的名字(出错的语句中有)
这一步需要将生成的.onnx文件简化一下,命令如下:
python -m onnxsim output_onnx_name.onnx output_onnx_name-sim.onnx
#output_onnx_nameonnx文件在上面是生成 output_onnx_name-sim.onnx可改为自己目标文件名
整个onnx转换的最终目标就是获得output_onnx_name-sim.onnx文件
一般在ncnn安装路径 ... ... \ncnn-master\build-vs2022\tools\onnx (找到你自己的ncnn安装路径):
同时,将上一步生成的output_onnx_name-sim.onnx文件复制到该路径下
onnx2ncnn.exe output_onnx_name-sim.onnx output_onnx_name-sim.param output_onnx_name-sim.bin
output_onnx_name-sim.param
和
output_onnx_name-sim.bin
可以修改前面的名字,只要后缀不更改就可以,这两个就是这一步需要的目标文件。
一般在ncnn安装路径 ... ... \ncnn-master\build-vs2022\tools (和上面的安装路径不同) :
同时,将上一步生成的output_onnx_name-sim.param
和
output_onnx_name-sim.bin
文件复制到该路径下
ncnn2mem output_onnx_name-sim.param output_onnx_name-sim.bin output_onnx_name-sim.id.h output_onnx_name-sim.mem.h
output_onnx_name-sim.param.bin 、
output_onnx_name-sim.id.h 和
output_onnx_name-sim.mem.h
是这一步需生成的目标文件,output_onnx_name-sim.id.h是含有网络结构各层的名字,可以看到输入层、输出层和中间各层的名字,在后面网络的输入输出可以用到。
1.下载squeezenet测试工程
建议先把squeezenet最原始的工程运行起来,这个运行的时候也会遇到一些问题,尤其是gradle的版本之类的,先尝试把这个工程部署到手机上。
部署到手机上有两种方式:
2.移到文件到工程中
output_onnx_name-sim.param.bin和output_onnx_name-sim.bin-->
... \app\src\main\assets output_onnx_name-sim.id.h
--> ..\app\src\main\jni
3.修改文件内容
yourSelectedImage = Bitmap.createScaledBitmap(rgba, 32, 32, false);
#图像大小调整,调整的是训练时的图片的大小 我的是32*32
1) #include "output_onnx_name-sim.id.h" #更改自己的.id.h文件名字
2) int ret = squeezenet.load_param_bin(mgr, "output_onnx_name-sim.param.bin");
#更改成自己生成的参数文件
3) int ret = squeezenet.load_model(mgr, "output_onnx_name-sim.bin");
#更改成自己生成的bin文档
4)
#这里面的图像的大小更改为自己的图像大小
5)均值和方差更改:
pytorch: ( x/255 - mean ) / std
ncnn: ( x - mean_vals ) * std_vals
修改公式为:
mean_vals = 255 * mean
std_vals = 1 / ( 255 * std )
6)网络输入输出和中间层修改
ex.input(output_onnx_name_sim_param_id ::BLOB_input0, in);
#output_onnx_name_sim_param_id 修改为自己.id.h文件中类的名字
#BLOB_input0:修改为自己的输入层的名字 .id.h文件中可以查看
ex.extract(output_onnx_name_sim_param_id ::BLOB_output0, out);
#output_onnx_name_sim_param_id 修改为自己.id.h文件中类的名字
#BLOB_output0:修改为自己的输入层的名字 .id.h文件中可以查看
注:上面我们写的是输入和输出,但有时候我们需要用到中间层的结果,这样的时候我们只需要在输入和输之间加入语句ex.extract(output_onnx_name_sim_param_id ::BLOB_output0, out);提取中间的结果。
这里要说明的是每一次extract之后,并不是重新从输入层执行到该层,而且从上一次执行的位置到该层执行,之前执行的结果会被保留,所以extract并不会重复执行网络的路径。
全部都弄完之后,就可以调试运行啦!!
祝大家一举成功!!!
第一次尝试把深度学习模型部署到手机上,刚开始想到要这么做的时候有点懵懵的,完全不知道怎么着手去做这件事,网上相关资料并不多,后来也查到了一些参考资料,在网络上大家的帮助和自己的努力尝试下,成功运行啦!
写这篇文章也是想能够为想要做这件事的伙伴们提供一些思路和参考,也是想如果日后我再想做这件事的时候,可以有回顾的资料。
参考链接:
参考链接1https://blog.csdn.net/weixin_43541325/article/details/118083141参考链接2https://github.com/nihui/ncnn-android-squeezenet
参考链接3https://blog.csdn.net/xiaodidididi521/article/details/123520665参考链接4https://blog.csdn.net/xiaodidididi521/article/details/123520665?spm=1001.2014.3001.5502
参考链接5https://github.com/Tencent/ncnn