其实最早接触 OpenCV 是很久很久之前的事了,大概在 2013 年的 5,6 月份,当时还是个菜逼(虽然现在也是个菜逼),在那一段时间,学了一段时间的 android(并不算学,一个月都不到),之后再也没接触 android,而是一直在接触 java web。那次接触 OpenCV 是因为一个学长的毕业设计,这次接触 OpenCV 是因为自己的毕业设计。2013 年那年技术太菜,ndk 环境都搭不好,当初还是 eclipse 环境,一直按照网上的教程去搭,下什么 cygwin,简直就是个坑,网上的文章转来转去,都是过时的。后来一个机会看到了 google 官方的一个文档,就像发现了新大陆一样,发现 ndk 环境根本不需要装 cygwin,装了你就坑了,装这个东西有好多 G 呢,时间浪费不说,简直误人子弟啊。后来在那年 7 月写下一篇博客
这段时间在填自己毕业设计的坑,要用到 OpenCV,首先得下载到 sdk 吧,这个从官网上下载就好了
注意下载的是 OpenCV for android。当前版本是 3.0
解压后,里面的内容如下
samples 目录下是样例代码,sdk 目录下是我们需要用到的 java 层和 jni 层的代码。apk 目录是 manager 的 apk 安装包
其实 OpenCV 最简单的使用方式是使用 manager,也就是使用 apk 目录下的安装包,安装对应的 apk,将 java 层代码导入,使用 OpenCVLoader.initAsync() 加载库,之后你就可以直接用 java 代码调用 Opencv 相关的功能了。
但是这种方式除了安装我们自己的 apk 还需要安装上面提到的 manager 的 apk,用户体验十分不好,不推荐使用,本文的三种方式将完全脱离这个 manager 的 apk。
本文下面的三种方式的内容参考自文章
本篇文章使用 android studio 作为开发环境,由于实验性的构建工具对 ndk 支持还不好,所以使用旧的构建方式,在原来写的一篇博客基础上修改即可
这正式介绍三种方式之前,我们需要做一些前期准备。
首先新建一个项目,将 OpenCV 中 sdk 目录下的 native 目录拷到项目根目录
然后新建 Jni 目录
在里面新建两个文件
编辑 gradle.properties 文件,增加下面的属性使用旧版的 ndk 功能(不添加会使用实验性的 ndk 构建工具)
- android.useDeprecatedNdk = true
在 local.properties 文件中配置 ndk 目录
- ndk.dir = D\: \\AndroidSDK\\sdk\\ndk - bundle
编辑 build.gradle,在 android 节点中增加下面的代码
- sourceSets.main.jni.srcDirs = []
- //禁止自带的ndk功能
- sourceSets.main.jniLibs.srcDirs = ['src/main/libs', 'src/main/jniLibs']
- //重定向so目录为src/main/libs和src/main/jniLibs,原来为src/main/jniLibs
- task ndkBuild(type: Exec, description: 'Compile JNI source with NDK') {
- Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) def ndkDir = properties.getProperty('ndk.dir') if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
- commandLine "$ndkDir/ndk-build.cmd",
- '-C',
- file('src/main/jni').absolutePath
- } else {
- commandLine "$ndkDir/ndk-build",
- '-C',
- file('src/main/jni').absolutePath
- }
- }
- tasks.withType(JavaCompile) {
- compileTask - >compileTask.dependsOn ndkBuild
- }
- task ndkClean(type: Exec, description: 'Clean NDK Binaries') {
- Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) def ndkDir = properties.getProperty('ndk.dir') if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
- commandLine "$ndkDir/ndk-build.cmd",
- 'clean',
- '-C',
- file('src/main/jni').absolutePath
- } else {
- commandLine "$ndkDir/ndk-build",
- 'clean',
- '-C',
- file('src/main/jni').absolutePath
- }
- }
- clean.dependsOn 'ndkClean'
在之前新建的 Application.mk 中增加下面的内容
- APP_STL: =gnustl_static APP_CPPFLAGS: =-frtti - fexceptions APP_ABI: =armeabi armeabi - v7a APP_PLATFORM: =android - 8
在 Android.mk 中增加下面的内容
- LOCAL_PATH: =$(call my - dir) include $(CLEAR_VARS) OpenCV_INSTALL_MODULES: =on OpenCV_CAMERA_MODULES: =off OPENCV_LIB_TYPE: =STATIC ifeq("$(wildcard $(OPENCV_MK_PATH))", "") include..\..\..\..\native\jni\OpenCV.mk
- else include $(OPENCV_MK_PATH) endif LOCAL_MODULE: =OpenCV LOCAL_SRC_FILES: =LOCAL_LDLIBS += -lm - llog include $(BUILD_SHARED_LIBRARY)
这时候,使用 gradle 构建一下,如果能成功构建出 so,说明配置没问题,如下图,点击 as 右侧的 gradle 展开,双击 ndkBuild 进行构建
下面开始讲第一种方法,纯 jni 层的代码,该方法基于上面的所有步骤,为静态链接库
声明 java 层的 native 方法
- public class OpenCVHelper {
- static {
- System.loadLibrary("OpenCV");
- }
- public static native int[] gray(int[] buf, int w, int h);
- }
使用 javah 命令生成头文件,内容如下
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include < jni.h >
- /* Header for class cn_edu_zafu_opencv_OpenCVHelper */
- #ifndef _Included_cn_edu_zafu_opencv_OpenCVHelper#define _Included_cn_edu_zafu_opencv_OpenCVHelper#ifdef __cplusplus extern "C" {#endif
- /*
- * Class: cn_edu_zafu_opencv_OpenCVHelper
- * Method: gray
- * Signature: ([III)[I
- */
- JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray(JNIEnv * , jclass, jintArray, jint, jint);#ifdef __cplusplus
- }#endif#endif
新建 cpp 文件,实现对应的方法,就是灰度处理
- #include "cn_edu_zafu_opencv_OpenCVHelper.h"#include < stdio.h > #include < stdlib.h > #include < opencv2 / opencv.hpp > using namespace cv;
- extern "C" {
- JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray(JNIEnv * env, jclass obj, jintArray buf, int w, int h);
- JNIEXPORT jintArray JNICALL Java_cn_edu_zafu_opencv_OpenCVHelper_gray(JNIEnv * env, jclass obj, jintArray buf, int w, int h) {
- jint * cbuf;
- cbuf = env - >GetIntArrayElements(buf, JNI_FALSE);
- if (cbuf == NULL) {
- return 0;
- }
- Mat imgData(h, w, CV_8UC4, (unsigned char * ) cbuf);
- uchar * ptr = imgData.ptr(0);
- for (int i = 0; i < w * h; i++) {
- //计算公式:Y(亮度) = 0.299*R + 0.587*G + 0.114*B
- //对于一个int四字节,其彩色值存储方式为:BGRA
- int grayScale = (int)(ptr[4 * i + 2] * 0.299 + ptr[4 * i + 1] * 0.587 + ptr[4 * i + 0] * 0.114);
- ptr[4 * i + 1] = grayScale;
- ptr[4 * i + 2] = grayScale;
- ptr[4 * i + 0] = grayScale;
- }
- int size = w * h;
- jintArray result = env - >NewIntArray(size);
- env - >SetIntArrayRegion(result, 0, size, cbuf);
- env - >ReleaseIntArrayElements(buf, cbuf, 0);
- return result;
- }
- }
之后,需要将 cpp 文件编译进去,在 Andorid.mk 文件中加入
- LOCAL_SRC_FILES: =cn_edu_zafu_opencv_OpenCVHelper.cpp
然后在 java 层写个测试方法测试一下是否进行灰度化了
- Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.ic)).getBitmap();
- int w = bitmap.getWidth(),
- h = bitmap.getHeight();
- int[] pix = new int[w * h];
- bitmap.getPixels(pix, 0, w, 0, 0, w, h);
- int[] resultPixes = OpenCVHelper.gray(pix, w, h);
- Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
- result.setPixels(resultPixes, 0, w, 0, 0, w, h);
- img.setImageBitmap(result);
运行效果如下,灰度化后的结果
上面的这种方法生成的 so 库的大小见下图,大约有 1.4M 左右
第二种方法也是纯 jni 的,但是是动态链接库,在第一种基础上,修改 Android.mk 文件为
- LOCAL_PATH: =$(call my - dir) include $(CLEAR_VARS) OpenCV_INSTALL_MODULES: =on OpenCV_CAMERA_MODULES: =off OPENCV_LIB_TYPE: =SHARED ifeq("$(wildcard $(OPENCV_MK_PATH))", "") include..\..\..\..\native\jni\OpenCV.mk
- else include $(OPENCV_MK_PATH) endif LOCAL_MODULE: =OpenCV LOCAL_SRC_FILES: =cn_edu_zafu_opencv_OpenCVHelper.cpp LOCAL_LDLIBS += -lm - llog include $(BUILD_SHARED_LIBRARY)
注意上面的 OPENCV_LIB_TYPE 属性的改动,从 STATIC 改为了 SHARED,这时候再用 ndkBuild 一下,你会发现会输出一些警告以及一部分红色的内容
生成的 so 库的大小为 310k,小了好几倍
这时候如果你直接取运行程序,会报错误
原因是我们使用的是动态库加载方式,还需要将依赖的 so 加进去,这个 so 就是图中的 libopencv_java3.so,他在我们的最开始加到项目里的 native 目录中
将它拷到我们的 jniLibs 目录中去,这里只拷贝 armeabi 和 armeabi-v7a 中的,至于其他的按需拷贝
这时候运行就不会报错了。
既然我们使用了动态链接库,那么我们同样也可以使用 java 层的接口,优点是 java 开发速度相对快一点。第三种方法在第二种方法基础上,使用纯 java 层代码进行处理。
在此之前,我们需要将 sdk 目录中的 java 代码拷到项目中去
但是 org.opencv.engine 包中是一个 aidl,我们需要将它剪贴到 aidl 目录中去,就像这样子
最后还有一个资源文件 attrs.xml,拷过来
build 一下项目,不出意外应该会报错,这时候找到该类,引入自己的 R 文件包就可以了
再次 build 应该就不会有什么问题了。
java 层的测试方法
- OpenCVLoader.initDebug();
- Mat rgbMat = new Mat();
- Mat grayMat = new Mat();
- Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic);
- Bitmap grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
- Utils.bitmapToMat(srcBitmap, rgbMat); //convert original bitmap to Mat, R G B.
- Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY); //rgbMat to gray grayMat
- Utils.matToBitmap(grayMat, grayBitmap); //convert mat to bitmap
- img.setImageBitmap(grayBitmap);
注意使用 OpenCVLoader.initDebug(); 进行初始化而不是使用 OpenCVLoader.initAsync()
这种方法的特点是处理都在 java 层,不怎么会涉及 jni 层的代码,除非 java 层完成不了的工作会转移到 jni 层去。
三种方法各有各的优点,根据自己的情况进行选择。
最后附上源码
来源: http://lib.csdn.net/article/android/43622