继上文两篇文章,我们已经实现了 针对 MNIST 数据集的模型训练和推理
今天我们来看一下 怎么将推理代码编译成动态库so 供Android项目调用
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 文件 将所有文件都加入到 SRC 中
file(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 级别(如 21 或 24)
# -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文件