WINDOWS系統Eclipse+NDK+Android + OpenCv
參考文檔博客
1 NDK環境搭建
http://jingyan.baidu.com/article/5d6edee22d908799eadeec9f.html
2 官方文檔
Android.mk與Application.mk如何編寫,OpenCV庫如何調用
http://docs.opencv.org/trunk/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.html#dev-with-ocv-on-android
3 OpenCv測試代碼來源
http://www.verydemo.com/demo_c131_i131095.html
4 NDK OpenCv測試入門程序(有問題)
http://blog.csdn.net/pwh0996/article/details/8957764
5 NDK測試入門程序(有問題)
http://blog.csdn.net/redoffice/article/details/6654714
6 出現部分問題的解法
http://blog.csdn.net/houshunwei/article/details/17217695
?
目前Android平臺下調用OpenCv庫函數有兩種方式:
1使用封裝的OpenCV API開發android:這種方式調用Java的包中的類實現類似。
2 利用JNI接口通過NDK平臺調用OpenCV API函數(這種方式比較普遍)。
本文將實現這兩種方式的調用。
第一部分 準備工作:
1.1本文所使用的軟件版本如下:
ADT版本:ADT-22.6.3 ,開發工具ADT(Android Development Tool),集成了最新的ADT和NDK插件以及Eclipse,還有一個最新版本SDK。解壓之后就可以用了,下載地址http://developer.android.com/tools/sdk/ndk/index.html
NDK: android-ndk-r10, NDK插件:用于開發Android NDK的插件,ADT版本在20以上,就能安裝NDK插件,另外NDK集成了CDT插件,NDK版本在r7以上之后就集成了Cygwin,而且還是十分精簡版。下載鏈接見:http://developer.android.com/tools/sdk/ndk/index.html
OpenCV:OpenCV-2.4.4-android-sdk,下載地址http://opencv.org/downloads.html
?
1.2 配置NDK的環境變量
1.2.1解壓NDK壓縮包,配置環境變量。
將解壓的地址寫入環境變量PATH中。在命令提示符下輸入ndk-build如果彈出如下的錯誤,而不是說ndk-build not found,就說明ndk環境已經安裝成功了。特別提示一下,搜索引擎中會告訴一些早期的NDK版本的使用,是在命令提示符下輸入build/host- setup.sh;但是NDK經過更新,這個文件已經沒有了。只需要輸入ndk-build就可以了。
Android NDK: Could not find application project directory !???
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.???
/home/braincol/workspace/android/android-ndk-r5/build/core/build-local.mk:85: *** Android NDK: Aborting??? .? Stop.
1.2.2 Eclipse配置NDK環境
打開Eclipse,點Window->Preferences->Android->NDK,設置NDK路徑,例如當前的NDK版本是是E:\android-ndk-r10。
1.3 配置OpenCv
下載OpenCV-2.4.4-android-sdk解壓出來,可以看到文件下有apk文件,doc文檔,sample例子程序,以及OpenCV4Android-sdk。
其 中,OpenCV4Android-sdk目錄即是我們開發opencv所需要的類庫;samples目錄中存放著若干opencv應用示例(包括人臉檢測等),可為我們進行 android下的opencv開發提供參考;doc目錄為opencv類庫的使用說明及api文檔等;而apk目錄則存放著對應于各內核版本的。
?
1.3.1 安裝OpenCV_Manager
????OpenCV_2.4.3.2_Manager_2.4應用安裝包。此應用用來管理手機設備中的opencv類庫,在運行opencv應用之前,必須確保手機中已經安裝了OpenCV_2.4.4.4_Manager_2.6_*.apk,否則opencv應用將會因為無法加載opencv類庫而無法運行。
可以根據自己的手機類型來安裝OpenCV_2.4.4.4_Manager.apk.
通過查詢自己的手機CPU信息
adb shell
cat /proc/cpuinfo
?
我的手機是arm v7 –neon 架構的,Android版本為4.4.4選擇第3個,第四個是適用于低版本Android系統的手機。
安裝完畢后,可以測試OpenCV_2.4.4.4_Manager.apk.是否正確。
在samples文件架中找個例子安裝包,如example-15-puzzle.apk,安裝到手機測試這個拼圖游戲是否正常運行。
?
1.3.2 OpenCV4Android-sdk引入工作空間
(1) 將OpenCV4Android-sdk目錄copy至后面的Android工程的工作空間;
(2)打開eclipse,將OpenCV-SDK引入到工作空間中,如下圖所示:
?
第二部分 開始實戰---使用封裝的OpenCV API開發android(不通過jni接口)
?
1.1 創建工程
(1) 打開eclipse,創建android應用工程API_OPENCV;
(2) 在手機的的SD卡根目錄/sdcard上新建一個目錄andimg ,放入圖像img1.jpg
(3) 在Package Explorer中選擇項目API_OPENCV,單擊右鍵在彈出菜單中選擇Properties,
然后在彈出的Properties窗口中左側選擇 Android,然后點擊右下方的Add按鈕,選擇OpenCV Library 2.4.4并點擊OK,
操作完成后,會將OpenCV類庫添加到API_OPENCV的Android Dependencies中,如下圖所示:
?
1.2 工程代碼:
?
(1)布局文件:main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="${packageName}.${activityClass}" ><Buttonandroid:id="@+id/imagegray"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="圖像灰度化" /><ImageViewandroid:id="@+id/imageview"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/imagegray"android:layout_centerHorizontal="true"android:layout_marginTop="86dp" /></RelativeLayout>
?
?
(2) 在AndroidManifest.xml文件中加入
?
<!-- 在SDCard中創建與刪除文件權限 --><uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/><!-- 往SDCard寫入數據權限 --><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
?
?
如圖所示
?(3) MainActivity.java
?
package com.example.api_opencv;import org.opencv.android.BaseLoaderCallback;import org.opencv.android.LoaderCallbackInterface;import org.opencv.android.OpenCVLoader;import org.opencv.android.Utils;import org.opencv.core.Mat;import org.opencv.imgproc.Imgproc;import android.annotation.SuppressLint;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.Bitmap.Config;import android.graphics.BitmapFactory;import android.os.Bundle;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ImageView;public class MainActivity extends Activity {private Button btngray;private ImageView imageview;private Bitmap bmp;//OpenCV類庫加載并初始化成功后的回調函數,在此我們不進行任何操作private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) {@Overridepublic void onManagerConnected(int status) {switch (status) {case LoaderCallbackInterface.SUCCESS:{} break;default:{super.onManagerConnected(status);} break;}}};@SuppressLint("SdCardPath")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);this.setTitle("Android API OpenCV");btngray = (Button)findViewById(R.id.imagegray);imageview = (ImageView)this.findViewById(R.id.imageview);bmp=BitmapFactory.decodeFile("/sdcard/andimg/img1.jpg");imageview.setImageBitmap(bmp);btngray.setOnClickListener(new OnClickListener() { @Overridepublic void onClick(View v) {Mat rgbMat = new Mat();Mat grayMat = new Mat();//獲取lena彩色圖像所對應的像素數據
Utils.bitmapToMat(bmp, rgbMat);//將彩色圖像數據轉換為灰度圖像數據并存儲到grayMat中
Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);//創建一個灰度圖像
Bitmap grayBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), Config.RGB_565);//將矩陣grayMat轉換為灰度圖像
Utils.matToBitmap(grayMat, grayBmp);imageview.setImageBitmap(grayBmp);}});}@Overridepublic void onResume(){//通過OpenCV引擎服務加載并初始化OpenCV類庫,所謂OpenCV引擎服務即是//OpenCV_2.4.4_Manager_2.4_*.apk程序包,存在于OpenCV安裝包的apk目錄中super.onResume();OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_4, this, mOpenCVCallBack);}}
?
1.3 運行結果
第三部分 開始實戰--- 利用JNI接口通過NDK平臺調用OpenCV API函數
- 新建一個Android工程 NDK_OPENCV(后面將會看到這種命名方式不是很好)
默認使用MainActivitiy,包名com.example.ndk_opencv
?
- 構建Android的Java層代碼
?
(1)布局文件:main.xml
?
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${packageName}.${activityClass}" >
<Button
android:id="@+id/imagegray"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="圖像灰度化" /><ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imagegray"
android:layout_centerHorizontal="true"
android:layout_marginTop="86dp" />
</RelativeLayout>
?
?
?
?
(2) 在AndroidManifest.xml文件中加入
?
<!-- 在SDCard中創建與刪除文件權限 --><uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/><!-- 往SDCard寫入數據權限 --><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
?
?
?(3) MainActivity.java
?
package com.example.ndk_opencv;import android.annotation.SuppressLint;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.Bitmap.Config;import android.graphics.BitmapFactory;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ImageView;@SuppressLint("SdCardPath")public class MainActivity extends Activity {private Button btngray;private ImageView imageview;private Bitmap bmp;@SuppressLint("SdCardPath")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);this.setTitle("Android NDK OpenCV");btngray = (Button)findViewById(R.id.imagegray);//btnProc.setOnClickListener(this);
imageview = (ImageView)this.findViewById(R.id.imageview);bmp=BitmapFactory.decodeFile("/sdcard/andimg/img1.jpg");imageview.setImageBitmap(bmp);//點擊按鈕,調用Opencv接口實現SD卡中的圖片灰度化
btngray.setOnClickListener(new OnClickListener() { @Overridepublic void onClick(View v) {bmp=BitmapFactory.decodeFile(stringNDKOPENCV());imageview.setImageBitmap(bmp);}});}//通過jni接口實現的本地函數public native String stringNDKOPENCV();static {System.loadLibrary("NDK_OPENCV");}}
?
3.2 配置工程的NDK編譯環境
?
3.2.1在工程上右鍵點擊Android Tools->Add Native Support...,然后給我們的.so文件取個名字,例如: NDK_OPENCV(默認為工程名字)
這時候工程就會多一個jni的文件夾,jni下有Android.mk和NDK_OPENCV.cpp文件。
3.2.2工程右鍵,點Properties->C/C++ Build的Building Settings中去掉Use default build command,然后輸入${NDKROOT}/ndk-build.cmd
3.2.3在C/C++ Build中點擊Environment,點Add...添加環境變量NDKROOT,值為NDK的根目錄E:\project\Android\adt-bundle-windows-x86-20140321\android-ndk-r10
3.3 編寫NDK_OPENCV.cpp文件
3.3.1生成.h文件
在編寫NDK_OPENCV.cpp文件前,需要利用javah這個工具生成相應的.h文件,然后根據這個.h文件編寫相應的C/C++代碼。用eclipse編譯該工程,生成相應的.class文件,這步必須在下一步之前完成,因為生成.h文件需要用到相應的.class文件。暫時不考慮報錯信息。
進入到剛才建立的NDK_OPENCV工程目錄中,在工程目錄下執行:?
Javah -classpath bin\classes -d jni com.example.ndk_opencv.MainActivity
這里-classpath表示類的路徑;-d jni表示生成的.h文件存放的目錄com.example.ndk_opencv.MainActivity則是完整的類名。現在可以在jni目錄下看到多了一個.h文件:com_example_ndk_opencv_MainActivity.h;
打開后,可以看到.h的內容:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_example_ndk_opencv_MainActivity */#ifndef _Included_com_example_ndk_opencv_MainActivity#define _Included_com_example_ndk_opencv_MainActivity#ifdef __cplusplusextern "C" {#endif/** Class: com_example_ndk_opencv_MainActivity* Method: stringNDKOPENCV* Signature: ()Ljava/lang/String;*/JNIEXPORT jstring JNICALL Java_com_example_ndk_1opencv_MainActivity_stringNDKOPENCV(JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
?
代碼解釋:
- JNI接口的命名規范是:Java_ + 調用該方法的包名(包名的點用_代替) + _ + 調用該接口的類名 + _ + 方法名。函數名比較長但是完全按照:java_pacakege_class_mathod 形式來命名。也就是說:MainActivity.java中stringNDKOPENCV () 方法對應于 C/C++中的 Java_com_example_ndk_1opencv_MainActivity_stringNDKOPENCV () 方法。
- 對于實例方法, JNIEXPORT 和 JNICALL 是jni的宏,在android的jni中不需要,當然寫上去也不會有錯。注意下其中的注釋:Signature: ()Ljava/lang/String表示函數的參數為空,這里為空是指除了JNIEnv *, jobject 這兩個參數之外沒有其他參數,JNIEnv*, jobject是所有jni函數必有的兩個參數,分別表示jni環境和對應的java類(或對象)本身,Ljava/lang/String表示函數的返回值是java的String對象。
- ndk_1opencv 代碼中的紅色標記處多了個1,這個1應該表示ndk_opencv為一個整體而非繼承結構,因此不建議包名出現空格,以免出錯。
3.3.2 編寫NDK_OPENCV.cpp
#include <jni.h>#include <string.h>#include <opencv2/core/core.hpp>#include <opencv2/features2d/features2d.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/calib3d/calib3d.hpp>#include <opencv2/imgproc/imgproc_c.h>extern "C" jstringJava_com_example_ndk_1opencv_MainActivity_stringNDKOPENCV( JNIEnv* env,jobject thiz ){//return env->NewStringUTF("/sdcard/andimg/img2.jpg");const char* file="/sdcard/andimg/img1.jpg";IplImage* object = cvLoadImage(file,CV_LOAD_IMAGE_GRAYSCALE);std::string filePath="/sdcard/andimg/img1_result.jpg";jstring filePath1=env->NewStringUTF(filePath.c_str());const char * resultpath=env->GetStringUTFChars(filePath1, 0);cvSaveImage(resultpath,object);//env->ReleaseStringUTFChars(path, file);
env->ReleaseStringUTFChars(filePath1, resultpath);return filePath1;}
?
代碼解釋:這段代碼首先讀取了SD卡中的一張圖像,接著調用env->GetStringUTFChars,以灰度形式讀取圖像并保存在SD卡中,最后返回灰度圖像的路徑。
3.4利用NDK平臺編譯C++語言函數成.so文件
3.4.1首先需要編寫Android.mk文件
LOCAL_PATH := $(call my-dir)
include$(CLEAR_VARS)
# OpenCV
OPENCV_CAMERA_MODULES:=on
OPENCV_INSTALL_MODULES:=on
OPENCV_LIB_TYPE:=STATIC
include E:\project\JavaWork\JavaExistWork\sdk\native\jni\OpenCV.mk
LOCAL_MODULE := NDK_OPENCV
LOCAL_SRC_FILES := NDK_OPENCV.cpp
include$(BUILD_SHARED_LIBRARY)
?
3.4.2同時建立Application.mk文件
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a
APP_PLATFORM := android-9
?
4測試運行
????如果在Eclipse下面打開NDK_OPEC.cpp文件會出現很多錯誤,需要將錯誤刪除掉,否則無法編譯。
"Eclipse可能報一堆的錯誤提示 由于eclipse的語法檢查,當你打開一個源碼,尤其是引入外面開發完成的項目,因為源碼不是在工程中管理的,大部分情況會報一堆的錯誤提示,而你是明 確這些問題實際上是不存在的,那么就可以這樣子做了,設置項目屬性,把eclipse多管的這些閑事給免了它的職,如下圖,保存后,你會發現你的 problems窗口下非常清爽了:"
解決方法之一:需要將錯誤刪除掉,否則無法編譯。
解決方法之二:打開工程屬性,選擇C/C++ General 中的Code Analysis ,接著選擇Useproject setting 去掉Syntax and Semantic……,這是Eclipse在檢測一些不符合java中的符號報錯,其實代碼沒有錯的。
?
?
運行結果