darknet源码是纯c写的,移植到安卓是可行,可能速度不会很快。ncnn是主要用于移动端部署的深度学习前向传播框架,做了很多优化。我们可以将darknet转为ncnn部署在安卓上,项目使用的版本是老版darknet2ncnn ,之后作者适配到了ncnn最新版 。
本项目地址
主要步骤:
编译安卓版ncnn
darknet模型转ncnn模型
添加相关源码,编写jni接口,编写cmake文件,编译so
编译安卓版ncnn 在linux上交叉编译对应版本的ncnn ,查看ncnn官方教程即可。注意使用与android studio相同版本的ndk。(设置android studio ndk:修改local.properties,ndk.dir=D:\softback\Android\android-ndk-r15c)
将编译好的libncnn.a添加到工程src/main/jniLibs/armeabi-v7a/
目录。
darknet转ncnn 在linux上编译darknet2ncnn ,按照步骤来即可。将darknet训练好的模型cfg和权重weight转为ncnn可读取的param和bin文件,连同标签文件一起放到src/mai/assets
目录下。
编译so 将darknet、ncnn、darknet2ncnn相关源文件和头文件按目录添加到src/main/cpp
目录下,目录结构如下:
1 2 3 4 5 6 7 8 9 10 ├─cpp ├─darknet │ ├─include │ └─src ├─darknet2ncnn │ ├─include │ └─src └─ncnn ├─include └─src
在darknet和darknet2ncnn package下new package,添加相关源码,但编译后android studio中该源码目录会显示在cpp目录下,只是显示问题,没啥影响,应该是AS的bug。
src/main/cpp
目录下添加yolov3-tiny-jni.cpp文件。
编写Cmakelists.txt,这个是重点,很多问题就是没有写好cmake文件引起的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 cmake_minimum_required(VERSION 2.8.10) set(CMAKE_BUILD_TYPE RELEASE) set(libs "${CMAKE_SOURCE_DIR}/src/main/jniLibs") include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/darknet2ncnn/include ${CMAKE_SOURCE_DIR}/src/main/cpp/ncnn/include ${CMAKE_SOURCE_DIR}/src/main/cpp/darknet/include ${CMAKE_SOURCE_DIR}/src/main/cpp/darknet2ncnn/src ${CMAKE_SOURCE_DIR}/src/main/cpp/ncnn/src) set(CMAKE_STATIC_LINKER_FLAGS "-lm -pthread -fopenmp -lstdc++") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -Wno-unused-result -Wfatal-errors -fPIC -fno-rtti -fno-exceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Ofast -Wno-unused-result -Wfatal-errors -fPIC -fno-rtti -fno-exceptions") add_library (libncnn STATIC IMPORTED) set_target_properties(libncnn PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libncnn.a) file(GLOB_RECURSE darknet_src ${CMAKE_SOURCE_DIR}/src/main/cpp/darknet/src/*.c) set(darknet2ncnn_dir ${CMAKE_SOURCE_DIR}/src/main/cpp/darknet2ncnn/src) set(darknet2ncnn_src ${darknet2ncnn_dir}/layer/darknet_activation.cpp ${darknet2ncnn_dir}/layer/darknet_shortcut.cpp ${darknet2ncnn_dir}/layer/yolov1_detection.cpp ${darknet2ncnn_dir}/layer/yolov3_detection.cpp ${darknet2ncnn_dir}/object_detection.cpp ${darknet2ncnn_dir}/register_darknet.cpp ${darknet2ncnn_dir}/darknet2ncnn.cpp) set(ncnn_src ${CMAKE_SOURCE_DIR}/src/main/cpp/ncnn/src) set(lib_src ${darknet_src} ${darknet2ncnn_src} ${CMAKE_SOURCE_DIR}/src/main/cpp/yolov3-tiny-jni.cpp) add_library( # Sets the name of the library. yolov3_tiny_jni # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ${lib_src}) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) target_link_libraries( # Specifies the target library. yolov3_tiny_jni libncnn jnigraphics # Links the target library to the log library # included in the NDK. ${log-lib})
make project编译成功,so生成位置:app\build\intermediates\cmake\debug\obj\armeabi-v7a\,然后就可以在java代码中调用接口进行检测了。
错误解决 darknet源码报错:compare.c:17:13 error: initializing 'network' (aka 'struct network') with an expression of incompatible type 'network *' (aka 'struct network *'); dereference with *.
修改compare.c,多处对应的指针类型和引用改一下就OK。
报错:undefined reference to ‘typeinfo for ncnn::Layer’,这个在arm-linux交叉编译时遇到过,添加-fno-rtti编译选项即可。
报错:fatal error: use of undeclared identifier ‘nullptr’,添加-std=c++11编译选项即可。
build apk报错:Cause: org.jetbrains.plugins.gradle.tooling.util.ModuleComponentIdentifierImpl.getModuleIdentifier()Lorg/gradle/api/artifacts/ModuleIdentifier; 更新android studio即可.
ex.extract返回-100,一般是没找到目标,这里有两种可能,一种是模型正确检测正确,测试图片本来就没目标,另一种情况就是模型有问题,不能正常检测到我们的目标。我反复检查了测试图片和模型都没问题,最后发现还是加载模型错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private String getPathFromAssets(String assetsFileName){ File f = new File(getCacheDir()+"/"+assetsFileName); if (!f.exists()) try { InputStream is = getAssets().open(assetsFileName); int size = is.available(); byte[] buffer = new byte[size]; is.read(buffer); is.close(); FileOutputStream fos = new FileOutputStream(f); fos.write(buffer); fos.close(); } catch (Exception e) { throw new RuntimeException(e); } return f.getPath(); }
之前的模型已经加载到缓存中了,后来我更换过一次模型,但是缓存没删除,还是之前的模型,所以一直检测不到目标,将if (!f.exists())
注释掉,每次初始化都重新写入缓存,最后成功检测到目标。
总结 经测验,在骁龙625上400ms左右,速度还是可以的。
其实也比较简单,主要就写了个cmake文件。参考以下两项目,感谢作者大佬。
参考 darknet2ncnn
安卓demo