|
| 1 | +# 在 Android 中通过 JNI 使用 FastDeploy C++ SDK |
| 2 | +本文档将以PicoDet为例,讲解如何通过JNI,将FastDeploy中的模型封装到Android中进行调用。阅读本文档,您至少需要了解C++、Java、JNI以及Android的基础知识。如果您主要关注如何在Java层如何调用FastDeploy的API,则可以不阅读本文档。 |
| 3 | + |
| 4 | +## 目录 |
| 5 | +- [新建Java类并定义native API](#Java) |
| 6 | +- [Android Studio 生成JNI函数定义](#JNI) |
| 7 | +- [在C++层实现JNI函数](#CPP) |
| 8 | +- [编写CMakeLists.txt及配置build.gradle](#CMakeAndGradle) |
| 9 | +- [更多FastDeploy Android 使用案例](#Examples) |
| 10 | + |
| 11 | +## 新建Java类并定义native API |
| 12 | +<div id="Java"></div> |
| 13 | + |
| 14 | +```java |
| 15 | +public class PicoDet { |
| 16 | + protected long mNativeModelContext = 0; // Context from native. |
| 17 | + protected boolean mInitialized = false; |
| 18 | + // ... |
| 19 | + // Bind predictor from native context. |
| 20 | + private static native long bindNative(String modelFile, |
| 21 | + String paramsFile, |
| 22 | + String configFile, |
| 23 | + int cpuNumThread, |
| 24 | + boolean enableLiteFp16, |
| 25 | + int litePowerMode, |
| 26 | + String liteOptimizedModelDir, |
| 27 | + boolean enableRecordTimeOfRuntime, |
| 28 | + String labelFile); |
| 29 | + |
| 30 | + // Call prediction from native context. |
| 31 | + private static native long predictNative(long nativeModelContext, |
| 32 | + Bitmap ARGB8888Bitmap, |
| 33 | + boolean saved, |
| 34 | + String savedImagePath, |
| 35 | + float scoreThreshold, |
| 36 | + boolean rendering); |
| 37 | + |
| 38 | + // Release buffers allocated in native context. |
| 39 | + private static native boolean releaseNative(long nativeModelContext); |
| 40 | + |
| 41 | + // Initializes at the beginning. |
| 42 | + static { |
| 43 | + FastDeployInitializer.init(); |
| 44 | + } |
| 45 | +} |
| 46 | +``` |
| 47 | +这些被标记为native的接口是需要通过JNI的方式实现,并在Java层供PicoDet类调用。完整的PicoDet Java代码请参考 [PicoDet.java](../../../examples/vision/detection/paddledetection/android/app/src/main/java/com/baidu/paddle/fastdeploy/vision/detection/PicoDet.java) 。各个函数说明如下: |
| 48 | +- `bindNative`: C++层初始化模型资源,如果成功初始化,则返回指向该模型的指针(long类型),否则返回0指针 |
| 49 | +- `predictNative`: 通过已经初始化好的模型指针,在C++层执行预测代码,如果预测成功则返回指向预测结果的指针,否则返回0指针。注意,该结果指针在当次预测使用完之后需要释放,具体操作请参考 [PicoDet.java](../../../examples/vision/detection/paddledetection/android/app/src/main/java/com/baidu/paddle/fastdeploy/vision/detection/PicoDet.java) 中的predict函数。 |
| 50 | +- `releaseNative`: 根据传入的模型指针,在C++层释放模型资源。 |
| 51 | + |
| 52 | +## Android Studio 生成JNI函数定义 |
| 53 | +<div id="JNI"></div> |
| 54 | + |
| 55 | +Android Studio 生成 JNI 函数定义: 鼠标停留在Java中定义的native函数上,Android Studio 便会提示是否要创建JNI函数定义;这里,我们把JNI函数定义创建在一个事先创建好的c++文件`picodet_jni.cc`上; |
| 56 | + |
| 57 | +- 使用Android Studio创建JNI函数定义: |
| 58 | + |
| 59 | + |
| 60 | +- 将JNI函数定义创建在picodet_jni.cc上: |
| 61 | + |
| 62 | + |
| 63 | +- 创建的JNI函数定义如下: |
| 64 | + |
| 65 | + |
| 66 | +其他native函数对应的JNI函数定义的创建和此流程一样。 |
| 67 | + |
| 68 | +## 在C++层实现JNI函数 |
| 69 | +<div id="CPP"></div> |
| 70 | + |
| 71 | +以下为PicoDet JNI层实现的示例,相关的辅助函数不在此处赘述,完整的C++代码请参考 [android/app/src/main/cpp](../../../examples/vision/detection/paddledetection/android/app/src/main/cpp/). |
| 72 | +```C++ |
| 73 | +#include <jni.h> // NOLINT |
| 74 | +#include "fastdeploy_jni.h" // NOLINT |
| 75 | + |
| 76 | +#ifdef __cplusplus |
| 77 | +extern "C" { |
| 78 | +#endif |
| 79 | + |
| 80 | +// 绑定C++层的模型 |
| 81 | +JNIEXPORT jlong JNICALL |
| 82 | +Java_com_baidu_paddle_fastdeploy_vision_detection_PicoDet_bindNative( |
| 83 | + JNIEnv *env, jclass clazz, jstring model_file, jstring params_file, |
| 84 | + jstring config_file, jint cpu_num_thread, jboolean enable_lite_fp16, |
| 85 | + jint lite_power_mode, jstring lite_optimized_model_dir, |
| 86 | + jboolean enable_record_time_of_runtime, jstring label_file) { |
| 87 | + std::string c_model_file = fastdeploy::jni::ConvertTo<std::string>(env, model_file); |
| 88 | + std::string c_params_file = fastdeploy::jni::ConvertTo<std::string>(env, params_file); |
| 89 | + std::string c_config_file = astdeploy::jni::ConvertTo<std::string>(env, config_file); |
| 90 | + std::string c_label_file = fastdeploy::jni::ConvertTo<std::string>(env, label_file); |
| 91 | + std::string c_lite_optimized_model_dir = fastdeploy::jni::ConvertTo<std::string>(env, lite_optimized_model_dir); |
| 92 | + auto c_cpu_num_thread = static_cast<int>(cpu_num_thread); |
| 93 | + auto c_enable_lite_fp16 = static_cast<bool>(enable_lite_fp16); |
| 94 | + auto c_lite_power_mode = static_cast<fastdeploy::LitePowerMode>(lite_power_mode); |
| 95 | + fastdeploy::RuntimeOption c_option; |
| 96 | + c_option.UseCpu(); |
| 97 | + c_option.UseLiteBackend(); |
| 98 | + c_option.SetCpuThreadNum(c_cpu_num_thread); |
| 99 | + c_option.SetLitePowerMode(c_lite_power_mode); |
| 100 | + c_option.SetLiteOptimizedModelDir(c_lite_optimized_model_dir); |
| 101 | + if (c_enable_lite_fp16) { |
| 102 | + c_option.EnableLiteFP16(); |
| 103 | + } |
| 104 | + // 如果您实现的是其他模型,比如PPYOLOE,请注意修改此处绑定的C++类型 |
| 105 | + auto c_model_ptr = new fastdeploy::vision::detection::PicoDet( |
| 106 | + c_model_file, c_params_file, c_config_file, c_option); |
| 107 | + // Enable record Runtime time costs. |
| 108 | + if (enable_record_time_of_runtime) { |
| 109 | + c_model_ptr->EnableRecordTimeOfRuntime(); |
| 110 | + } |
| 111 | + // Load detection labels if label path is not empty. |
| 112 | + if ((!fastdeploy::jni::AssetsLoaderUtils::IsDetectionLabelsLoaded()) && |
| 113 | + (!c_label_file.empty())) { |
| 114 | + fastdeploy::jni::AssetsLoaderUtils::LoadDetectionLabels(c_label_file); |
| 115 | + } |
| 116 | + // WARN: need to release manually in Java ! |
| 117 | + return reinterpret_cast<jlong>(c_model_ptr); // native model context |
| 118 | +} |
| 119 | + |
| 120 | +// 通过传入的模型指针在C++层进行预测 |
| 121 | +JNIEXPORT jlong JNICALL |
| 122 | +Java_com_baidu_paddle_fastdeploy_vision_detection_PicoDet_predictNative( |
| 123 | + JNIEnv *env, jclass clazz, jlong native_model_context, |
| 124 | + jobject argb8888_bitmap, jboolean saved, jstring saved_image_path, |
| 125 | + jfloat score_threshold, jboolean rendering) { |
| 126 | + if (native_model_context == 0) { |
| 127 | + return 0; |
| 128 | + } |
| 129 | + cv::Mat c_bgr; |
| 130 | + if (!fastdeploy::jni::ARGB888Bitmap2BGR(env, argb8888_bitmap, &c_bgr)) { |
| 131 | + return 0; |
| 132 | + } |
| 133 | + auto c_model_ptr = reinterpret_cast<fastdeploy::vision::detection::PicoDet *>( |
| 134 | + native_model_context); |
| 135 | + auto c_result_ptr = new fastdeploy::vision::DetectionResult(); |
| 136 | + t = fastdeploy::jni::GetCurrentTime(); |
| 137 | + if (!c_model_ptr->Predict(&c_bgr, c_result_ptr)) { |
| 138 | + delete c_result_ptr; |
| 139 | + return 0; |
| 140 | + } |
| 141 | + // ... |
| 142 | + return reinterpret_cast<jlong>(c_result_ptr); // native result context |
| 143 | +} |
| 144 | + |
| 145 | +// 在C++层释放模型资源 |
| 146 | +JNIEXPORT jboolean JNICALL |
| 147 | +Java_com_baidu_paddle_fastdeploy_vision_detection_PicoDet_releaseNative( |
| 148 | + JNIEnv *env, jclass clazz, jlong native_model_context) { |
| 149 | + if (native_model_context == 0) { |
| 150 | + return JNI_FALSE; |
| 151 | + } |
| 152 | + auto c_model_ptr = reinterpret_cast<fastdeploy::vision::detection::PicoDet *>( |
| 153 | + native_model_context); |
| 154 | + // ... |
| 155 | + delete c_model_ptr; |
| 156 | + return JNI_TRUE; |
| 157 | +} |
| 158 | + |
| 159 | +#ifdef __cplusplus |
| 160 | +} |
| 161 | +#endif |
| 162 | +``` |
| 163 | +## 编写CMakeLists.txt及配置build.gradle |
| 164 | +<div id="CMakeAndGradle"></div> |
| 165 | +
|
| 166 | +实现好的JNI代码,需要被编译成so库,才能被Java调用,为实现该目的,需要在build.gradle中添加JNI项目支持,并编写对应的CMakeLists.txt。 |
| 167 | +- build.gradle中配置NDK、CMake以及Android ABI |
| 168 | +```java |
| 169 | +android { |
| 170 | + defaultConfig { |
| 171 | + // 省略其他配置 ... |
| 172 | + externalNativeBuild { |
| 173 | + cmake { |
| 174 | + arguments '-DANDROID_PLATFORM=android-21', '-DANDROID_STL=c++_shared', "-DANDROID_TOOLCHAIN=clang" |
| 175 | + abiFilters 'armeabi-v7a', 'arm64-v8a' |
| 176 | + cppFlags "-std=c++11" |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + // 省略其他配置 ... |
| 181 | + externalNativeBuild { |
| 182 | + cmake { |
| 183 | + path file('src/main/cpp/CMakeLists.txt') |
| 184 | + version '3.10.2' |
| 185 | + } |
| 186 | + } |
| 187 | + ndkVersion '20.1.5948944' |
| 188 | +} |
| 189 | +``` |
| 190 | +- 编写CMakeLists.txt示例 |
| 191 | +```cmake |
| 192 | +cmake_minimum_required(VERSION 3.10.2) |
| 193 | +project("fastdeploy_jni") |
| 194 | +
|
| 195 | +set(FastDeploy_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/fastdeploy-android-0.4.0-shared") |
| 196 | +
|
| 197 | +find_package(FastDeploy REQUIRED) |
| 198 | +
|
| 199 | +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) |
| 200 | +include_directories(${FastDeploy_INCLUDE_DIRS}) |
| 201 | +
|
| 202 | +add_library( |
| 203 | + fastdeploy_jni |
| 204 | + SHARED |
| 205 | + utils_jni.cc |
| 206 | + bitmap_jni.cc |
| 207 | + vision/results_jni.cc |
| 208 | + vision/visualize_jni.cc |
| 209 | + vision/detection/picodet_jni.cc |
| 210 | + vision/classification/paddleclas_model_jni.cc) |
| 211 | +
|
| 212 | +find_library(log-lib log) |
| 213 | +
|
| 214 | +target_link_libraries( |
| 215 | + # Specifies the target library. |
| 216 | + fastdeploy_jni |
| 217 | + jnigraphics |
| 218 | + ${FASTDEPLOY_LIBS} |
| 219 | + GLESv2 |
| 220 | + EGL |
| 221 | + ${log-lib} |
| 222 | +) |
| 223 | +``` |
| 224 | +完整的工程示例,请参考 [android/app/src/main/cpp/CMakelists.txt](../../../examples/vision/detection/paddledetection/android/app/src/main/cpp/) 以及 [android/app/build.gradle](../../../examples/vision/detection/paddledetection/android/app/build.gradle). |
| 225 | + |
| 226 | +## 更多FastDeploy Android 使用案例 |
| 227 | +<div id="Examples"></div> |
| 228 | + |
| 229 | +更多FastDeploy Android 使用案例请参考以下文档: |
| 230 | +- [图像分类Android使用文档](../../../examples/vision/classification/paddleclas/android/README.md) |
| 231 | +- [目标检测Android使用文档](../../../examples/vision/detection/paddledetection/android/README.md) |
0 commit comments