美文网首页
安卓调用第三方带模型的so文件教程

安卓调用第三方带模型的so文件教程

作者: 一个摸鱼AI喵 | 来源:发表于2021-10-14 18:49 被阅读0次

    安卓调用第三方带模型的so文件教程

    百度上的教程基本都是复制黏贴,针对的是简单的demo.而项目中都比较复杂,做AI的都需要加载模型,而正常的编译生成so文件是不带模型文件的.故需要特殊处理.

    一、准备工作

    1.1 具有ncnn模型文件,如果没有可以把mxnet,onnx跟caffe模型通过ncnn工具进行转换

    1.2 电脑已经安装了cmake, 编译了protoobuf、ncnn(利用vs2019编译,查看cmake_protoobuf_ncnn_opecv安装教程)

    二、不把模型打包进so的调用方法

    2.1 原项目编译

    原项目新建时需选择C++安卓项目

    在编译原始项目时,在app/src/mian下新建一个assets的文件夹,不能右键-新建文件夹,这不不会被识别为资源文件.必须在android studio里右键-New-Folder-Assets Folder,把模型文件放入该文件夹中.

    编译完成后在\app\build\intermediates下的cmake, merged_native_libs,stripped_native_libs下的子文件夹下产生相应的so文件,文件格式为lib库名.so,其中库名在CmakeLists.txt里设置.

    ./app/build/intermediates/cmake/debug/obj/arm64-v8a/libarc_sateis.so
    ./app/build/intermediates/cmake/debug/obj/armeabi-v7a/libarc_sateis.so
    ./app/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libtest.so
    ./app/build/intermediates/merged_native_libs/debug/out/lib/armeabi-v7a/libtest.so
    ./app/build/intermediates/stripped_native_libs/debug/out/lib/arm64-v8a/libtest.so
    ./app/build/intermediates/stripped_native_libs/debug/out/lib/armeabi-v7a/libtest.so
    

    其中obj是中间生成so,用的话可以用merged_native_libs和stripped_native_libs下的;
    区别:cmake, merged_native_libs的so文件一样大,stripped_native_libs的so文件更小.,应该是merged下包含一些map信息、地址等,调试用比较合适;stripped移除了这部分,release比较合适

    2.2 新安卓项目

    2.2.1 拷贝so文件

    拷贝出任意一个(拷贝出带安卓cpu的文件夹,如armeabi-v7a), 在新安卓项目中(不需要C++安卓项目),在app下新建文件夹libs,把带so的文件夹拷贝进这里:

    image-20210715100207195.png

    在app下的build.gradle中增加ndk 跟sourceSets

    android {
        compileSdkVersion 30
        buildToolsVersion "30.0.3"
        defaultConfig {
            applicationId "com.ylz.javatest"
            minSdkVersion 26
            targetSdkVersion 30
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            ndk {
    //            moduleName "ncnn"
                ldLibs "log"
                abiFilters  "armeabi-v7a", "armeabi", "x86"
            }
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        sourceSets {
            main {
                jniLibs.srcDirs = ['libs']   //寻找库文件地址添加进library
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'androidx.appcompat:appcompat:1.0.2'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.0'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    

    这样设置后会在app下自动生成jniLibs文件夹,含了刚刚添加进去的文件

    image-20210715102114669.png

    2.2.2 设置资源文件

    同源项目一样,在app/src/main下新建assets资源文件夹,并把模型文件放入

    2.2.3 建立类文件

    需要建立与so文件功能对应的类文件,里面含有他的功能,并且文件的名称跟地址都要跟源so一模一样.

    image-20210715104837582.png

    点击运行即可生成apk,可使用so文件里的功能

    三、模型文件加密

    原始ncnn模型文件有:.param参数文件跟.bin模型文件

     mnet.25-opt.param mnet.25-opt.bin 
    

    由于后面生成头文件 .跟-都会变成下划线,我干脆对上面文件重命名

    mnet25.param mnet25.bin  
    

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

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

    读取文件

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

    四、源文件改造

    4.1 导入头文件

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

    这种方式虽然给模型文件加密了,但还是没办法把模型文件加入到so文件,其实两个头文件已经涵盖了模型的所有内容,所以我们只需要把两个头文件赋值到源项目中的app/src/main/cpp下,就不再需要assets文件夹了.

    image-20210715110328657.png

    打开mnet25.id.h查看内容如下:

    #ifndef NCNN_INCLUDE_GUARD_mnet25_id_h
    #define NCNN_INCLUDE_GUARD_mnet25_id_h
    namespace mnet25_param_id {
    const int LAYER_data = 0;
    const int BLOB_data = 0;
    const int LAYER_mobilenet0_conv0_fwd = 1;
    const int BLOB_mobilenet0_relu0_fwd = 1;
    const int LAYER_mobilenet0_conv1_fwd = 2;
    const int BLOB_mobilenet0_relu1_fwd = 2;
    ...
    ...
    const int BLOB_face_rpn_cls_prob_reshape_stride8 = 106;
    const int LAYER_face_rpn_bbox_pred_stride8 = 89;
    const int BLOB_face_rpn_bbox_pred_stride8 = 107;
    const int LAYER_face_rpn_landmark_pred_stride8 = 90;
    const int BLOB_face_rpn_landmark_pred_stride8 = 108;
    } // namespace mnet25_param_id
    #endif // NCNN_INCLUDE_GUARD_mnet25_id_h
    

    可以看到输入层输入数据命名为mnet25_param_id::BLOB_data

    在原始cpp代码中需要修改输入跟输出,以及加载方式:

    4.2 增加导入头文件

    #include "mnet25.id.h"#include "mnet25.mem.h"
    

    导入mmet25.mem.h 会报文件大小限制问题,根据提示修改设置Help-Edit Custom Properties

    # custom Android Studio propertiesidea.max.intellisense.filesize=5000000  //我设置为5M
    

    4.3 修改加载模型方式

    从内存引用加载网络和模型,没有可见字符串,模型数据全在代码里头,没有任何外部文件 另外,android apk 打包的资源文件读出来也是内存块

    当加载模型文件时,可传入文件管理器AAssetManager,但如果加载为模型的头文件时,不支持该项.

    //加载模型方式//AAssetManager *mgr = AAssetManager_fromJava(env, assetManager); //int ret = retinaface.load_param(mgr, "mnet.25-opt.param"); //int ret = retinaface.load_param_bin(mgr,"mnet25.param.bin")  //加载二进制模型//ret = retinaface.load_model(mgr,"mnet25.bin")
    

    <font color='red'>这种方式ret=0,表明加载模型/参数成功</font>

    //加载头文件方式int ret =retinaface.load_param(mnet25_param_bin);   //在官方说明里用的是load_param_bin,但是测试发现报错, int ret =retinaface.load_model(mnet25_bin)
    

    <font color='red'>这种方式 加载成功,ret并不是==0, 在我这个测试中加载成功参数ret=4984,bin=853632</font>

    安卓c++查看log信息方法

    注意:log打印信息必须为char* 类型,所以其他类型都要转为char*类型

    #include <android/log.h>#define TAG "projectname" // 这个是自定义的LOG的标识#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型//或者#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)//其他类似
    
    //打印信息//原始方法__android_log_print(ANDROID_LOG_DEBUG, "RetinaFace", "load_param failed")  //固定内容__android_log_print(ANDROID_LOG_DEBUG, "RetinaFace", "加载模型参数=%d",ret);   //根据参数ret变化//简写方法LOGI("ret= %d \n",ret)   //必须有有%d//如果参数为char*时,采用以下方法int ret;string ret_info = to_string(ret);const char * ret_infos = ret_info.c_str();   //或者用.data()LOGI("ret= %s \n",ret_infos)
    

    <font color='red'>注意:打印信息的内容格式只能为char*,必须要加%,如%d,%f,%s, 不能像python的print()或者c++的cout功能.</font>

    解决中文乱码问题:

        cpp文件必须在UTF-8下编写,如果之前为GBK, 则点击后选择utf-8 点convert
    
    image-20210715195245963.png

    4.4 修改输入与输出

    修改的内容为把字符串的输入输出层名称根据mnet25.id.h 修改为指引方式

    输入修改

    //    ex.input("data", in);    ex.input(mnet25_param_id::BLOB_data, in);
    

    输出修改(其中一个示意)

        // stride 32    {        ncnn::Mat score_blob, bbox_blob, landmark_blob;//        ex.extract("face_rpn_cls_prob_reshape_stride32", score_blob);//        ex.extract("face_rpn_bbox_pred_stride32", bbox_blob);//        ex.extract("face_rpn_landmark_pred_stride32", landmark_blob);        ex.extract(mnet25_param_id::BLOB_face_rpn_cls_prob_reshape_stride32, score_blob);        ex.extract(mnet25_param_id::BLOB_face_rpn_bbox_pred_stride32, bbox_blob);        ex.extract(mnet25_param_id::BLOB_face_rpn_landmark_pred_stride32, landmark_blob);
    

    这样运行编译后即可得到so包含了两个头文件,即含有了模型文件

    另外,可进行如下设置

        ncnn::Extractor ex = retinaface.create_extractor();    ex.set_light_mode(true);   //使用light模式,intermediate blob will be recycled when enabled    ex.set_num_threads(4);      //使用4线程
    

    4.5 其他项目调用第三方so文件

    新建一个安卓空项目,设置同2.2一致,唯一不同就是不需要新建assets文件.

    这样我们就只需要提供一个so文件给别人,而不需要提供模型文件.别人就可以在他们项目中调用我们的模型及函数了.

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

    相关文章

      网友评论

          本文标题:安卓调用第三方带模型的so文件教程

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