MNIST 图像分类实战3-模型编译动态库

124 阅读3分钟

继上文两篇文章,我们已经实现了 针对 MNIST 数据集的模型训练和推理

今天我们来看一下 怎么将推理代码编译成动态库so 供Android项目调用

企业微信截图_17451982797131.png

JNI接口实现

extern "C"
JNIEXPORT jstring JNICALL
Java_com_test_cmaketest_JniTest_startPredict(JNIEnv *env, jobject thiz, jstring models_android_path,
                                            jstring img_android_path) {

   mnncv::MNNAdapterInference mnnAdapterInference;

   std::string path = jstring2str(env, models_android_path);
   std::string img_path = jstring2str(env, img_android_path);

   LOGE("modelPath=%s", path.c_str());
   LOGE("imgPath=%s", img_path.c_str());

   std::string pred = mnnAdapterInference.startPredict(path, img_path);
   LOGE("pred=%s", pred.c_str());

   return env->NewStringUTF(pred.c_str());
}

现在看下CmakeLists


cmake_minimum_required(VERSION 3.18)
project(CmakeTest)

set(CMAKE_CXX_STANDARD 14)

option( BUILD_SHARE             "Build shared libs"     ON )
option( BUILD_SAMPLES           "Build samples demo"    ON )

# 1.设置库名称 LIB_NAME 最后生成.so 的名字
set(LIB_NAME cmakeTest_0.0.1)

# 2. 递归查找实现 cpp 文件 将所有文件都加入到 SRCfile(GLOB_RECURSE SRC CONFIGURE_DEPENDS src/*.cpp )

# 3. jni接口类
file(GLOB_RECURSE ANDROID_JNI CONFIGURE_DEPENDS jni_interface/*)

if (ANDROID)
    message("[${LIB_NAME}]Build Android")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
    set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
    message("building release android")

    # 根据 OPENCV_DIR 指定的路径去寻找 opencv 并自动设置相关的 include_directories 和 link_directories
    find_package(OpenCV REQUIRED)

    #MNN 
    # 向构建系统中添加.h的搜索路径
    set(MNN_INCLUDE_DIR ${MNN_INCLUDE_DIR})#MNN_INCLUDE_DIR 是release_android.sh在cmake阶段用—D进行指定的
    include_directories(${MNN_INCLUDE_DIR})
    
    #向构建系统中添加.so的搜索路径
    set(MNN_LIB_DIR ${MNN_DIR})#MNN_DIR 是release_android.sh在cmake阶段用—D进行指定的
    link_directories(${MNN_LIB_DIR})


    #生成动态链接库 名称为 LIB_NAME 源文件包括 SRC 和 JNI
    add_library(${LIB_NAME} SHARED  ${SRC} ${ANDROID_JNI})

    # 将指定的库链接到目标 ${LIB_NAME} 中。具体链接的库包括:
    # - OpenCV 库:通过变量 ${OpenCV_LIBS} 引用
    # - 日志库:通过变量 ${log-lib} 引用以及标准日志库 log
    # - MNN 库:使用 --whole-archive 选项将 MNN 库中的所有符号都包含进来,避免符号剥离导致的问题
    
    target_link_libraries(${LIB_NAME} ${OpenCV_LIBS} ${log-lib}  log -Wl,--whole-archive MNN -Wl,--no-whole-archive)

    # 如果增加示例代码,则编译示例代码
    if (BUILD_SAMPLES)
        message("[${LIB_NAME}]Build Samples demo")
        # inner sample
        add_subdirectory(samples)

    endif ()

else ()
    message("[${LIB_NAME}]Build Linux")
    #mnn
    set(MNN_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/MNN/include)
    set(MNN_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/MNN/linux/lib/)

    #opencv
    set(OpenCV_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/opencv/linux/include)
    set(OpenCV_LIB_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/opencv/linux/lib)


endif ()

if (NOT ANDROID)
    # mnn
    include_directories(${MNN_INCLUDE_DIR})
    link_directories(${MNN_LIB_DIR})
    
    # opencv
    include_directories(${OpenCV_INCLUDE_DIRS})
    link_directories(${OpenCV_LIB_DIRS})


    if (BUILD_SAMPLES)
        message("[${LIB_NAME}]Build Samples demo")
        # inner sample
       add_subdirectory(samples)

    endif ()

    if (BUILD_SHARE)
        message("[CmakeTest]Build Shared Library: ${LIB_NAME}")
        
        # build share library
        add_library(${LIB_NAME} SHARED ${SRC} )
        target_link_libraries(${LIB_NAME}  opencv_world MNN)

    endif ()
endif ()

# 路径位置打印
message("---------------------------------------------")
message(OpenCV_INCLUDE_DIRS=${OpenCV_INCLUDE_DIRS})
message(OpenCV_LIB_DIRS=${OpenCV_LIB_DIRS})
message(MNN_INCLUDE_DIR=${MNN_INCLUDE_DIR})
message(MNN_LIB_DIR=${MNN_LIB_DIR})
message("---------------------------------------------")

执行脚本编译

#!/bin/bash

set -ex
#编译andriod时候修改opencv和androidNDK对应路径

RELEASE_HOME=$(cd $(dirname $0)/../..; pwd)

# opencv 和 androidNDK 对应路径
export OPENCV_DIR=${RELEASE_HOME}/3rdparty/opencv-4.5.3-android-contrib-sdk/sdk/native/jni
export ANDROID_NDK=/home/chen/Downloads/android-ndk-r25c

# 编译路径
BUILD_DIR=${RELEASE_HOME}/build_android/release

# 清理编译目录
[[ -d ${BUILD_DIR} ]] && rm -r ${BUILD_DIR}

build() {
    arch=$1
    NDK_API_LEVEL=$2
    # 创建架构特定的构建目录
    mkdir -p ${BUILD_DIR}/${arch}
    # 进入构建目录
    pushd ${BUILD_DIR}/${arch}
    # 调用 cmake 进行配置
    # -G "Unix Makefiles" 指定生成的构建系统类型为Unix Makefiles
    # -DCMAKE_BUILD_TYPE=Release 指定构建类型为Release 优化性能并禁用调试信息
    # -DCMAKE_TOOLCHAIN_FILE=${NDK_HOME}/build/cmake/android.toolchain.cmake \ 执行工具链文件,用于配置Androd NDK交叉编译环境
    # -DANDROID_TOOLCHAIN=clang 设置 Android 工具链为 Clang 编译器
    # -DANDROID_ABI=${arch} 指定目标架构(如 arm64-v8a 或 armeabi-v7a)
    # -DANDROID_NATIVE_API_LEVEL=${NDK_API_LEVEL} 设置 Android API 级别(如 2124)
    # -DANDROID_STL=c++_static 指定使用静态链接的 C++ 标准库。
    # -DBUILD_CUDA=OFF 禁用 CUDA 支持。
    # -DOpenCV_DIR=${OPENCV_DIR} 指定 OpenCV 的安装路径
    # -DBUILD_SAMPLES=ON 启用样例代码的构建。
    # -DMNN_DIR=${RELEASE_HOME}/3rdparty/MNN/android/${arch}
    # -DMNN_INCLUDE_DIR=${RELEASE_HOME}/3rdparty/MNN/include


    cmake ${RELEASE_HOME} \
        -G "Unix Makefiles" \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \
        -DANDROID_TOOLCHAIN=clang \
        -DANDROID_ABI=${arch} \
        -DANDROID_NATIVE_API_LEVEL=${NDK_API_LEVEL} \
        -DANDROID_STL=c++_static \
        -DBUILD_CUDA=OFF \
        -DOpenCV_DIR=${OPENCV_DIR} \
        -DBUILD_SAMPLES=ON \
        -DMNN_DIR=${RELEASE_HOME}/3rdparty/MNN/android/${arch} \
        -DMNN_INCLUDE_DIR=${RELEASE_HOME}/3rdparty/MNN/include \
        -DSOLEXCV_INCLUDE_DIRS=${RELEASE_HOME}/3rdparty/solexcv/android/include \
        -DSOLEXCV_INCLUDE_LIBS=${RELEASE_HOME}/3rdparty/solexcv/android/${arch}


    # 使用 make -j$(nproc) 执行多线程构建,生成目标库或可执行文件。
    make -j$(nproc) ${LIB_NAME}
    # 列出指定目录下的所有文件,并删除除了so文件以外的其他文件。
    ls ${BUILD_DIR}/${arch}| grep -v so| xargs rm -r
    # 返回到命令执行前的目录
    popd
}

build arm64-v8a 24
build armeabi-v7a 24

执行当前 sh文件