https://blog.csdn.net/enchanted_zhouh/article/details/106063552
本文主要讲解ncnn模型加载的三种方式,模型以上文(https://blog.csdn.net/Enchanted_ZhouH/article/details/105861646)的resnet18模型为示例,模型文件如下:
resnet18.param //模型结构文件
resnet18.bin //模型参数文件
- 1
- 2
第一种方式:直接加载param和bin
最简单的方式为直接加载param和bin文件,适合快速测试模型效果,加载模型代码如下:
ncnn::Net net;
net.load_param("resnet18.param");
net.load_model("resnet18.bin");
- 1
- 2
- 3
param为模型文件,打开如下:
7767517
78 86
Input x 0 1 x
Convolution 123 1 1 x 123 0=64 1=7 11=7 2=1 12=1 3=2 13=2
.
.
.
Flatten 190 1 1 189 190
InnerProduct y 1 1 190 y 0=1000 1=1 2=512000
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
模型的输入为x,输出为y。因此,定义输入和输出的代码如下:
ncnn::Mat in;
ncnn::Mat out;
ncnn::Extractor ex = net.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ex.input("x", in);
ex.extract("y", out);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
测试一张图片,test.jpg换成自己的图片即可,整体代码如下:
#include <opencv2/highgui/highgui.hpp>
#include <vector>
#include "net.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");
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("x", in);
ex.extract("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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
运行结果如下:
predicted class: 588, predicted value: 154.039322
- 1
第二种方式:加载二进制的 param.bin 和 bin
第一种方式有个明显的问题,param模型文件是明文的,如果直接发布出去,任何使用者都可以窥探到模型结构,这很不利于加密工作的进行。
因此,引出第二种模型加载方式,将param转换成二进制文件,ncnn编译好后,tools里有个ncnn2mem工具,使用此工具可以生成param.bin、id.h和mem.h三个文件,命令如下:
ncnn2mem resnet18.param resnet18.bin resnet18.id.h resnet18.mem.h
- 1
生成三个文件如下:
resnet18.param.bin //二进制的模型结构文件
resnet18.id.h //模型结构头文件
resnet18.mem.h //模型参数头文件
- 1
- 2
- 3
param.bin不是明文的,没有可见字符串,适合模型的发布,加载模型代码如下:
ncnn::Net net;
net.load_param_bin("resnet18.param.bin");
net.load_model("resnet18.bin");
- 1
- 2
- 3
由于param.bin窥探不到模型结构,因此,需要导入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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如上可见,模型的输入为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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
同理,整体预测代码如下:
#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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
运行结果如下:
predicted class: 588, predicted value: 154.039322
- 1
第三种方式:从内存加载param 和 bin
虽然第二种模型加载方式可以避免模型结构的泄露,但是模型和代码还是处于分离状态的,如果将其打包发布,那么模型文件需要独立出来进行打包。
打个比方,如果写了一个算法的.so接口供前端调用,不仅需要发布.so文件,还需要发布model文件,这样着实不太方便。如若可以将model一起打包进.so接口,直接将包含了代码和模型的.so扔给前端人员调用,这样更为便利。
第二种方式中,已经生成了id.h和mem.h两个头文件,此处,只需要这两个头文件即可,不需要再调用param和bin文件。
从内存加载模型的代码如下:
#include "resnet18.mem.h"
ncnn::Net net;
net.load_param(resnet18_param_bin);
net.load_model(resnet18_bin);
- 1
- 2
- 3
- 4
定义输入和输出的代码和第二种方式保持一致,如下:
#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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
整体预测代码如下:
#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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
运行结果如下:
predicted class: 588, predicted value: 154.039322
- 1
至此,三种ncnn模型加载方式全部介绍完毕,做个简单的总结如下:
1. 直接加载ncnn模型可以快速测试模型效果,但是param是明文的,打开文件可以直接看到模型结构,不利于加密;
2. 将param文件转为二进制,可以起到一定的加密作用;
3. 将模型结构和参数直接读进内存,和代码整合在一起,利于打包发布,只需提供一个打包好的库即可(.so/.dll等),不用将模型文件单独拷贝到部署机器上,大大方便了算法的部署以及加密。
参考资料:https://github.com/Tencent/ncnn/wiki/use-ncnn-with-alexnet.zh