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