原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。 http://underthehood.blog.51cto.com/2531780/670169

看了网上的很多教程和官方http://opencv.willowgarage.com/wiki/Android提供的如何在Android上使用OpenCV的教程,照着一步一步的做最后总有些问题,不是APK安装失败就是运行时突然报错退出。和同学一起摸索了一段时间后,终于弄成功,在这里做一个总结。最关键的问题是项目中各个文件夹和文件的位置要放置正确,而且目标机器的CPU架构要设置正确,下面是配置的详细过程。

一、Android开发环境

1.Sun JDK 6

访问http://www.oracle.com/technetwork/java/javase/downloads/index.html这里并且安装好JDK

注意:不要使用OpenJDK,Android SDK支持Sun JDK

2.Android SDK

访问http://developer.android.com/sdk/index.html获取android sdk,如果选择的是Windows安装文件,则你还需要安装32bit JRE。

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

访问http://www.eclipse.org/downloads/下载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

访问http://developer.android.com/sdk/ndk/index.html下载最新的Android NDK,是一个ZIP解压包,只需解压到某个路径即可,例如"F:\android-ndk-r6b-windows\android-ndk-r6b",再把这个路径添加到系统的环境变量PATH中。

6. Cygwin

访问http://cygwin.com/index.html下载最新的Cygwin,最好安装全部的Cygwin组件。假设安装在"C:\cygwin"下,将"C:\cygwin\bin"添加到系统环境变量PATH中,为了方便的在命令行下调用Android NDK,找到"C:\cygwin\home\(你的用户名)"这个目录,打开文件".bash_profile",在文件的最下面加上下面两行内容:

NDK=/cygdrive/f/android-ndk-r6b-windows/android-ndk-r6b

export NDK

这样便可以在命令行中以 "$NDK/ndk-build" 这种形式调用NDK了。

二、OpenCV

1.首先下载在http://sourceforge.net/projects/opencvlibrary/files/opencv-android/2.3.1/已经预编译好的opencv包。

2.把下载好的包解压到某个路径上(最好不要带空格),例如"F:\OpenCV-2.3.1-android-bin"

三、如何在Android程序中使用OpenCV

有两种方式(重点讲后面一种):

1.使用OpenCV Java API。

OpenCV安装路径"F:\OpenCV-2.3.1-android-bin"下有两个文件夹,如下图

wps_clip_image-10099

将文件夹"OpenCV-2.3.1"拷贝到你的Eclipse工作空间所在的目录,也就是在你的项目的上一级目录中,然后导入到工作空间中,在Package Explorer中选择你的项目,单机右键在弹出菜单中选择Properties,然后在弹出的Properties窗口中左侧选择Android,然后点击右下方的Add按钮,选择OpenCV-2.3.1并点击OK,如下图:

如何在Android中使用OpenCV_第1张图片

此时,展开你的项目树,你可以看到新加了一个OpenCV-2.3.1_src目录,如下图,那么就是正确添加了OpenCV Java API,否则就是你放置OpenCV-2.3.1的目录路径不正确。

如何在Android中使用OpenCV_第2张图片

然后就可以在你的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中使用OpenCV_第3张图片

然后新建一个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同一级目录中,如下图所示:

如何在Android中使用OpenCV_第4张图片

(上面这个各个文件夹和文件的放置很重要,因为OpenCV-2.3.1下的OpenCV.mk中有很多相对路径的指定,如果不是这样放置,在NDK生成动态库时可能会报文件或文件夹无法找到的错误)

选择Package Explorer中你的项目,右键选择new->folder,新建一个名为jni的文件夹,用来存放你的c/c++代码。

然后把res->layout下的main.xml的内容改为下面所示:

            
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <Buttonandroid:layout_height="wrap_content"
  8. android:layout_width="fill_parent"
  9. android:id="@+id/btnNDK"
  10. android:text="使用C++OpenCV进行处理"/>
  11. <Buttonandroid:layout_height="wrap_content"
  12. android:layout_width="fill_parent"
  13. android:id="@+id/btnRestore"
  14. android:text="还原"/>
  15. <ImageViewandroid:id="@+id/ImageView01"
  16. android:layout_width="fill_parent"
  17. android:layout_height="fill_parent"/>
  18. </LinearLayout>

上面的代码就是一个线性布局里面包含2个按钮加上一个显示图像的ImageView

在文件夹src下的com.testopencv.haveimgfun包中新建一个类用于包装使用了opencv c++代码的动态库的导出函数,类名为LibImgFun。

Eclipse会为你创建一个新的文件LibImgFun.java,将里面的内容改为:

            
  1. packagecom.testopencv.haveimgfun;
  2. publicclassLibImgFun{
  3. static{
  4. System.loadLibrary("ImgFun");
  5. }
  6. /**
  7. *@paramwidththecurrentviewwidth
  8. *@paramheightthecurrentviewheight
  9. */
  10. publicstaticnativeint[]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<jni.h>
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<opencv2/opencv.hpp>
  5. usingnamespacecv;
  6. extern"C"{
  7. JNIEXPORTjintArrayJNICALLJava_com_testopencv_haveimgfun_LibImgFun_ImgFun(
  8. JNIEnv*env,jobjectobj,jintArraybuf,intw,inth);
  9. JNIEXPORTjintArrayJNICALLJava_com_testopencv_haveimgfun_LibImgFun_ImgFun(
  10. JNIEnv*env,jobjectobj,jintArraybuf,intw,inth){
  11. jint*cbuf;
  12. cbuf=env->GetIntArrayElements(buf,false);
  13. if(cbuf==NULL)
  14. {
  15. return0;
  16. }
  17. Matmyimg(h,w,CV_8UC4,(unsignedchar*)cbuf);
  18. for(intj=0;j<myimg.rows/2;j++)
  19. {
  20. myimg.row(j).setTo(Scalar(0,0,0,0));
  21. }
  22. intsize=w*h;
  23. jintArrayresult=env->NewIntArray(size);
  24. env->SetIntArrayRegion(result,0,size,cbuf);
  25. env->ReleaseIntArrayElements(buf,cbuf,0);
  26. returnresult;
  27. }
  28. }

上面的代码中#include <jni.h>是必须要包含的头文件,#include <opencv2/opencv.hpp>是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的内容改为如下所示:

            
  1. LOCAL_PATH:=$(callmy-dir)
  2. include$(CLEAR_VARS)
  3. include../includeOpenCV.mk
  4. ifeq("$(wildcard$(OPENCV_MK_PATH))","")
  5. #trytoloadOpenCV.mkfromdefaultinstalllocation
  6. include$(TOOLCHAIN_PREBUILT_ROOT)/user/share/OpenCV/OpenCV.mk
  7. else
  8. include$(OPENCV_MK_PATH)
  9. endif
  10. LOCAL_MODULE:=ImgFun
  11. LOCAL_SRC_FILES:=ImgFun.cpp
  12. include$(BUILD_SHARED_LIBRARY)

Application.mk的内容改为如下所示:

            
  1. APP_STL:=gnustl_static
  2. APP_CPPFLAGS:=-frtti-fexceptions
  3. APP_ABI:=armeabiarmeabi-v7a

其中APP_ABI指定的是目标平台的CPU架构。(经过很多测试,android2.2必须指定为armeabi,android2.2以上的使用armeabi-v7a,如果没有设置对,很有可能安装到android虚拟机失败,当然你同时如上面写上也是可以的)

上面的步骤完成后,就可以使用NDK生成动态库了,打开cygwin,cd到项目目录下,如下图所示:

如何在Android中使用OpenCV_第5张图片

输入$NDK/ndk-build命令,开始创建动态库。成功的话如下图所示。

如何在Android中使用OpenCV_第6张图片

这时候刷新Eclipse的Package Explorer会出现两个新的文件夹obj和libs。

现在,只剩最后一步完成这个测试程序。

将一张图片,例如"lena.jpg"放到项目res->drawable-hdpi目录中并刷新该目录。

然后将HaveImgFun.java的内容改为下面所示:

            
  1. packagecom.testopencv.haveimgfun;
  2. importandroid.app.Activity;
  3. importandroid.graphics.Bitmap;
  4. importandroid.graphics.Bitmap.Config;
  5. importandroid.graphics.drawable.BitmapDrawable;
  6. importandroid.os.Bundle;
  7. importandroid.widget.Button;
  8. importandroid.view.View;
  9. importandroid.widget.ImageView;
  10. publicclassHaveImgFunextendsActivity{
  11. /**Calledwhentheactivityisfirstcreated.*/
  12. ImageViewimgView;
  13. ButtonbtnNDK,btnRestore;
  14. @Override
  15. publicvoidonCreate(BundlesavedInstanceState){
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.main);
  18. this.setTitle("使用NDK转换灰度图");
  19. btnRestore=(Button)this.findViewById(R.id.btnRestore);
  20. btnRestore.setOnClickListener(newClickEvent());
  21. btnNDK=(Button)this.findViewById(R.id.btnNDK);
  22. btnNDK.setOnClickListener(newClickEvent());
  23. imgView=(ImageView)this.findViewById(R.id.ImageView01);
  24. Bitmapimg=((BitmapDrawable)getResources().getDrawable(R.drawable.lena)).getBitmap();
  25. imgView.setImageBitmap(img);
  26. }
  27. classClickEventimplementsView.OnClickListener{
  28. publicvoidonClick(Viewv){
  29. if(v==btnNDK)
  30. {

  31. longcurrent=System.currentTimeMillis();
  32. Bitmapimg1=((BitmapDrawable)getResources().getDrawable(R.drawable.lena)).getBitmap();
  33. intw=img1.getWidth(),h=img1.getHeight();
  34. int[]pix=newint[w*h];
  35. img1.getPixels(pix,0,w,0,0,w,h);

  36. int[]resultInt=LibImgFun.ImgFun(pix,w,h);
  37. BitmapresultImg=Bitmap.createBitmap(w,h,Config.RGB_565);
  38. resultImg.setPixels(resultInt,0,w,0,0,w,h);
  39. longperformance=System.currentTimeMillis()-current;
  40. imgView.setImageBitmap(resultImg);
  41. HaveImgFun.this.setTitle("w:"+String.valueOf(img1.getWidth())+",h:"+String.valueOf(img1.getHeight())
  42. +"NDK耗时"+String.valueOf(performance)+"毫秒");
  43. }
  44. elseif(v==btnRestore)
  45. {
  46. Bitmapimg2=((BitmapDrawable)getResources().getDrawable(R.drawable.lena)).getBitmap();
  47. imgView.setImageBitmap(img2);
  48. HaveImgFun.this.setTitle("使用OpenCV进行图像处理");
  49. }
  50. }
  51. }
  52. }

点击全部保存,OK,现在可以选择一个Android虚拟机运行看一下效果,配置好Run Configuration然后点击Run,得到下面的结果:

如何在Android中使用OpenCV_第7张图片

点击使用C++ OpenCV进行处理,得到下面的结果:

如何在Android中使用OpenCV_第8张图片

本文出自 “UnderTheHood” 博客,请务必保留此出处http://underthehood.blog.51cto.com/2531780/670169

更多相关文章

  1. 关于Android初级逻辑思考(续)——android文件夹讲解
  2. Android入门:使用Android自带媒体库读取MP3文件
  3. Android Studio 文件类型图标
  4. Android Studio中配置代码和资源的路径
  5. Android的NDK开发(5)————Android JNI层实现文件的read、writ
  6. Android studio 3.1.3 无法获取pom文件
  7. android各个文件分析

随机推荐

  1. 从android1.1到android2.2
  2. 【android】图片放错文件夹引起显示大小
  3. android window类解析
  4. Android实现应用程序换肤解决方案(二)Demos
  5. 编程回忆之Android回忆(Android标题栏的去
  6. android Manifest.xml全局配置文件中根标
  7. Android7.0、8.0应用内安装apk的问题
  8. Android(安卓)自定义时间选择器
  9. Android中给View设置阴影的三种方式
  10. 【Android应用开发】-(7)安全性设计(完整版