ncnn模型加載的三種方式

       本文主要講解ncnn模型加載的三種方式,模型以上文(https://blog.csdn.net/Enchanted_ZhouH/article/details/105861646)的resnet18模型爲示例,模型文件如下:

resnet18.param    //模型結構文件
resnet18.bin     //模型參數文件
       第一種方式:直接加載param和bin

       最簡單的方式爲直接加載param和bin文件,適合快速測試模型效果,加載模型代碼如下:

ncnn::Net net;
net.load_param("resnet18.param");
net.load_model("resnet18.bin");

       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

       模型的輸入爲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);

       測試一張圖片,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;
}

       運行結果如下:

predicted class: 588, predicted value: 154.039322
       第二種方式:加載二進制的 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

       生成三個文件如下:

resnet18.param.bin    //二進制的模型結構文件
resnet18.id.h        //模型結構頭文件
resnet18.mem.h       //模型參數頭文件

       param.bin不是明文的,沒有可見字符串,適合模型的發佈,加載模型代碼如下:

ncnn::Net net;
net.load_param_bin("resnet18.param.bin");
net.load_model("resnet18.bin");

       由於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

       如上可見,模型的輸入爲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;
}

       運行結果如下:

predicted class: 588, predicted value: 154.039322
       第三種方式:從內存加載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);

       定義輸入和輸出的代碼和第二種方式保持一致,如下:

#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"
#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;
}

       運行結果如下:

predicted class: 588, predicted value: 154.039322

       至此,三種ncnn模型加載方式全部介紹完畢,做個簡單的總結如下:

       1. 直接加載ncnn模型可以快速測試模型效果,但是param是明文的,打開文件可以直接看到模型結構,不利於加密;

       2. 將param文件轉爲二進制,可以起到一定的加密作用;

       3. 將模型結構和參數直接讀進內存,和代碼整合在一起,利於打包發佈,只需提供一個打包好的庫即可(.so/.dll等),不用將模型文件單獨拷貝到部署機器上,大大方便了算法的部署以及加密。

       參考資料:https://github.com/Tencent/ncnn/wiki/use-ncnn-with-alexnet.zh

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章