OpenCV4Android+JNI开发快速上手入门

ndroid  opencv  jni

目录[-]

  • 安卓开发环境与OpenCV的配置
  • 创建带OpenCV SDK的Android工程
  • 在Android项目中调用C++代码
  • 运行App
  • 最近尝试了一下在Android上试验简单的一些OpenCV算法,发现OpenCV4Android SDK非常好用,提供大部分常用的OpenCV功能的Java API。当然如果直接对图像像素进行操作的话Java会比较没有效率,这时可以对部分关键功能使用ndk和jni进行native的C++实现。有了这套SDK和简单的JNI接口,像我这样不懂安卓开发的人也可以在手机上尝试各种好玩的算法了。

    这个入门假定你已经比较熟悉OpenCV的使用,所以主要针对安卓工程的创建和配置。

    (本文为cvnote.info原创,转载请注明出处,同时欢迎关注新浪微博@cvnote)

    安卓开发环境与OpenCV的配置

    安卓的开发环境我用的是ADT Bundle(http://developer.android.com/sdk/installing/bundle.html),这是一套已经配置好的用于安卓开发的Android SDK及eclipse环境,下载之后放在一个目录直接运行eclipse即可,非常方便。

    OpenCV方面因为我们可能即要使用OpenCV4Android的Java API,又想要在JNI中用C++调OpenCV库实现一部分功能,所以OpenCV本身的C++库和OpenCV4Android SDK都要安装。

    OpenCV本身的库的安装网上资料很多,另外如果是linux的话也可以参考《Linux下OpenCV的自动安装脚本》。

    OpenCV4Android的安装比较简单,首先需到opencv.org上下载最新的 OpenCV-x.x.x-android-sdk.zip,解压后放在一个文件夹里。然后需要在ADT Bundle的Eclipse里导入这个SDK库。点击File‣Import,选择Existing Android Code Into Workspace。在下一步的Root Directory中选择存放OpenCV Android SDK的路径 <some_path>/OpenCV-x.x.x-android-sdk/sdk/ ,在下方的Project列表中选择OpenCV Library导入。

    此外因为要编译C++,还需要下载Android NDK,到安卓Developer网站Android NDK下载即可。

    (本文为cvnote.info原创,转载请注明出处,同时欢迎关注新浪微博@cvnote)

    创建带OpenCV SDK的Android工程

    这一节中的代码主要来自OpenCV官方教程《Android Development with OpenCV》,不过这个教程上只提供了代码片段,如果不想自己一段段copy代码的话,可以跳过这一节,直接git clone我写好的范例工程github.com/prclibo/OpenCVAndroidBoilerplate,按照README提示配置即可。

    创建工程步骤:

    1. 在Eclipse中点击File‣New‣Project,选择Android Application Project。
    2. 在下一步后设置项目名称,包名称等,这里我们令项目名称为OpenCVAndroidBoilerplate,包名称为com.example.opencvandroidboilerplate。
    3. 在Configure Project页面中勾选Create Activity。喜欢的话可以勾选Create custom launcher icon,在下一步后设置图标
    4. 在Create Activity页面选择Blank Activity。下一步后,Activity的名字和对应layout的名字就用默认的MainActivity和activity_main好了。
    5. 然后点Finish就创建了这个工程。
    6. 创建好工程之后需要导入OpenCV SDK到工程中,在Package Explorer中右键点击刚刚创建的项目,选择Properties。然后左边选Android,右边的Library中勾上OpenCV Library。如下图[在项目中导入OpenCV SDK库]
    OpenCV4Android+JNI开发快速上手入门_第1张图片

    在项目中导入OpenCV SDK库

    项目创建好了就可以添加代码了,首先修改res/layout/activity_main.xml,如下。代码可以参见:https://github.com/prclibo/OpenCVAndroidBoilerplate/blob/master/res/layout/activity_main.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <  LinearLayout  xmlns  :  android  =  "http://schemas.android.com/apk/res/android"
          xmlns  :  tools  =  "http://schemas.android.com/tools"
          xmlns  :  opencv  =  "http://schemas.android.com/apk/res-auto"
          android  :  layout_width  =  "match_parent"
          android  :  layout_height  =  "match_parent"  >
     
          <  org  .  opencv  .  android  .  JavaCameraView
              android  :  layout_width  =  "fill_parent"
              android  :  layout_height  =  "fill_parent"
              android  :  visibility  =  "gone"
              android  :  id  =  "@+id/CameraView"
              opencv  :  show_fps  =  "true"
              opencv  :  camera_id  =  "any"  /  >
     
    <  /  LinearLayout  >

    然后在AndroidManifest.xml中添加下面代码调用相机的权限,完整代码在这里(https://github.com/prclibo/OpenCVAndroidBoilerplate/blob/master/AndroidManifest.xml)

    1
    2
    3
    4
    5
    6
    <  uses  -  permission  android  :  name  =  "android.permission.CAMERA"  /  >
     
    <  uses  -  feature  android  :  name  =  "android.hardware.camera"  android  :  required  =  "false"  /  >
    <  uses  -  feature  android  :  name  =  "android.hardware.camera.autofocus"  android  :  required  =  "false" /  >
    <  uses  -  feature  android  :  name  =  "android.hardware.camera.front"  android  :  required  =  "false"  /  >
    <  uses  -  feature  android  :  name  =  "android.hardware.camera.front.autofocus"  android  :  required  = "false"  /  >

    下面修改MainActivity.java,添加OpenCV SDK对相机的控制,代码在这里:https://github.com/prclibo/OpenCVAndroidBoilerplate/blob/master/src/com/example/opencvandroidboilerplate/MainActivity.java

    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
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    package  com  .  example  .  opencvandroidboilerplate  ;
     
    import  org  .  opencv  .  android  .  BaseLoaderCallback  ;
    import  org  .  opencv  .  android  .  CameraBridgeViewBase  ;
    import  org  .  opencv  .  android  .  CameraBridgeViewBase  .  CvCameraViewFrame  ;
    import  org  .  opencv  .  android  .  CameraBridgeViewBase  .  CvCameraViewListener2  ;
    import  org  .  opencv  .  android  .  LoaderCallbackInterface  ;
    import  org  .  opencv  .  android  .  OpenCVLoader  ;
    import  org  .  opencv  .  core  .  Mat  ;
     
    import  android  .  os  .  Bundle  ;
    import  android  .  app  .  Activity  ;
    import  android  .  util  .  Log  ;
    import  android  .  view  .  Menu  ;
    import  android  .  view  .  SurfaceView  ;
    import  android  .  view  .  WindowManager  ;
     
    public  class  MainActivity  extends  Activity  implements  CvCameraViewListener2  {
          final  String  TAG  =  "Rectangle"  ;
          private  CameraBridgeViewBase  mOpenCvCameraView  ;
     
          private  BaseLoaderCallback  mLoaderCallback  =  new  BaseLoaderCallback  (  this  )  {
              @Override
              public  void  onManagerConnected  (  int  status  )  {
                  switch  (  status  )  {
                      case  LoaderCallbackInterface  .  SUCCESS  :
                      {
                          Log  .  i  (  TAG  ,  "OpenCV loaded successfully"  )  ;
                          System  .  loadLibrary  (  "process_frame"  )  ;
                        
                          mOpenCvCameraView  .  enableView  (  )  ;
                      }  break  ;
                      default  :
                      {
                          super  .  onManagerConnected  (  status  )  ;
                      }  break  ;
                  }
              }
          }  ;
     
          @Override
          public  void  onResume  (  )
          {
              super  .  onResume  (  )  ;
              OpenCVLoader  .  initAsync  (  OpenCVLoader  .  OPENCV_VERSION_2_4_6  ,  this  ,  mLoaderCallback )  ;
          }
        
          @Override
          public  void  onCreate  (  Bundle  savedInstanceState  )  {
              Log  .  i  (  TAG  ,  "called onCreate"  )  ;
              super  .  onCreate  (  savedInstanceState  )  ;
              getWindow  (  )  .  addFlags  (  WindowManager  .  LayoutParams  .  FLAG_KEEP_SCREEN_ON  )  ;
              setContentView  (  R  .  layout  .  activity_main  )  ;
              mOpenCvCameraView  =  (  CameraBridgeViewBase  )  findViewById  (  R  .  id  .  CameraView        )  ;
              mOpenCvCameraView  .  setVisibility  (  SurfaceView  .  VISIBLE  )  ;
              mOpenCvCameraView  .  setCvCameraViewListener  (  this  )  ;
          }
     
          @Override
          public  void  onPause  (  )
          {
              super  .  onPause  (  )  ;
              if  (  mOpenCvCameraView  !=  null  )
                  mOpenCvCameraView  .  disableView  (  )  ;
          }
     
          public  void  onDestroy  (  )  {
              super  .  onDestroy  (  )  ;
              if  (  mOpenCvCameraView  !=  null  )
                  mOpenCvCameraView  .  disableView  (  )  ;
          }
     
          public  void  onCameraViewStarted  (  int  width  ,  int  height  )  {
          }
     
          public  void  onCameraViewStopped  (  )  {
          }
     
          public  Mat  onCameraFrame  (  CvCameraViewFrame  inputFrame  )  {
              Mat  mat  =  new  Mat  (  )  ;
              Mat  input  =  inputFrame  .  rgba  (  )  ;
            
              processFrame  (  input  .  getNativeObjAddr  (  )  ,  mat  .  getNativeObjAddr  (  )  ,
                      input  .  height  (  )  ,  input  .  width  (  )  )  ;
              return  mat  ;
            
          }
     
          @Override
          public  boolean  onCreateOptionsMenu  (  Menu  menu  )  {
              // Inflate the menu; this adds items to the action bar if it is present.
              getMenuInflater  (  )  .  inflate  (  R  .  menu  .  main  ,  menu  )  ;
              return  true  ;
          }
     
          public  native  void  processFrame  (  long  matAddrInRGBA  ,  long  matAddrOutInRGBA  ,  int  height  , int  width  )  ;
    }

    函数 Mat onCameraFrame(CvCameraViewFrame inputFrame) 处理相机每帧读入的图像inputFrame ,同时返回一个 Mat 作为显示在手机界面上得图像。OpenCV4Android SDK中已经提供了对OpenCV各种函数的Java binding,比如在Java下的 Mat 类,可以像C++中一样方便调用OpenCV Java函数对 Mat 进行操作。如果纯写Java的话,上面的框架就足够进行简单的Android上得OpenCV开发了。

    (本文为cvnote.info原创,转载请注明出处,同时欢迎关注新浪微博@cvnote)

    在Android项目中调用C++代码

    虽然用OpenCV Java SDK可以实现大部分的OpenCV功能,但如果想写一些自定义的图像处理和视觉算法,用Java可能会非常没有效率。在这种情况下,可以选择将部分功能用C++实现,然后在Android程序中对其进行调用。这里介绍一种最基本的方法。

    在上面的MainActivity.java中,有一个native函数:

    1
    public  native  void  processFrame  (  long  matAddrInRGBA  ,  long  matAddrOutInRGBA  )  ;

    这个native关键字标记了这个函数将通过JNI使用C++实现。Java中得OpenCV Mat 类提供了一个 getNativeObjAddr() 成员函数,可以将 Mat 自己的地址(指针)以long的形式返回,这个地址可以用来将 Mat 作为参数传给native函数。

    首先如果Eclipse中上面的代码都正确创建了之后,项目文件夹会出现一个bin/文件夹。 打开一个terminal进入 <project_path>/bin/classes/

    执行命令 javah com.example.opencvandroidboilerplate.MainActivity ,注意要在bin/classes/目录下执行。发现生成了一个头文件 com_example_opencvandroidboilerplate_MainActivity.h 。把他移到 <project_path>/jni/ 下(需创建jni文件夹)。看到里面的内容就是自动生成了一个于native函数 processFrame() 对应的一个C函数声明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_example_opencvandroidboilerplate_MainActivity */
     
    #ifndef _Included_com_example_opencvandroidboilerplate_MainActivity
    #define _Included_com_example_opencvandroidboilerplate_MainActivity
    #ifdef __cplusplus
    extern  "C"  {
    #endif
    /*
    * Class:     com_example_opencvandroidboilerplate_MainActivity
    * Method:    processFrame
    * Signature: (JJ)V
    */
    JNIEXPORT  void  JNICALL  Java_com_example_opencvandroidboilerplate_MainActivity_processFrame
        (  JNIEnv  *  ,  jobject  ,  jlong  ,  jlong  )  ;
     
    #ifdef __cplusplus
    }
    #endif
    #endif

    现在有了头文件,要做得就是实现这个函数了。比如我们在里面调用一下Canny算子,创建一个process_frame.cpp 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <com_example_opencvandroidboilerplate_MainActivity.h>
    #include <opencv2/opencv.hpp>
     
    JNIEXPORT  void  JNICALL  Java_com_example_opencvandroidboilerplate_MainActivity_processFrame
        (  JNIEnv  *  ,  jobject  ,  jlong  addrInRGBA  ,  jlong  addrOut  )  {
          cv  ::  Mat  *  pMatInRGBA  =  (  cv  ::  Mat  *  )  addrInRGBA  ;
          cv  ::  Mat  *  pMatOut  =  (  cv  ::  Mat  *  )  addrOut  ;
          cv  ::  Mat  imageGray  ;
          cv  ::  cvtColor  (  *  pMatInRGBA  ,  imageGray  ,  CV_RGBA2GRAY  )  ;
          cv  ::  Canny  (  imageGray  ,  *  pMatOut  ,  30  ,  90  )  ;
    }

    看到代码中用了一个long到指针的强转。有了源文件还需要编写Android.mk和Application.mk作为ndk-build的makefile:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    LOCAL_PATH  :  =  $  (  call  my  -  dir  )
     
    include  $  (  CLEAR_VARS  )
     
    include  <  some_path  >  /  OpenCV  -  2.4.7  -  android  -  sdk  /  sdk  /  native  /  jni  /  OpenCV  .  mk
     
    LOCAL_MODULE        :  =  process_frame
    LOCAL_SRC_FILES  :  =  process_frame  .  cpp
    LOCAL_LDLIBS  +  =      -  llog  -  ldl
     
    include  $  (  BUILD_SHARED_LIBRARY  )
    1
    2
    3
    APP_STL  :  =  gnustl_static
    APP_CPPFLAGS  :  =  -  frtti  -  fexceptions
    APP_ABI  :  =  armeabi  -  v7a

    在Android.mk中需要把 <some_path>改为OpenCV4Android SDK的安装路径。另外LOCAL_MODULE := process_frame 中module的名字,与MainActivity中的System.loadLibrary("process_frame"); 一句中的名字对应。

    写好这两个文件之后,在jni/目录下执行 <mdk_path>/ndk-build (将 <ndk_path> 改为ndk的安装路径)。顺利的话,可以看到输出信息最后一行:

    1
    [  armeabi  -  v7a  ]  Install            :  libprocess_frame  .  so  =  >  libs  /  armeabi  -  v7a  / libprocess_frame  .  so

    说明已经编译成功了。

    运行App

    上面的步骤一切顺利的话,现在就可以运行App了。首先连接手机,点击Eclipse上得运行键。App安装成功并运行后会提示安装OpenCV Manager,这个App类似于为应用的OpenCV功能提供服务,按照提示安装后,程序即可运行啦。

    (本来还想尝试一下在Emulator上运行,不过没弄清楚为什么我的Emulator不能联网,因此没法安装OpenCV Manager,求达人指点。)

    (本文为cvnote.info原创,转载请注明出处,同时欢迎关注新浪微博@cvnote)

    android开发, JNI, OpenCV
    Address:   http://cvnote.info/opencv4androidjni-quick-tutorial/

    你可能感兴趣的:(OpenCV4Android+JNI开发快速上手入门)