VR/AR动手玩(三):Android使用jni调用opencv
来源:互联网 发布:中国历史故事软件 编辑:程序博客网 时间:2024/06/05 17:16
虽然网上有不少资料,但大多是老版本的,新版本Android Studio对ndk作了改进,采用cmake构建,简单了许多。下面示例基于Android Studio2.3版本,关于如何编译请参考上一篇http://blog.csdn.net/efanlee/article/details/69944267
一、新建工程
1、创建CvNative工程,注意要选上include C++ support
在定制Activity时,我习惯把下面这个勾去掉,直接采用简单的Activity基类。
C++设置页面中,将下面两项勾选,否则构建会失败。当然,后面再改CMakeLists.txt也是等效的。
2、不同于java方式需要导入一个module,jni方式只要把libs目录拷到项目里。将编译好的opencv4android/sdk/native/libs目录整个复制到CvNative\app\src\main目录下,并改名为jniLibs
3、将opencv4android/sdk/native/jni/include目录整个复制到CvNative下
4、修改app的gradle,在defaultConfig内添加ndk的配置(ndk部分),主要是指定编译哪些abi:
externalNativeBuild { cmake { cppFlags "-frtti -fexceptions" } ndk { abiFilters "armeabi", "armeabi-v7a" }}
注意刚才勾选的rtti和exceptions在这里体现了,如果需要,还可以加上-std=gnu++11。修改完后,同步一下配置。
5、修改CMakeLists.txt文件,如果是Android视图,应该在External Build Files下。
这一步主要是为了增加头文件路径,以及库文件。
在cmake_minimum_required之后,增加下面配置:
include_directories(../include)
由于CMakeLists.txt文件在CvNative/app目录下,所以../include表示CvNative/include目录,即上面步骤3中复制的include路径——如果你把include目录复制到其他地方,请修改这里。
继续添加
set(libs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")add_library(libopencv_java3 SHARED IMPORTED )set_target_properties(libopencv_ java3 PROPERTIESIMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libopencv_ java3.so")add_library(libopencv_world STATIC IMPORTED )set_target_properties(libopencv_world PROPERTIES IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libopencv_world.a")add_library(libopencv_contrib_world STATIC IMPORTED )set_target_properties(libopencv_contrib_world PROPERTIESIMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libopencv_contrib_world.a")
解释一下,首先是设置libs变量为jniLibs的路径,然后增加libopencv_java3、libopencv_world和libopencv_contrib_world三个库,并指定类型分别为共享和静态库,指定位置为相应的.so及.a文件。
这里有两点需要注意:
首先,第一行设置了libs变量,虽然这里看似可以设置任意的目录存放库文件,但实际使用中,必须使用src/main/jniLibs。否则虽然能够编译通过,但在运行时会提示找不到库。
其次,经过测试,libopencv_java3.so是必须的。缺少libopencv_java3.so支持时,链接时会报错,提示找不到carotene引用。网上说需要libtegra_hal.a,这个文件在3rdparty里,但单独引用此库并不能解决问题(链接问题能解决,但运行时缺少so)。目前测试只有提供libopencv_java3才管用。
但之前编译时提到,如果勾了world选项,就不会编译java组件。为解决这个问题,可以每种架构编译两遍,一次是用world选项,复制libopencv_world.a及libopencv_contrib_world.a两个文件,第二遍不选world,只复制libopencv_java3.so。但这样非常麻烦,时间和空间都要增加1倍,而且与PC版不同,Android版的world编译并不能有效减少占用空间,仅仅是方便在CMakeLists.txt少写几句配置(如果不用world,则每个lib库文件都要add一遍),是否值得,还要权衡一下。
接下来在原来target_link_libraries(通常在文件末尾)的基础上,添加刚才add进去的三个lib文件libopencv_java3、libopencv_world及libopencv_contrib_world:
target_link_libraries( # Specifies the target library. native-lib -ljnigraphics libopencv_java3 libopencv_world libopencv_contrib_world # Links the target library to the log library # included in the NDK. ${log-lib} )
新增的-ljnigraphics是为了后面引用AndroidBitmap添加的,否则会报链接错误。
修改完后,同步一下配置。如果一切顺利,同步配置后能看到BUILD SUCCESSFUL提示。
补充说明:
后来看了一下资料,其实应该使用find_library而不是add_library。因为add_library主要是用于本项目的代码生成library,而find_library才是使用准备好的库(通常是第三方)。但是测试时使用find_library一直未能成功,后来找到似乎是Android做过改动,要加一个CMAKE_FIND_ROOT_PATH_BOTH参数,例如:
find_library(OPENCV_JAVA NAMES libopencv_java3.so PATHS "${libs}/${ANDROID_ABI}" CMAKE_FIND_ROOT_PATH_BOTH)
然而libopencv_java3.so的问题虽然解决了,但引入libopencv_world.a的时候又会报找不到carotene库的错误,只好放弃。
二、编写测试代码
上面步骤自动生成的程序模板,自带一个native-lib.cpp,并编译为native-lib模块(参考CMakeLists.txt配置)。在MainActivity中,通过stringFromJNI方法获取”hello world”字符串并显示出来。
参考这种方式,稍作修改,测试opencv的调用。
1、 修改MainActivity.onCreate()如下:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ImageView imgView = new ImageView(this); setContentView(imgView); int w = 100, h = 100; Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); int[] pixels = new int[w * h]; bmp.getPixels(pixels, 0, w, 0, 0, w, h); int[] result = cvprocess(pixels, w, h); Bitmap resultBmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); resultBmp.setPixels(result, 0, w, 0, 0, w, h); imgView.setImageBitmap(resultBmp);}
2、 紧跟着stringFromJNI()后面,添加新的jni方法:
private native int[] cvprocess(int[] pixels, int w, int h);
并在native-lib.cpp实现如下:
extern "C"JNIEXPORT jintArray JNICALLJava_efan_cvnative2_MainActivity_cvprocess(JNIEnv *env, jobject instance, jintArray pixels_, jint w, jint h) { jint *pixels = env->GetIntArrayElements(pixels_, NULL); cv::Mat img(w, h, CV_8UC4, pixels); cv::circle(img, cv::Point(w/2, h/2), w * 0.4, cv::Scalar(255, 0, 0, 255)); jintArray result = env->NewIntArray(w * h); env->SetIntArrayRegion(result, 0, w * h, pixels); env->ReleaseIntArrayElements(pixels_, pixels, 0); return result;}
3、 编译运行,可以看到屏幕上出现一个蓝色的小圆。
三、效率对比
从刚才简单的测试代码来看,每次显示需要创建两个Bitmap对象,在java层需要new int[w * h]数组,JNI层还要再用env->NewIntArray(w*h),效率是比较低的。
实际使用时,从Camera.PreviewCallback接口获取byte[] data数据,要先转成YUV格式,再转成BGR,进行处理,最后转成RGBA格式的bmp,耗时更多。未经优化时,效率甚至比不上直接使用opencv封装好的java接口(最终也是调用JNI)。
于是参考了opencv自带java封装的实现(在Utils.matToBitmap方法),发现调用了AndroidBitmap_lockPixels和unlockPixels,可以节省一些内存的申请。修改代码如下:
1、 java部分(实现Camera.PreviewCallback的接口)
public void onPreviewFrame(byte[] data, Camera camera) { Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); jniProcess(data, bmp); imgView.setImageBitmap(bmp);}
2、 JNI部分
extern "C"JNIEXPORT void JNICALLJava_com_example_efanlee_cvnative_CameraActivity_jniProcess(JNIEnv * env, jobject instance, jbyteArray buf, jobject bitmap, jint width, jint height) { jbyte *cbuf = env->GetByteArrayElements(buf, NULL); cv::Mat yuv(height + height / 2, width, CV_8UC1, cbuf); cv::Mat bgr; cv::cvtColor(yuv, bgr, CV_YUV2BGR_NV21); //转成BGR //TODO 处理bgr void *pixels; AndroidBitmap_lockPixels(env, bitmap, &pixels); cv::Mat result(height, width, CV_8UC4, pixels); cv::cvtColor(bgr, result, CV_BGR2RGBA); AndroidBitmap_unlockPixels(env, bitmap); env->ReleaseByteArrayElements(buf, cbuf, 0);}
以轮廓、背景、keypoint三种场景下进行不严谨的对比测试,JNI比java接口有小幅度提升(测试手机是我的烂红米)。
这点提升很难令人满意,可能还是有一些优化没有做好(比如中间转换过程太多)。从理论上分析,应该是中间步骤越多,JNI提升越明显,可能需要测试混合场景才能体现出来。
但不管怎样,帧数实在太低,如果换成高配手机,显然无法实现廉价的方案,下一步要试一下树莓派。
参考资料:
http://blog.csdn.net/martin20150405/article/details/53284442
http://blog.csdn.net/sbsujjbcy/article/details/49520791(这里用的是make的方式,资料有点旧,仅参考)
- VR/AR动手玩(三):Android使用jni调用opencv
- VR/AR动手玩(一):在Android应用中集成opencv
- VR/AR动手玩(二):编译opencv_contrib模块
- Android JNI调用(三)
- Android JNI调用(三)
- Android 调用opencv jni方式
- Android studio 初步使用JNI(三)简单使用JNI
- VR/AR...
- Android程序员看世界--AR/VR(1)
- Android驱动使用JNI调用
- Android驱动使用JNI调用
- 在Android中使用JNI调用Opencv本地代码 配置方式 边缘检测 范例代码
- Windows下使用android NDK(JNI)调用OpenCV本地代码——流程梳理
- Android Studio中使用JNI调用OpenCV本地代码Canny 边缘检测
- 基于标识的AR的OpenCV实现(三)
- jni中调用opencv
- 陀螺仪(可用于AR/VR)
- 深剖VR,AR和MR三者之间关系
- [leetcode]: 455. Assign Cookies
- 4815: [Cqoi2017]小Q的表格
- poj1163——The Triangle(简单dp)
- Java-try_catch_finally中带有return语句的执行顺序
- Android 面试知识库
- VR/AR动手玩(三):Android使用jni调用opencv
- Linux信号——Sleep函数的模拟实现(利用alarm,pause,sigsuspend函数)
- HDU 1069 Monkey and Banana dp类型:最长上升子序列
- Android 打包签名DexIndexOverflowException错误解决
- 千万数据的分库分表(一)
- 文章标题
- 模型分解(瓦解、溶解...)Shader
- Java中的匿名内部类
- prim算法