最近嘗試了一下在Android上試驗簡單的一些OpenCV算法,發現OpenCV4Android SDK很是好用,提供大部分經常使用的OpenCV功能的Java API。固然若是直接對圖像像素進行操做的話Java會比較沒有效率,這時能夠對部分關鍵功能使用ndk和jni進行native的C++實現。有了這套SDK和簡單的JNI接口,像我這樣不懂安卓開發的人也能夠在手機上嘗試各類好玩的算法了。 html
這個入門假定你已經比較熟悉OpenCV的使用,因此主要針對安卓工程的建立和配置。 java
(本文爲cvnote.info原創,轉載請註明出處,同時歡迎關注新浪微博@cvnote) linux
安卓的開發環境我用的是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官方教程《Android Development with OpenCV》,不過這個教程上只提供了代碼片斷,若是不想本身一段段copy代碼的話,能夠跳過這一節,直接git clone我寫好的範例工程github.com/prclibo/OpenCVAndroidBoilerplate,按照README提示配置便可。 eclipse
建立工程步驟:
項目建立好了就能夠添加代碼了,首先修改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)
雖然用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了。首先鏈接手機,點擊Eclipse上得運行鍵。App安裝成功並運行後會提示安裝OpenCV Manager,這個App相似於爲應用的OpenCV功能提供服務,按照提示安裝後,程序便可運行啦。
(原本還想嘗試一下在Emulator上運行,不過沒弄清楚爲何個人Emulator不能聯網,所以無法安裝OpenCV Manager,求達人指點。)
(本文爲cvnote.info原創,轉載請註明出處,同時歡迎關注新浪微博@cvnote)