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
相關文章
相關標籤/搜索