OpenCV4Android+JNI开发快速上手入门

最近尝试了一下在Android上试验简单的一些OpenCV算法,发现OpenCV4Android SDK很是好用,提供大部分经常使用的OpenCV功能的Java API。固然若是直接对图像像素进行操做的话Java会比较没有效率,这时能够对部分关键功能使用ndk和jni进行native的C++实现。有了这套SDK和简单的JNI接口,像我这样不懂安卓开发的人也能够在手机上尝试各类好玩的算法了。 html

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

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

安卓开发环境与OpenCV的配置

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

OpenCV方面由于咱们可能即要使用OpenCV4Android的Java API,又想要在JNI中用C++调OpenCV库实现一部分功能,因此OpenCV自己的C++库和OpenCV4Android SDK都要安装。 git

OpenCV自己的库的安装网上资料不少,另外若是是linux的话也能够参考《Linux下OpenCV的自动安装脚本》。 github

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下载便可。 app

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

建立带OpenCV SDK的Android工程

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

建立工程步骤:

  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库]
在项目中导入OpenCV SDK库

在项目中导入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开发JNIOpenCV
相关文章
相关标签/搜索