zoukankan      html  css  js  c++  java
  • 【转载】如何在Android中使用OpenCV

    最近在看opencv的东西,突然想到,能不能再android手机上使用呢。。。百度到一个比较好的文章,转载如下

    ----------------------------------------------------------------------

    看了网上的很多教程和官方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"下有两个文件夹,如下图

    将文件夹"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"?> 
    2 <LinearLayout xmlns: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 <Button android:layout_height="wrap_content"
    8 android:layout_width="fill_parent"
    9 android:id="@+id/btnNDK"
    10 android:text="使用C++ OpenCV进行处理" />
    11 <Button android:layout_height="wrap_content"
    12 android:layout_width="fill_parent"
    13 android:id="@+id/btnRestore"
    14 android:text="还原" />
    15 <ImageView android: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 package com.testopencv.haveimgfun; 
    2 public class LibImgFun {
    3 static {
    4 System.loadLibrary("ImgFun");
    5 }
    6 /**
    7 * @param width the current view width
    8 * @param height the current view height
    9 */
    10 public static native int[] ImgFun(int[] buf, int w, int h);
    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 using namespace cv;
    6
    7 extern "C"
    8 {
    9 JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h);
    10 JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h){
    11
    12 jint *cbuf;
    13 cbuf = env->GetIntArrayElements(buf, false);
    14 if(cbuf == NULL)
    15 {
    16 return 0;
    17 }
    18
    19 Mat myimg(h, w, CV_8UC4, (unsigned char*)cbuf);
    20 for(int j=0;j<myimg.rows/2;j++)
    21 {
    22 myimg.row(j).setTo(Scalar(0,0,0,0));
    23 }
    24
    25 int size=w * h;
    26 jintArray result = env->NewIntArray(size);
    27 env->SetIntArrayRegion(result, 0, size, cbuf);
    28 env->ReleaseIntArrayElements(buf, cbuf, 0);
    29 return result;
    30 }
    31 }

    上面的代码中#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 := $(call my-dir)  
    2. include $(CLEAR_VARS)  
    3. include ../includeOpenCV.mk  
    4. ifeq ("$(wildcard $(OPENCV_MK_PATH))","" 
    5. #try to load OpenCV.mk from default install location  
    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:=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的内容改为下面所示:

     1 package com.testopencv.haveimgfun; 
    2
    3 import android.app.Activity;
    4 import android.graphics.Bitmap;
    5 import android.graphics.Bitmap.Config;
    6 import android.graphics.drawable.BitmapDrawable;
    7 import android.os.Bundle;
    8 import android.widget.Button;
    9 import android.view.View;
    10 import android.widget.ImageView;
    11
    12 public class HaveImgFun extends Activity{
    13 /** Called when the activity is first created. */
    14 ImageView imgView;
    15 Button btnNDK, btnRestore;
    16
    17 @Override
    18 public void onCreate(Bundle savedInstanceState) {
    19 super.onCreate(savedInstanceState);
    20 setContentView(R.layout.main);
    21
    22 this.setTitle("使用NDK转换灰度图");
    23 btnRestore=(Button)this.findViewById(R.id.btnRestore);
    24 btnRestore.setOnClickListener(new ClickEvent());
    25 btnNDK=(Button)this.findViewById(R.id.btnNDK);
    26 btnNDK.setOnClickListener(new ClickEvent());
    27 imgView=(ImageView)this.findViewById(R.id.ImageView01);
    28 Bitmap img=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
    29 imgView.setImageBitmap(img);
    30 }
    31
    32 class ClickEvent implements View.OnClickListener{
    33 public void onClick(View v){
    34 if(v == btnNDK){
    35 long current=System.currentTimeMillis();
    36 Bitmap img1=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
    37 int w=img1.getWidth(),h=img1.getHeight();
    38 int[] pix = new int[w * h];
    39 img1.getPixels(pix, 0, w, 0, 0, w, h);
    40 int[] resultInt=LibImgFun.ImgFun(pix, w, h);
    41 Bitmap resultImg=Bitmap.createBitmap(w, h, Config.RGB_565);
    42 resultImg.setPixels(resultInt, 0, w, 0, 0,w, h);
    43 long performance=System.currentTimeMillis()-current;
    44 imgView.setImageBitmap(resultImg);
    45 HaveImgFun.this.setTitle("w:"+String.valueOf(img1.getWidth())+",h:"+String.valueOf(img1.getHeight())+"NDK耗时"+String.valueOf(performance)+" 毫秒");
    46 } else if(v == btnRestore){
    47 Bitmap img2=((BitmapDrawable) getResources().getDrawable(R.drawable.lena)).getBitmap();
    48    imgView.setImageBitmap(img2);
    49 HaveImgFun.this.setTitle("使用OpenCV进行图像处理");
    50 }
    51 }
    52 }
    53 }



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

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

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

  • 相关阅读:
    Eclipse正确导入第三方project
    面试的基础_01字符串反向操作
    一个简单的实现了智能虚拟女友—图灵机器人
    Notepad++去除代码行号的几种方法
    fastjson将bean转成字符串时首字母变小写问题
    2015第34周二能收发邮件但不能打开网页解决方法
    2015第34周一
    2015第33周日
    2015第33周六
    构建自己的顾问团
  • 原文地址:https://www.cnblogs.com/ldr123/p/2359262.html
Copyright © 2011-2022 走看看