美文网首页机器学习
ncnn之模型转换与加密教程

ncnn之模型转换与加密教程

作者: 一个摸鱼AI喵 | 来源:发表于2021-10-06 09:23 被阅读0次

    一、模型构造与支持模型

    ncnn模型有两个文件:参数文件.param跟模型文件.bin

    支持转为ncnn的模型有caffe、darknet、mxnet、onnx(故tensorflow跟pytorch模型都先要转成onnx或者其他格式)

    二、模型转换

    只能用CMD进入,其他方式无效

    命令行中文件明的顺序也是有要求,不能调换顺序,可以理解为先放小的再放大的

    在所有工作之前需要先编译ncnn,编译ncnn还需要cmake protoobuf vs2019支持,具体查看cmake_protoobuf_ncnn_opecv安装教程.md

    编译后....../ncnn/build-vs2019/tools/下有caffe、darknet、onnx文件夹,里面有各自的转模型工具.exe

    2.1 onnx2ncnn

    进入onnx工具文件夹,并把onnx模型放入该文件夹中,也可以新建一个文件夹onnxModels放置onnx模型,新建一个文件夹ncnnModels放置ncnn模型

    cd onnx
    mkdir onnxModels
    mkdir ncnnModels
    ./onnx2ncnn ./onnxModels/resnet18-sim.onnx ./ncnnModels/resnet18.param ./ncnnModels/resnet18.bin 
    

    2.2 mxnet2ncnn

    cd mxnet
    mkdir mxnetModels
    mkdir ncnnModels
    mxnet2ncnn.exe ./mxnetModels/mnet12-symbol.json ./mxnetModels/mnet12-symbol.params ./ncnnModels/mnet12-symbol.param ./ncnnModels/mnet12-symbol.bin
    

    或者新建一个脚本文件makemodel.dat

    set MXNET_MODEL_DIR=mxnetModels
    set NCNN_MODEL_DIR=ncnnModels
    mxnet2ncnn.exe %MXNET_MODEL_DIR%/mnet12-symbol.json %MXNET_MODEL_DIR%/mnet12-symbol.params %NCNN_MODEL_DIR%/mnet12-symbol.param %NCNN_MODEL_DIR%/mnet12-symbol.bin
    pause
    

    运行即可

    2.3 caffe2ncnn

    2.3.1 旧版caffe模型转新版caffe模型

    因为ncnn只支持转换新版的caffe模型,所以需要对旧版的caffe模型

    $ cd caffe/build/tools
    $ upgrade_net_proto_text mobilenet_deploy.prototxt mobilenet_deploy_new.prototxt
    $ upgrade_net_proto_binary mobilenet.caffemodel mobilenet_new.caffemodel
    

    2.3.2 新版caffe模型转ncnn

    //在ncnn
    $cd 
    $./caffe2ncnn mobilenet_deploy_new.prototxt mobilenet_new.caffemodel mobilenet.param mobilenet.bin
    

    [进行在线模型转换神器] 一键转换 Caffe, ONNX, TensorFlow 到 NCNN, MNN, Tengine (convertmodel.com)

    image-20210723165936775.png

    三、NCNN模型加密

    在ncnn/build-vs2019/tools下有一个ncnn2mem.exe文件,对参数文件和模型文件进行加密会生成param.bin、id.h跟mem.h头文件, 命令行中不需要写param.bin

    $ncnn2mem resnet18.param resnet18.bin resnet18.id.h resnet18.mem.h
    

    注意:名字不能以数字开头

    resnet18.param.bin    //二进制的模型结构文件
    resnet18.id.h        //模型结构头文件
    resnet18.mem.h       //模型参数头文件
    

    读取文件

    //load非加密的ncnn模型
    ncnn::Net net;
    net.load_param_bin("resnet18.param");
    net.load_model("resnet18.bin");
    //load加密的ncnn模型
    ncnn::Net net;
    net.load_param_bin("resnet18.param.bin");
    net.load_model("resnet18.bin");
    

    *在安卓项目中,会加入文件管理器AAssetManager mgr = AAssetManager_fromJava(env, assetManager),模型放入app/src/main/assets文件夹中,会去搜索模型.

    但是纯C++项目,相对路径则是按build后所在可执行文件的地址:如vs编译后执行路径在<项目/out/build/x64-Release>下

    由于param.bin窥探不到模型结构,因此,需要导入id.h头文件来获取模型的输入和输出

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

    把模型文件也打包进so

    已经生成了id.h和mem.h两个头文件,

    resnet18.param.bin    //二进制的模型结构文件
    resnet18.id.h        //模型结构头文件
    resnet18.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);
    

    某些使用技巧

    Extractor 有个多线程加速的开关,设置线程数能加快计算

    ex.set_num_threads(4);
    

    整体预测代码如下:

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

    Net 有从 FILE* 文件描述加载的接口,可以利用这点把多个网络和模型文件合并为一个,分发时能方便些,内存引用就无所谓了

    $ cat alexnet.param.bin alexnet.bin > alexnet-all.bin

    #include "net.h"
    FILE* fp = fopen("alexnet-all.bin", "rb");
    net.load_param_bin(fp);
    net.load_model(fp);
    fclose(fp);
    

    参考:use ncnn with alexnet.zh · Tencent/ncnn Wiki (github.com)

    相关文章

      网友评论

        本文标题:ncnn之模型转换与加密教程

        本文链接:https://www.haomeiwen.com/subject/anconltx.html