• 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

  • 相关阅读:
    企业级 SpringBoot 教程 (九)springboot整合Redis
    03 网格系统
    02 表单
    01 排版
    客户端调用webSerices
    sql 一行转多行
    sql 多行转一行
    时间差计算 Stopwatch
    sql 游标
    Linq连接查询
  • 原文地址:https://www.cnblogs.com/shuimuqingyang/p/13958561.html
Copyright © 2020-2023  润新知