ncnn模型加載的三種方式


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM