一、模型构造与支持模型
ncnn模型有两个文件:参数文件.param跟模型文件.bin
支持转为ncnn的模型有caffe、darknet、mxnet、onnx(故tensorflow跟pytorch模型都先要转成onnx或者其他格式)
二、模型转换
只能用CMD进入,其他方式无效
命令行中文件明的顺序也是有要求,不能调换顺序,可以理解为先放小的再放大的
在所有工作之前需要先编译ncnn,编译ncnn还需要cmake protoobuf vs2019支持,具体查看cmake_protoobuf_ncnn_opecv安装教程.md
编译后....../ncnn/build-vs2019/tools/下有caffe、darknet、onnx文件夹,里面有各自的转模型工具.exe
2.1 onnx2ncnn
进入onnx工具文件夹,并把onnx模型放入该文件夹中,也可以新建一个文件夹onnxModels放置onnx模型,新建一个文件夹ncnnModels放置ncnn模型
cd onnx
mkdir onnxModels
mkdir ncnnModels
./onnx2ncnn ./onnxModels/resnet18-sim.onnx ./ncnnModels/resnet18.param ./ncnnModels/resnet18.bin
2.2 mxnet2ncnn
cd mxnet
mkdir mxnetModels
mkdir ncnnModels
mxnet2ncnn.exe ./mxnetModels/mnet12-symbol.json ./mxnetModels/mnet12-symbol.params ./ncnnModels/mnet12-symbol.param ./ncnnModels/mnet12-symbol.bin
或者新建一个脚本文件makemodel.dat
set MXNET_MODEL_DIR=mxnetModels
set NCNN_MODEL_DIR=ncnnModels
mxnet2ncnn.exe %MXNET_MODEL_DIR%/mnet12-symbol.json %MXNET_MODEL_DIR%/mnet12-symbol.params %NCNN_MODEL_DIR%/mnet12-symbol.param %NCNN_MODEL_DIR%/mnet12-symbol.bin
pause
运行即可
2.3 caffe2ncnn
2.3.1 旧版caffe模型转新版caffe模型
因为ncnn只支持转换新版的caffe模型,所以需要对旧版的caffe模型
$ cd caffe/build/tools
$ upgrade_net_proto_text mobilenet_deploy.prototxt mobilenet_deploy_new.prototxt
$ upgrade_net_proto_binary mobilenet.caffemodel mobilenet_new.caffemodel
2.3.2 新版caffe模型转ncnn
//在ncnn
$cd
$./caffe2ncnn mobilenet_deploy_new.prototxt mobilenet_new.caffemodel mobilenet.param mobilenet.bin
[进行在线模型转换神器] 一键转换 Caffe, ONNX, TensorFlow 到 NCNN, MNN, Tengine (convertmodel.com)
image-20210723165936775.png三、NCNN模型加密
在ncnn/build-vs2019/tools下有一个ncnn2mem.exe文件,对参数文件和模型文件进行加密会生成param.bin、id.h跟mem.h头文件, 命令行中不需要写param.bin
$ncnn2mem resnet18.param resnet18.bin resnet18.id.h resnet18.mem.h
注意:名字不能以数字开头
resnet18.param.bin //二进制的模型结构文件
resnet18.id.h //模型结构头文件
resnet18.mem.h //模型参数头文件
读取文件
//load非加密的ncnn模型
ncnn::Net net;
net.load_param_bin("resnet18.param");
net.load_model("resnet18.bin");
//load加密的ncnn模型
ncnn::Net net;
net.load_param_bin("resnet18.param.bin");
net.load_model("resnet18.bin");
*在安卓项目中,会加入文件管理器AAssetManager mgr = AAssetManager_fromJava(env, assetManager),模型放入app/src/main/assets文件夹中,会去搜索模型.
但是纯C++项目,相对路径则是按build后所在可执行文件的地址:如vs编译后执行路径在<项目/out/build/x64-Release>下
由于param.bin窥探不到模型结构,因此,需要导入id.h头文件来获取模型的输入和输出
#include "resnet18.id.h"
resnet18.id.h文件如下:
namespace resnet18_param_id {
const int LAYER_x = 0;
const int BLOB_x = 0;
.
.
.
const int LAYER_y = 77;
const int BLOB_y = 85;
} // namespace resnet18_param_id
如上可见,模型的输入为resnet18_param_id::BLOB_x,输出为resnet18_param_id::BLOB_y,定义输入和输出的代码如下:
#include "resnet18.id.h"
ncnn::Mat in;
ncnn::Mat out;
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ex.input(resnet18_param_id::BLOB_x, in);
ex.extract(resnet18_param_id::BLOB_y, out);
不同网络的输入输出名字不同,按实际名字来
同理,整体预测代码如下:
#include <opencv2/highgui/highgui.hpp>
#include <vector>
#include "net.h"
#include "resnet18.id.h"
using namespace std;
int main()
{
cv::Mat img = cv::imread("test.jpg");
int w = img.cols;
int h = img.rows;
ncnn::Mat in = ncnn::Mat::from_pixels_resize(img.data, ncnn::Mat::PIXEL_BGR, w, h, 224, 224);
ncnn::Net net;
net.load_param_bin("resnet18.param.bin");
net.load_model("resnet18.bin");
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ncnn::Mat out;
ex.input(resnet18_param_id::BLOB_x, in);
ex.extract(resnet18_param_id::BLOB_y, out);
ncnn::Mat out_flattened = out.reshape(out.w * out.h * out.c);
vector<float> score;
score.resize(out_flattened.w);
for (int i = 0; i < out_flattened.w; ++i) {
score[i] = out_flattened[i];
}
vector<float>::iterator max_id = max_element(score.begin(), score.end());
printf("predicted class: %d, predicted value: %f", max_id - score.begin(), score[max_id - score.begin()]);
net.clear();
return 0;
}
把模型文件也打包进so
已经生成了id.h和mem.h两个头文件,
resnet18.param.bin //二进制的模型结构文件
resnet18.id.h //模型结构头文件
resnet18.mem.h //模型参数头文件
此处,只需要这两个头文件即可,不需要再调用param和bin文件
从内存加载模型的代码如下:
#include "resnet18.mem.h"
ncnn::Net net;
net.load_param(resnet18_param_bin);
net.load_model(resnet18_bin);
定义输入和输出的代码和第二种方式保持一致,如下:
#include "resnet18.id.h"
ncnn::Mat in;
ncnn::Mat out;
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ex.input(resnet18_param_id::BLOB_x, in);
ex.extract(resnet18_param_id::BLOB_y, out);
某些使用技巧
Extractor 有个多线程加速的开关,设置线程数能加快计算
ex.set_num_threads(4);
整体预测代码如下:
#include <opencv2/highgui/highgui.hpp>
#include <vector>
#include "net.h"
#include "resnet18.id.h"
#include "resnet18.mem.h"
using namespace std;
int main()
{
cv::Mat img = cv::imread("test.jpg");
int w = img.cols;
int h = img.rows;
ncnn::Mat in = ncnn::Mat::from_pixels_resize(img.data, ncnn::Mat::PIXEL_BGR, w, h, 224, 224);
ncnn::Net net;
net.load_param(resnet18_param_bin);
net.load_model(resnet18_bin);
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ncnn::Mat out;
ex.input(resnet18_param_id::BLOB_x, in);
ex.extract(resnet18_param_id::BLOB_y, out);
ncnn::Mat out_flattened = out.reshape(out.w * out.h * out.c);
vector<float> score;
score.resize(out_flattened.w);
for (int i = 0; i < out_flattened.w; ++i) {
score[i] = out_flattened[i];
}
vector<float>::iterator max_id = max_element(score.begin(), score.end());
printf("predicted class: %d, predicted value: %f", max_id - score.begin(), score[max_id - score.begin()]);
net.clear(); //卸载模型
return 0;
Net 有从 FILE* 文件描述加载的接口,可以利用这点把多个网络和模型文件合并为一个,分发时能方便些,内存引用就无所谓了
$ cat alexnet.param.bin alexnet.bin > alexnet-all.bin
#include "net.h"
FILE* fp = fopen("alexnet-all.bin", "rb");
net.load_param_bin(fp);
net.load_model(fp);
fclose(fp);
参考:use ncnn with alexnet.zh · Tencent/ncnn Wiki (github.com)
网友评论