在OpenCV4Android中沒有nonfree module,因此也就沒有了SURF和SIFT組件。但是我們可以通過OpenCV for Windows的nonfree module開原始碼通過NDK將其編譯為Android可以使用的.so庫檔案,然後通過JNI技術,將該.so檔案掛載到JNI的庫中。
具體實現方法如下:
需要的工具:
- NDK
- OpenCV for Android
- OpenCV for Windows(其實只需要兩個標頭檔)
- Android ADT
電腦作業系統為Windows7 x86,Android開發環境為ADT 20130512。下面說明如何在OpenCV中使用nonfree module:
1.編譯nonfree module庫
博主OpenCV for Windows安裝路徑為C:\Program Files\opencv,OpenCV4Android路徑為 E:\My Documents\Android\OpenCV-2.4.5-android-sdk。
nonfree module 的原始碼儲存在C:\Program Files\opencv\modules\nonfree\src中,標頭檔儲存在C:\Program Files\opencv\modules\nonfree\include\opencv2\nonfree中。
編譯的過程中需要兩個標頭檔,一個為nonfree.hpp,一個為features2d.hpp,將這兩個標頭檔拷貝至OpenCV4Android SDK的include檔案夾下:E:\My Documents\Android\OpenCV-2.4.5-android-sdk\sdk\native\jni\include\opencv2\nonfree檔案夾下。
為了編譯庫檔案,我們還需要nonfree module的原始碼。原始碼為C:\Program Files\opencv\modules\nonfree\src中的nonfree_init.cpp,precomp.cpp,sift.cpp,surf.cpp和precomp.hpp五個檔案。為了簡單起見,我們需要一個簡單的Android JNI的工程來幫我們編譯這些原始碼。
2.配置NDK環境
在Android中建立JNI工程的方法是在一個已經建立好的Android工程上點擊右鍵,new->Ohter->Convert to a C/C++ Project(Adds C/C++ Nature),並在Android工程目錄中建立jni檔案夾,將上述5個原始碼檔案拷貝至jni檔案夾中。並在jni檔案夾中建立Android.mk和Application.mk檔案。此時檔案組織如下:
博主NDK的位置為C:\android-ndk-r8e,在系統變數中建立NDKROOT,變數值為C:\android-ndk-r8e,在該檔案夾下有ndk-build.cmd指令碼命令檔案,該檔案是使用NDK編譯C代碼的關鍵。配置完畢後,在Android工程中配置C++編譯命令,配置如下所示,意味使用環境變數中NDKROOT/ndk-build.cmd進行編譯 在Behaviour選項卡中勾選Build on resource save(Auto build)。至此NDK配置完畢 3.編寫makefile檔案 在Android.mk中代碼如下
LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)OPENCV_INSTALL_MODULES:=onOPENCV_CAMERA_MODULES:=offinclude ..\OpenCV-2.4.5-android-sdk\sdk\native\jni\OpenCV.mkLOCAL_C_INCLUDES:= ../OpenCV-2.4.5-android-sdk/sdk/native/jni/includeLOCAL_MODULE := nonfreeLOCAL_CFLAGS := -Werror -O3 -ffast-mathLOCAL_LDLIBS += -llogLOCAL_SRC_FILES := nonfree_init.cpp \ precomp.cpp \ sift.cpp \ surf.cppinclude $(BUILD_SHARED_LIBRARY)
在Android.mk中使用了OpenCV.mk的路徑和jni/include的路徑,我的工程是和OpenCV SDK放在同一檔案夾下的,因此可以這樣寫。兩處使用絕對路徑也可以,但是不能有空格。
在Application.mk中代碼如下
#APP_ABI := armeabi APP_ABI := armeabi-v7aAPP_STL := gnustl_staticAPP_CPPFLAGS := -frtti -fexceptionsAPP_PLATFORM := android-8
這裡可能有問題,APP_PLATFORM按照Andoird工程建立時最小SDK填寫,否則編譯不過。
如果之前勾選了Auto Build,這裡工程會自動編譯,在libs下會產生兩個庫,為libnonfree.so和libopencv_java.so,這兩個庫就是需要的庫檔案了。使用這兩個庫檔案,就可以通過JNI技術在Android中使用SURF演算法。4. 在Android中使用SURF的例子 和2中一樣,首先建立一個帶有NDK工程的Android工程,並建立jni檔案夾,用javah產生native的類標頭檔(如何產生類檔案請參看另一邊博文),並將剛才編譯好的libnonfree.so和libopencv_java.so拷貝值jni檔案夾下(當然你也可以把這兩個東西放到OpenCV的庫檔案夾下,一勞永逸,只要能在連結的時候能搜尋到就行了)。編寫好native方法的c++實現,這裡我實現了利用SURF的特徵點檢測和描述符產生。代碼如下:
#include "NativeSurf.h"#include <opencv2/opencv.hpp>#include <stdio.h>#include <opencv2/nonfree/features2d.hpp>#include <opencv2/nonfree/nonfree.hpp>using namespace cv;using namespace std;void KeyPoint2Mat(vector<KeyPoint>& keypoints, Mat& mat){int i = 0;int size = keypoints.size();mat.create(size,1,CV_32FC(7));float* buff = (float*)mat.data;for(i=0;i<size;i++){KeyPoint kp = keypoints[i];buff[7*i+0] = kp.pt.x;buff[7*i+1] = kp.pt.y;buff[7*i+2] = kp.size;buff[7*i+3] = kp.angle;buff[7*i+4] = kp.response;buff[7*i+5] = kp.octave;buff[7*i+6] = kp.class_id;}}JNIEXPORT void JNICALL Java_com_ruif_nativeSurf_NativeSurf_SurfDetect (JNIEnv *, jclass, jlong imgObj, jlong keyPointsObj, jlong descriptorObj){//Create MatsMat* img = (Mat*)imgObj;//imgMat* descriptor = (Mat*)descriptorObj;//DescriptorMat* keyPointsMat = (Mat*)keyPointsObj;vector<KeyPoint> keyPointvec;SurfFeatureDetector surfDetector(1000);SurfDescriptorExtractor surfExtractor;surfDetector.detect(*img,keyPointvec);surfExtractor.compute(*img,keyPointvec,*descriptor);KeyPoint2Mat(keyPointvec,*keyPointsMat);}
編寫Android.mk檔案如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := nonfreeLOCAL_SRC_FILES := libnonfree.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := opencv_java_prebuiltLOCAL_SRC_FILES := libopencv_java.soinclude $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)include ../OpenCV-2.4.5-android-sdk/sdk/native/jni/OpenCV.mkLOCAL_MODULE := SurfLOCAL_CFLAGS := -Werror -O3 -ffast-mathLOCAL_LDLIBS += -llog -ldl LOCAL_SHARED_LIBRARIES := nonfree opencv_java_prebuiltLOCAL_SRC_FILES := Surf.cppinclude $(BUILD_SHARED_LIBRARY)
編寫Application.mk檔案如下:
APP_ABI := armeabi-v7aAPP_STL := gnustl_staticAPP_CPPFLAGS := -frtti -fexceptionsAPP_PLATFORM := android-8
此時你會發現各種語法錯誤和不能解析的變數。其實他們並不影響編譯,所有的標頭檔在Android.mk和Application.mk中已經聲明,編譯仍然會成功,但是自己編寫程式時為了使用Eclipse的提示功能,需要對工程進行如下設定。
在C/C++ General->Paths and Symbols中設定Includes為:${NDKROOT}/platforms/android-14/arch-arm/usr/include${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.7/include${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.7/libs/armeabi-v7a/include../OpenCV-2.4.5-android-sdk/sdk/native/jni/include參見
OpenCV4Android文檔
你會發現所有都正常了,此時工程結構組織如下:
在libs下的libSurf.so庫就是我們需要使用的JNI庫。
5.使用JNI庫 該JNI庫需要結合OpenCV使用,因此仍然需要調用OpenCV4Anroid庫,調用的方法參見
OpenCV文檔。需等待OpenCV庫載入完畢後才能載入我們編寫的庫。2.4.5中應該不用添加initial_nonfree()這個函數了。運行能夠成功。
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: { Log.i(Unity.TAG,"OpenCV loaded successfully"); System.loadLibrary("nonfree"); System.loadLibrary("opencv_java"); System.loadLibrary("Surf"); isOpenCVLoad = true; } break; default: { Log.i(Unity.TAG,"OpenCV loaded Failed!"); super.onManagerConnected(status); } break; } } };
一定要順序載入,否則會報錯。至此就能夠在JAVA中使用Native函數了。網上這方面資料很少,希望對大家有所協助。