最近在看opencv的东西,突然想到,能不能再android手机上使用呢。。。百度到一个比较好的文章,转载如下
----------------------------------------------------------------------
一、Android开发环境
注意:不要使用OpenJDK,Android SDK支持Sun JDK 2.Android SDK
3.Android SDK组件
l Android SDK Tools, revision 12或者更新
l SDK平台Android 2.2, API 8, revision 2(also known as Java API) 这是OpenCV Java API支持的最低平台,OpenCV发布默认为Android 2.2 4. Eclipse IDE和ADT plugin for Eclipse
打开Eclipse,选择Help->Install New Software菜单,但后点击Add按钮,在Add Repository对话框中的Name一栏输入'ADT Plugin',Location一栏输入https://dl-ssl.google.com/android/eclipse/,但后点击OK。在Available Software对话框中选中所有单选框,然后一路next直到finish为止,当安装ADT完毕后重启Eclipse即可。 5. Android NDK
NDK=/cygdrive/f/android-ndk-r6b-windows/android-ndk-r6b
export NDK
这样便可以在命令行中以 '$NDK/ndk-build' 这种形式调用NDK了。
二、OpenCV
2.把下载好的包解压到某个路径上(最好不要带空格),例如'F:\OpenCV-2.3.1-android-bin'
三、如何在Android程序中使用OpenCV
有两种方式(重点讲后面一种):
OpenCV安装路径'F:\OpenCV-2.3.1-android-bin'下有两个文件夹,如下图
将文件夹'OpenCV-2.3.1'拷贝到你的Eclipse工作空间所在的目录,也就是在你的项目的上一级目录中,然后导入到工作空间中,在Package Explorer中选择你的项目,单机右键在弹出菜单中选择Properties,然后在弹出的Properties窗口中左侧选择Android,然后点击右下方的Add按钮,选择OpenCV-2.3.1并点击OK,如下图:
此时,展开你的项目树,你可以看到新加了一个OpenCV-2.3.1_src目录,如下图,那么就是正确添加了OpenCV Java API,否则就是你放置OpenCV-2.3.1的目录路径不正确。 然后就可以在你的Java源文件中导入OpenCV的API包,并且使用OpenCV API了,OpenCV API的包的形式如下:
Org.opencv.(OpenCV模块名).(OpenCV类名)
例如:
Org.opencv.core.Mat
2.利用JNI编写C++ OpenCV代码,通过Android NDK创建动态库(.so) 新建一个工作空间,例如'TestOpenCV',在Window->Preferences中设置好Android SDK的路径,如下图所示。
然后新建一个Android项目,Build Target选择Android2.2,命名为'HaveImgFun',活动名改为HaveImgFun,Package name中填写com.testopencv.haveimgfun,最后点击finish。 如同使用OpenCV Java API那样,将OpenCV-2.3.1文件夹拷贝到与工作空间同一级目录中;另外,将'F:\OpenCV-2.3.1-android-bin\samples'下的includeOpenCV.mk文件拷贝到和项目HaveImgFun同一级目录中,如下图所示:
(上面这个各个文件夹和文件的放置很重要,因为OpenCV-2.3.1下的OpenCV.mk中有很多相对路径的指定,如果不是这样放置,在NDK生成动态库时可能会报文件或文件夹无法找到的错误)
选择Package Explorer中你的项目,右键选择new->folder,新建一个名为jni的文件夹,用来存放你的c/c++代码。 然后把res->layout下的main.xml的内容改为下面所示:
1<>xml version='1.0' encoding='utf-8'?>
2LinearLayout xmlns:android='http://schemas.android.com/apk/res/android'
3android:orientation='vertical'
4android:layout_width='fill_parent'
5android:layout_height='fill_parent'
6>
7Button android:layout_height='wrap_content'
8android:layout_width='fill_parent'
9android:id='@+id/btnNDK'
10android:text='使用C++ OpenCV进行处理'/> 11Button android:layout_height='wrap_content'
12android:layout_width='fill_parent'
13android:id='@+id/btnRestore'
14android:text='还原'/>
15ImageView android:id='@+id/ImageView01'
16android:layout_width='fill_parent'
17android:layout_height='fill_parent'/>
18LinearLayout>
上面的代码就是一个线性布局里面包含2个按钮加上一个显示图像的ImageView
在文件夹src下的com.testopencv.haveimgfun包中新建一个类用于包装使用了opencv c++代码的动态库的导出函数,类名为LibImgFun。 Eclipse会为你创建一个新的文件LibImgFun.java,将里面的内容改为:
1packagecom.testopencv.haveimgfun;
2publicclassLibImgFun {
3static{
4System.loadLibrary('ImgFun');
5}
6/**
7* @paramwidth the current view width
8* @paramheight the current view height
9*/
10publicstaticnativeint[] ImgFun(int[] buf, intw, inth);
11}
从上面的代码可以得知,我们的动态库名字应该为“libImgFun.so”,注意'public static native int[] ImgFun(int[] buf, int w, int h)'中的native关键字,表明这个函数来自native code。static表示这是一个静态函数,这样就可以直接用类名去调用。
在jni文件夹下建立一个'ImgFun.cpp'的文件,内容改为下面所示:
1#include
2#include
3#include
4#include
5usingnamespacecv;
6
7extern'C'
8{
9JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, intw, inth);
10JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, intw, inth){
11
12jint *cbuf;
13cbuf = env->GetIntArrayElements(buf, false);
14if(cbuf == NULL)
15{
16return0;
17}
18
19Mat myimg(h, w, CV_8UC4, (unsigned char*)cbuf);
20for(intj=0;j2;j++)
21{
22myimg.row(j).setTo(Scalar(0,0,0,0));
23}
24
25intsize=w * h;
26jintArray result = env->NewIntArray(size);
27env->SetIntArrayRegion(result, 0, size, cbuf);
28env->ReleaseIntArrayElements(buf, cbuf, 0);
29returnresult;
30}
31}
上面的代码中#include 是必须要包含的头文件,#include 是opencv要包含的头文件。
动态库要导出的函数如下声明:
JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h);
JNIEXPORT 和JNICALL是必须要加的关键字
jintArray就是int[],这里返回类型要么为空,要么为jni中定义的类型,事实上就是C\C++类型前面加上j,如果是数组,则在后面加上Array。
函数名的命名规则如下:
Java_(包路径)_(类名)_(函数名) (JNIEnv *env, jobject obj, 自己定义的参数...)
包路径中的'.'用'_'(下划线)代替,类名就是上面包装该动态库函数的类的名字,最后一个才是真正的函数名;JNIEnv *env和jobject obj这两个参数时必须的,用来调用JNI环境下的一些函数;后面就是你自己定义的参数。在这里,jintArray buf代表了传进来的图像的数据,int w是图像的宽,int h是图像的高。
这个函数的功能是将传进来的图像的上半部分涂成黑色。
然后再在jni下新建两个文件'Android.mk'文件和'Application.mk'文件,这两个文件事实上就是简单的Makefile文件。
其中将Android.mk的内容改为如下所示:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include ../includeOpenCV.mk
ifeq ('$(wildcard $(OPENCV_MK_PATH))','')
#try toload OpenCV.mk fromdefault install location
include $(TOOLCHAIN_PREBUILT_ROOT)/user/share/OpenCV/OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := ImgFun
LOCAL_SRC_FILES := ImgFun.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk的内容改为如下所示:
APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti -fexceptions
APP_ABI:=armeabi armeabi-v7a
其中APP_ABI指定的是目标平台的CPU架构。(经过很多测试,android2.2必须指定为armeabi,android2.2以上的使用armeabi-v7a,如果没有设置对,很有可能安装到android虚拟机失败,当然你同时如上面写上也是可以的) 上面的步骤完成后,就可以使用NDK生成动态库了,打开cygwin,cd到项目目录下,如下图所示:
输入$NDK/ndk-build命令,开始创建动态库。成功的话如下图所示。
这时候刷新Eclipse的Package Explorer会出现两个新的文件夹obj和libs。
现在,只剩最后一步完成这个测试程序。
将一张图片,例如'lena.jpg'放到项目res->drawable-hdpi目录中并刷新该目录。
然后将HaveImgFun.java的内容改为下面所示:
1packagecom.testopencv.haveimgfun;
2
3importandroid.app.Activity;
4importandroid.graphics.Bitmap;
5importandroid.graphics.Bitmap.Config;
6importandroid.graphics.drawable.BitmapDrawable;
7importandroid.os.Bundle;
8importandroid.widget.Button;
9importandroid.view.View;
10importandroid.widget.ImageView;
11
12publicclassHaveImgFun extendsActivity{
13/**Called when the activity is first created. */
14ImageView imgView;
15Button btnNDK, btnRestore;
16
17@Override
18publicvoidonCreate(Bundle savedInstanceState) {
19super.onCreate(savedInstanceState);
20setContentView(R.layout.main);
21
22this.setTitle('使用NDK转换灰度图');
23btnRestore=(Button)this.findViewById(R.id.btnRestore);
24btnRestore.setOnClickListener(newClickEvent());
25btnNDK=(Button)this.findViewById(R.id.btnNDK);
26btnNDK.setOnClickListener(newClickEvent());
27imgView=(ImageView)this.findViewById(R.id.ImageView01);
28Bitmap img=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
29imgView.setImageBitmap(img);
30}
31
32classClickEvent implementsView.OnClickListener{
33publicvoidonClick(View v){
34if(v == btnNDK){
35longcurrent=System.currentTimeMillis();
36Bitmap img1=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
37intw=img1.getWidth(),h=img1.getHeight();
38int[] pix = newint[w * h];
39img1.getPixels(pix, 0, w, 0, 0, w, h);
40int[] resultInt=LibImgFun.ImgFun(pix, w, h);
41Bitmap resultImg=Bitmap.createBitmap(w, h, Config.RGB_565);
42resultImg.setPixels(resultInt, 0, w, 0, 0,w, h);
43longperformance=System.currentTimeMillis()-current;
44imgView.setImageBitmap(resultImg);
45HaveImgFun.this.setTitle('w:'+String.valueOf(img1.getWidth())+',h:'+String.valueOf(img1.getHeight())+'NDK耗时'+String.valueOf(performance)+' 毫秒');
46} elseif(v == btnRestore){
47Bitmap img2=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
48imgView.setImageBitmap(img2);
49HaveImgFun.this.setTitle('使用OpenCV进行图像处理');
50}
51}
52}
53}
点击全部保存,OK,现在可以选择一个Android虚拟机运行看一下效果,配置好Run Configuration然后点击Run,得到下面的结果:
点击使用C++ OpenCV进行处理,得到下面的结果: