基于Android studio3.6的JNI教程之ncnn之目标检测ssd
16lz
2021-01-23
代码链接:
https://github.com/watersink/MobileNetSSD-linux-as
本代码可以在模拟器下进行跑。
环境:
Android studio 3.6
Sdk:android10 api 29
Ndk:r15c
Ncnn:20200226
Linux下的代码测试:
mkdir buildcd buildcmake ..make./ssd
效果:
Android下的开发:
(1)增加ncnn的依赖库.a(src/main/jniLibs)
增加ncnn的头文件include(src/main/cpp)
(2)增加ssd模型文件(src/main/assets)
和ssd模型的加密后的*.id.h文件(src/main/cpp)
(3)增加需要使用的图片(src/main/res/drawable)
(4)修改布局文件(src/main/res/layout/activity_main.xml)
(5)修改java部分代码(src/main/java)
增加MobileNetssd,主要实现初始化Init和检测Detect代码
package com.example.mobilenetssd;import android.graphics.Bitmap;/** * MobileNetssd的java接口,与本地c++代码相呼应 native为本地 此文件与 MobileNetssd.cpp相呼应 */public class MobileNetssd { public native boolean Init(byte[] param, byte[] bin); // 初始化函数 public native float[] Detect(Bitmap bitmap); // 检测函数 // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("MobileNetssd"); }}//修改MainActivity代码,主要onCreate函数中实现模型初始化,label文件读取,模型的检测,结果显示。 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { initMobileNetSSD();//初始化模型 Log.e("MainActivity", "initMobileNetSSD ok"); } catch (IOException e) { Log.e("MainActivity", "initMobileNetSSD error"); } readCacheLabelFromLocalFile();//初始化读取words.txt init_view();//检测+view画图}
(6)修改cpp部分代码(src/main/cpp)
MobileNetssd.h和MobileNetssd.cpp分别实现了MOBILENETSSD及其相关方法。
ssd_jni.cpp实现了jnl方式的c++方法。
MobileNetssd.h中的MOBILENETSSD类及其方法,
class MOBILENETSSD {public: MOBILENETSSD(string param_path, string bin_path);MOBILENETSSD(ncnn::Mat param_path, ncnn::Mat bin_path);ncnn::Mat detect(ncnn::Mat in); ~MOBILENETSSD();private: ncnn::Net net; const int target_size = 300; const float mean_vals[3] = {127.5f, 127.5f, 127.5f}; const float norm_vals[3] = {0.007843f, 0.007843f, 0.007843f};};
MobileNetssd.cpp中的具体实现,
MOBILENETSSD::MOBILENETSSD(string param_path, string bin_path) { const char *param_path_char = param_path.c_str(); const char *bin_path_char = bin_path.c_str(); int ret_param = net.load_param_bin(param_path_char); int ret_bin = net.load_model(bin_path_char);//std::cout<<"### "<
ssd_jni.cpp的具体实现,
static MOBILENETSSD *ncnn_net;extern "C"JNIEXPORT jboolean JNICALLJava_com_example_mobilenetssd_MobileNetssd_Init(JNIEnv *env, jobject thiz, jbyteArray param, jbyteArray bin) { // TODO: implement Init() ncnn::Mat ncnn_param; ncnn::Mat ncnn_bin; // init param { int len = env->GetArrayLength(param); ncnn_param.create(len, (size_t) 1u); env->GetByteArrayRegion(param, 0, len, (jbyte *) ncnn_param); } // init bin { int len = env->GetArrayLength(bin); ncnn_bin.create(len, (size_t) 1u); env->GetByteArrayRegion(bin, 0, len, (jbyte *) ncnn_bin); } ncnn_net = new MOBILENETSSD(ncnn_param,ncnn_bin); return JNI_TRUE;}extern "C"JNIEXPORT jfloatArray JNICALLJava_com_example_mobilenetssd_MobileNetssd_Detect(JNIEnv *env, jobject thiz, jobject bitmap) { // TODO: implement Detect() // ncnn from bitmap ncnn::Mat in; { AndroidBitmapInfo info; AndroidBitmap_getInfo(env, bitmap, &info); int width = info.width; int height = info.height; if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) return NULL; void* indata; AndroidBitmap_lockPixels(env, bitmap, &indata); // 把像素转换成data,并指定通道顺序 // 因为图像预处理每个网络层输入的数据格式不一样一般为300*300 128*128等等所以这类需要一个resize的操作可以在cpp中写,也可以是java读入图片时有个resize操作 //in = ncnn::Mat::from_pixels_resize((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2RGB, width, height,300,300); in = ncnn::Mat::from_pixels(static_cast(indata), ncnn::Mat::PIXEL_RGBA2RGB, width, height); // 下面一行为debug代码 __android_log_print(ANDROID_LOG_DEBUG, "MobilenetssdJniIn", "Mobilenetssd_predict_has_input1, in.w: %d; in.h: %d", in.w, in.h); //AndroidBitmap_unlockPixels(env, bitmap); } { ncnn::Mat out = ncnn_net->detect(in); int output_wsize = out.w; int output_hsize = out.h; //输出整理 jfloat *output[output_wsize * output_hsize]; // float类型 for(int i = 0; i< out.h; i++) { for (int j = 0; j < out.w; j++) { output[i*output_wsize + j] = &out.row(i)[j]; } } //建立float数组 长度为 output_wsize * output_hsize,如果只是ouput_size相当于只有一行的out的数据那就是一个object检测数据 jfloatArray jOutputData = env->NewFloatArray(output_wsize * output_hsize); if (jOutputData == nullptr) return nullptr; env->SetFloatArrayRegion(jOutputData, 0, output_wsize * output_hsize, reinterpret_cast(*output)); return jOutputData; }}
(7)CMakeLists修改(src/main/cpp/CMakeLists),加入ncnn路径
cmake_minimum_required(VERSION 3.4.1)#include头文件目录include_directories(include)#添加ncnn库#source directory源文件目录file(GLOB SSD_SRC *.h *.cpp)set(SSD_COMPILE_CODE ${SSD_SRC})add_library(libncnn STATIC IMPORTED )set_target_properties(libncnn PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libncnn.a)add_library( # Sets the name of the library. MobileNetssd ## 为生成.so的文字最好直接和.c名字一样,需要更改 # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ${SSD_COMPILE_CODE})##cpp文件的namefind_library( # Sets the name of the path variable. log-lib log )target_link_libraries( # Specifies the target library. MobileNetssd libncnn android jnigraphics # Links the target library to the log library # included in the NDK. ${log-lib} )
(8) build.gradle修改,增加ndk,cmake选项。
externalNativeBuild { cmake { arguments "-DANDROID_TOOLCHAIN=clang" cFlags "-fopenmp -O2 -fvisibility=hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math " cppFlags "-fopenmp -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math " arguments "-DANDROID_STL=c++_shared", "-DANDROID_CPP_FEATURES=rtti exceptions" cppFlags "" cppFlags "-std=c++11" cppFlags "-frtti" cppFlags "-fexceptions" } } ndk { abiFilters 'armeabi-v7a'// , 'arm64-v8a' //,'x86', 'x86_64', 'armeabi' stl "gnustl_static" }
(9)整体目录结构,
运行结果:
更多相关文章
- Android获取SD卡上图片和视频文件及其缩略图
- Android 代码修改按钮上的图片
- Android实现网络图片查看器和网页源码查看器
- Android 九宫格图片展示的实现
- 在Android中使用GIF图片
- Android进阶之代码应用技巧