Android 使用OpenCV的三种方式(Android Studio)

http://blog.csdn.net/sbsujjbcy/article/details/49520791

Android 使用OpenCV的三种方式(Android Studio)

其实最早接触OpenCV是很久很久之前的事了,大概在2013年的5,6月份,当时还是个菜逼(虽然现在也是个菜逼),在那一段时间,学了一段时间的android(并不算学,一个月都不到),之后再也没接触android,而是一直在接触java web。那次接触OpenCV是因为一个学长的毕业设计,这次接触OpenCV是因为自己的毕业设计。2013年那年技术太菜,ndk环境都搭不好,当初还是eclipse环境,一直按照网上的教程去搭,下什么cygwin,简直就是个坑,网上的文章转来转去,都是过时的。后来一个机会看到了google官方的一个文档,就像发现了新大陆一样,发现ndk环境根本不需要装cygwin,装了你就坑了,装这个东西有好多G呢,时间浪费不说,简直误人子弟啊。后来在那年7月写下一篇博客

NDK开发环境

这段时间在填自己毕业设计的坑,要用到OpenCV,首先得下载到sdk吧,这个从官网上下载就好了
OpenCV for Android

注意下载的是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。

本文下面的三种方式的内容参考自文章 OpenCV4Android释疑: 透析Android以JNI调OpenCV的三种方式(让OpenCVManager永不困扰)

本篇文章使用android studio作为开发环境,由于实验性的构建工具对ndk支持还不好,所以使用旧的构建方式,在原来写的一篇博客基础上修改即可android studio下ndk开发

这正式介绍三种方式之前,我们需要做一些前期准备。

首先新建一个项目,将OpenCV中sdk目录下的native目录拷到项目根目录

这里写图片描述

然后新建Jni目录

这里写图片描述

在里面新建两个文件

这里写图片描述

编辑gradle.properties文件,增加下面的属性使用旧版的ndk功能(不添加会使用实验性的ndk构建工具)

?
1
<code bash= "" class = "hljs" >android.useDeprecatedNdk= true </code>

在local.properties文件中配置ndk目录

?
1
<code class = "hljs" tex= "" >ndk.dir=D:\AndroidSDK\sdk\ndk-bundle</code>

编辑build.gradle,在android节点中增加下面的代码

?
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
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' data-snippet-id=ext.57cfe881f1b599f1cacdf54006a6556e data-snippet-saved= false data-csrftoken=jAbtS2Wo-dGpwcxcGt2vqP0mibIzN7nCKugQ data-codota-status=done><code class = "hljs" mel= "" >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' </code>

在之前新建的Application.mk中增加下面的内容

?
1
2
3
4
5
<code class = "hljs" makefile= "" >APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android- 8
</code>

在Android.mk中增加下面的内容

?
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
<code class = "hljs" ruby= "" >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 ........
ativejniOpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
 
LOCAL_MODULE := OpenCV
 
LOCAL_SRC_FILES :=
 
LOCAL_LDLIBS +=  -lm -llog
 
include $(BUILD_SHARED_LIBRARY)
</code>

这时候,使用gradle构建一下,如果能成功构建出so,说明配置没问题,如下图,点击as右侧的gradle展开,双击ndkBuild进行构建

这里写图片描述

这里写图片描述

下面开始讲第一种方法,纯jni层的代码,该方法基于上面的所有步骤,为静态链接库

声明java层的native方法

?
1
2
3
4
5
6
7
<code class = "hljs" cs= "" > public class OpenCVHelper {
     static {
         System.loadLibrary(OpenCV);
     }
     public static native int [] gray( int [] buf, int w, int h);
}
</code>

使用javah命令生成头文件,内容如下

?
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
/* 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
  data-snippet-id=ext.ba8837e741e5214b4467247cc79e58a3 data-snippet-saved= false data-csrftoken=jJMOrjOS-jHPgZkClm2cErR8IXlybhUpqg4E data-codota-status=done><code class = "hljs" vala= "" > /* 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
</jni.h></code>

新建cpp文件,实现对应的方法,就是灰度处理

?
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#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;
}
} data-snippet-id=ext.7031b99c9209dfeeef3168f2def15639 data-snippet-saved= false data-csrftoken=1DJbOl7m-UVQNbl8xQw8YX34eXWB5DaBgWq4 data-codota-status=done><code class = "hljs" perl= "" >#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;
}
}</opencv2></stdlib.h></stdio.h></code></opencv2></stdlib.h>

之后,需要将cpp文件编译进去,在Andorid.mk文件中加入

?
1
<code class = "hljs" fix= "" >LOCAL_SRC_FILES := cn_edu_zafu_opencv_OpenCVHelper.cpp</code>

然后在java层写个测试方法测试一下是否进行灰度化了

?
1
2
3
4
5
6
7
8
9
<code avrasm= "" class = "hljs" >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);</code>

运行效果如下,灰度化后的结果

这里写图片描述

上面的这种方法生成的so库的大小见下图,大约有1.4M左右

这里写图片描述

第二种方法也是纯jni的,但是是动态链接库,在第一种基础上,修改Android.mk文件为

?
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
<code class = "hljs" ruby= "" >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 ........
ativejniOpenCV.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)
</code>

注意上面的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层的测试方法

?
1
2
3
4
5
6
7
8
9
<code avrasm= "" class = "hljs" >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);</code>

注意使用OpenCVLoader.initDebug();进行初始化而不是使用OpenCVLoader.initAsync()

这种方法的特点是处理都在java层,不怎么会涉及jni层的代码,除非java层完成不了的工作会转移到jni层去。

三种方法各有各的优点,根据自己的情况进行选择。

如果c++特别好的,建议使用第一种方法 如果更习惯java代码的,并且java层的函数都能进行处理的,建议选择第三种方法 第二种方法建议在第三种方法不满足条件的情况下进行辅助使用,因为使用了第三种方法的前提是使用第二种方法的动态链接库。

最后附上源码

CSDN又抽了,正常之后有空补上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值