https://my.oschina.net/cvnote/blog/180661
最近尝试了一下在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提示配置即可。
创建工程步骤:
- 在Eclipse中点击File‣New‣Project,选择Android Application Project。
- 在下一步后设置项目名称,包名称等,这里我们令项目名称为OpenCVAndroidBoilerplate,包名称为com.example.opencvandroidboilerplate。
- 在Configure Project页面中勾选Create Activity。喜欢的话可以勾选Create custom launcher icon,在下一步后设置图标
- 在Create Activity页面选择Blank Activity。下一步后,Activity的名字和对应layout的名字就用默认的MainActivity和activity_main好了。
- 然后点Finish就创建了这个工程。
- 创建好工程之后需要导入OpenCV SDK到工程中,在Package Explorer中右键点击刚刚创建的项目,选择Properties。然后左边选Android,右边的Library中勾上OpenCV Library。如下图[在项目中导入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)
Address: http://cvnote.info/opencv4androidjni-quick-tutorial/