按照使用语言角度,在Android下使用OpenCV有以下几种方式:

完全使用Java语言

完全使用C++语言

混合使用Java和C++语言

下面分别讲一下怎么做,并说明这样做可能需要注意的问题。

完全使用Java语言

开始我是希望完全用Java语言开发的(也就是使用OpenCV4Android),主要好处是:

开发效率高,我指的是Java比C++生产代码的速度要快一些,至少在我的团队是这样

编译环境搭建比较简单,尤其是通过async initialization的情况下

但是,有得就有失,OpenCV是C++写的,虽然OpenCV组织也推出了供开发者使用的OpenCV4Android,但并不是所有的OpenCV C++功能都能在OpenCV4Android中找到。这也挺好理解的,人家是先写出C++的功能,然后再考虑在OpenCV4Android中实现。

所以,使用OpenCV4Android的问题是有些功能OpenCV里有,但它没有。

另外一个问题是,需要在架构上避免频繁的JNI操作,这样对性能不利。如下图:


实际上,OpenCV4Android只是在原来C++本地库的基础上做了个Java/JNI的包装(wrapper)。

在开发上,又有两种加载OpenCV4Android的方式:

async initialization,这是官方文档里推荐的,你都不需要在自己Android项目里加入OpenCV的本地库,OpenCV提供了一个可在Google Play上下载的App,你只需要将OpenCV4Android的Android库项目(libraray project)加入到你Android项目中即可,官方文档说明了这个配置过程

static initialization,官方文档建议只在开发阶段使用,和前者的不同在于,你需要将相关的本地库(so文件)部署到项目中来

如果我来选择,我可能希望正式环境下也使用static initialization的方式,原因是:

国内Android设备使用Google Play的并不多,虽然可以指定下载链接,但是增加了用户操作,不方便

可能存在这样的问题,通过Google Play下载的版本和开发使用的OpenCV4Android Java库版本不匹配

TODO: 提供一个这样的github项目示例

完全使用C++

在前一阶段的开发,我们使用的是这种方式。

希望能尽量减少混合编程带来的复杂性。借图说明:


但是,项目中业务的复杂性,需要考虑面向对象和它们的生命周期,这样可以让架构简明,而使用纯粹的C++解决方案,Java无法管理到OpenCV的对象,比如矩阵(Mat)。我们对C++的控制能力有限,担心如果设计复杂的对象和生命周期的结构,会带来错误和调试上的效率问题。

TODO: 提供github项目示例

混合使用Java和C++语言

使用上,用矩阵(Mat)的操作举例。

Java创建矩阵

创建矩阵,将Android api中的Bitmap转为矩阵:

1

2Mat bitmapMat = new Mat();

Utils.bitmapToMat(bitmap, bitmapMat);

如何将Java创建矩阵对象传递到C++

将矩阵对象传递给C++代码,实际上Mat就是C++在内存中生成的对象,Java只需传递该对象的地址就行了。

1long address=bitmapMat.getNativeObjAddr();

然后Java本地方法类似这样:

1private native long generateHistogram(long bitmap);

那么通过javah生成的头文件中方法类似这样:

1

2JNIEXPORT void JNICALL Java_marshal_opencvproto_Detector_detect(JNIEnv *env,

jobject thiz, jlong matPtr);

在C++代码中这样生成Mat对象:

1Mat *bitmpaMat = (Mat*) bitmapMatPtr;ddd

如何将C++的矩阵对象传递给Java

要根据生命周期来分析,可以有两种方式:

可在Java中创建,然后传递到C++中做处理,不需要作为返回值,因为引用的是同一个对象

在C++中创建,然后作为方法的返回值

对于后者,在C++中只需要做一次强制转型就可将矩阵对象指针传递回Java:

1

2

3

4

5Mat *hist = new Mat();

// ...

return (jlong) hist;

回传到Java需要做的处理:

1

2long address = generateHistogram(bitmapMat.nativeObj);

Mat histogram = new Mat(address);

即,直接用地址的long值创建矩阵对象即可。

C++和Java混合编程需要注意的问题

环境搭建

环境搭建中的步骤类似官方文档中static initialization的步骤。

要使用文档中说的有JNI部分的方式,而不是简单的复制本地库到libs目录下。所以重点是配置Android.mk文件,以下是我的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

OPENCV_INSTALL_MODULES:=on

include /opt/OpenCV-2.4.6-android-sdk/sdk/native/jni/OpenCV.mk

LOCAL_MODULE := Detector

LOCAL_SRC_FILES := Detector.cpp

LOCAL_LDLIBS :=-llog

include $(BUILD_SHARED_LIBRARY)

如何加载OpenCV库

加载OpenCV库,如果按照官方文档,也就是这样:

1OpenCVLoader.initDebug();

会有一个error日志,不过不影响使用:

1OpenCV error: Cannot load info library for OpenCV

查了一下OpenCV源代码,是加载opencv_info.so出错造成的,我没来得及看如何在Android.mk中设置将它加进来。

但可以直接这样直白的加载OpenCV库:

1system.loadLibrary("opencv_java");

就没有问题了。

使用OpenCVLoader.initDebug()加载的好处是,日志会显示很详细的OpenCV加载信息,便于你排查问题。

未解决的问题

目前有一个,就是怎样在Java中保存矩阵对象数据,以后还能根据这个数据恢复矩阵对象。

OpenCV提供了写入文件的办法,使用YAML格式,但是在Android环境下,未提供相应的wrapper API。