OpenCV4Android+JNI開發快速上手入門
https://my.oschina.net/cvnote/blog/180661
最近嘗試了一下在Android上試驗簡單的一些OpenCV演算法,發現OpenCV4Android SDK非常好用,提供大部分常用的OpenCV功能的Java API。當然如果直接對影象畫素進行操作的話Java會比較沒有效率,這時可以對部分關鍵功能使用ndk和jni進行native的C++實現。有了這套SDK和簡單的JNI介面,像我這樣不懂安卓開發的人也可以在手機上嘗試各種好玩的演算法了。
這個入門假定你已經比較熟悉OpenCV的使用,所以主要針對安卓工程的建立和配置。
(本文為cvnote.info原創,轉載請註明出處,同時歡迎關注新浪微博@
安卓開發環境與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的話也可以參考《
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/