1. 程式人生 > >ORB_SLAM2移植到Android,完整配置+填各種自己遇到的坑

ORB_SLAM2移植到Android,完整配置+填各種自己遇到的坑

2018.9.30修改:
建議使用Android studio實現JNI庫配置,拋棄eclipse吧,誰用誰知道!
配置方式可以參考我的新文章android sudio 在專案中使用android-serialport-api 實現串列埠通訊,非常簡單。

----原文-----
本來想直接下載github上的工程import,但是不知道為什麼直接匯入後eclipse直接卡死,不管怎麼配置SDK、NDK、ADT各種都沒用,所以只能自己移植,折騰了好久還順便學了下Android,終於搞定了。

關於Android的移植網上的資料非常多也非常雜。雖然很多部落格寫的很好,但是還是推薦直接看官方文件。

Android環境配置

首先是JDK的配置,很簡單百度一下,最後做個測試:
在命令列輸入java -version,如果出現以下類似內容則配置成功:
JDK測試

然後下載完Eclipse後,Java的環境配置好了,但是Android的開發,還需要以後步驟:
1.ADT,有線上安裝和離線包兩種方式,線上方法為:
在Eclipse-> “Help” -> “Install New Software” ->Add ,出現下圖所示後Name隨便取,Location輸入http://dl-ssl.google.com/android/eclipse 確認後按提示一步一步next就OK了。
ADT
離線方式前面也一樣,只是不是直接填Location,而是先下載離線包,然後點選Archive選擇壓縮包

2.Android SDK,建議從官網下載(需要科學上網),SDK官網下載 網上下載說不定就會掉坑,不要問我為什麼知道。。下載並解壓後(路徑不要有中午),在Eclipse中選擇Window–>preferences ->Android 在Android Location裡填入解壓的根目錄,則Android環境配置基本完成。如下圖所示:
SDK
這時候你的選單欄應該有了這兩項 :
SDKAVD
分別是SDK和AVD(模擬器)
PS:如果配置完後運氣不好沒有出現也沒事(沒錯就是我),先在Eclipse點Window,如果有下圖這樣的SDK和AVD,那麼就證明並不是配置失敗了,而是設定問題。
SDK測試

設定方法:Windows–>Customize Perspective,選擇Command Groups Availability選項,確保Android SDK and AVD Manager是勾選上的,然後選擇Tool Bar Visibility選項—>勾選Android SDK and AVD Manager,這兩個順序不能反,OK後好了~
設定

3.更新SDK,在更新之前,建議先科學上網,這個需要連線外網。當然網上有更改host檔案的方法,可以自行搜尋。點剛剛那個左邊的圖示,介面如下:
SDK更新

其中有許多檔案,但是不需要全下!不需要全下!不需要全下!
Tools全下,然後每個版本的SDK Platform、Sources for Android SDK全下,Googel APIs在天朝一般用不到可以不下,主要要提System Image,映象是模擬器,只用下自己需要模擬器的版本就可以了,都下的話超大超久,血淚教訓,Extras建議下載。
具體內容可以參考知乎:Android SDk裡面到底哪些東西是必須下載的

---------至此Android開發環境就配置好了----
4.NDK配置。下載官網最新版本的NDK NDK官網下載(需要科學上網)。網上直接下說不定就有坑。。而且這個坑還根本看不出來,還是官網安全。而且低版本r7以下還需要cygwin,7以上的新版就不用了。
先在環境變數裡配置NDK的根目錄(這一步在命令列操作的時候挺重要的),解壓後(規則同sdk),開啟Eclipse,點選Window–>Preferences,選擇Android–>NDK,在右邊介面中填入NDK Location即可。
NDK

先測試一下NDK是否配置好(翻譯官網提供的教程):
開啟Eclipse,File->import->Android->Existing Android Code Into Workspace(**這裡不要直接在General裡選擇Existing Projects Into Workspace,**是找不到例程的,我剛開始這樣還以為是NDK配置問題,還蠢蠢的重新弄,走了好多彎路)
->Browse->/samples/,加入HelloJni工程,(最新版的NDK不知道為什麼不提供例程了,可以下r10b版本然後匯入)。
匯入後,右鍵select Android Tools > Add Native Support,Accept the default library name (“hello-jni”), and click Finish.
這時候選單欄應該會出現一個小錘子圖示。點選Build,在模擬器或者真機執行後如下圖所示則配置成功:
ND測試

Android移植基礎##

NDK是整合的Android中呼叫C++程式碼的工具包,核心是JNI(Java Native Interface)技術,具體這裡略過不表。只說說NDK開發的基本步驟:
編寫Java程式碼:在Java中定義一個類,比如說叫NDKHelper吧,裡面定義幾個java的方法,只需要宣告,不需要實現,如下所示:

public class NDKHelper {
    //NDK示例方法1
    public static native void ndkOne(int a,long b);

    //NDK示例方法2
    public static native int ndkTwo(String a,String b);
}

native識別符號表示該函式將會利用C++程式碼完成實現。
接下來在工程上右鍵,Android Tools–>Add native support
名字就是最後我們要生成的庫的名字,隨便填,可修改。點選確定就會給你的工程新增C++編譯支援,選單欄會多了個小錘子,這個是用來編譯C++的快捷鍵。在你的工程目錄下會新建jni目錄和obj目錄,其中jni目錄用來存放和C++程式碼有關的東西,obj則存放C++進行編譯時產生的中介軟體,最後生成的library會寫入到libs資料夾下。
在jni資料夾中生成了如下檔案,一個.cpp,一個Android.mk,其中.cpp是自動生成的,是用來編寫C++部分的,而Android.mk類似C++裡面的CMakeList,用來指定需要編譯的檔案和編譯生成的模組名,一個最簡單的Android.mk檔案如下所示:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := NDKTest
LOCAL_SRC_FILES := NDKTest.cpp
include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH :=$(call my-dir)表示包含當前目錄。
include $(CLEAR_VARS)表示清除全部非系統變數和部分系統變數;
LOCAL_MODULE := NDKTest 表示當前生成的模組名,最終會生成libNDKTest.so檔案
LOCAL_SRC_FILES := NDKTest.cpp 表示當前需要編譯的cpp檔案;
include $(BUILD_SHARED_LIBRARY) 表示生成共享庫,需要生成靜態庫請修改成BUILD_STATIC_LIBRARY。

其他基礎命令:
LOCAL_C_INCLUDES:= 表示新增標頭檔案進入編譯環境
LOCAL_LDLIBS:= 表示新增系統靜態庫
LOCAL_SHARED_LIBRARIES:= 表示新增共享庫
其他命令請自行檢視API文件。

這裡指定了進行編譯時的各項條件,如果需要指定編譯器版本和編譯目標平臺等資訊,則需要在jni目錄下新建Application.mk檔案。
接下來編寫對應的C++檔案。
開啟eclipse,點選Project–>build Project(若build automatically已勾選則會自動編譯)開啟命令列,cd到你的工程資料夾下的bin–>classes資料夾下,輸入如下命令:
javah com.example.ndktest.NDKHelper
回車,則在你的classes資料夾下會生成對應的標頭檔案。這裡com.example.ndktest是你的package名字,NDKHelper是你的NDK函式的類名。
就會生成標頭檔案。其他不用管,我們關注中間的兩個函式宣告:

JNIEXPORT void JNICALL Java_com_example_ndktest_NDKHelper_ndkOne(JNIEnv *, jclass, jint, jlong);
1

這個函式就是NDKHelper類中ndkOne函式對應的C++版本,其中JNIEXPORT和JNICALL是固定欄位,void是函式返回值,函式名由Java欄位+包名+類名+函式名組成,引數則多了幾個JNI的系統引數JNIEnv 和jclass,其他的就是NDKHelper類中的對應引數,ndk會對該函式進行解析和連結,實現java和C++的對接。
將生成的.h標頭檔案複製到jni目錄下,新建對應的cpp檔案,將該標頭檔案include進來並對對應函式進行實現,實現過程就視函式功能而定。
這些工作完成後需要修改你的Android.mk檔案,將剛剛新建的cpp和h檔案包括進來。
然後點選開始那個小錘子或者直接專案右鍵RunAs–>Android Application,則C++部分會開始編譯,編譯具體過程可以在Eclipse下方Console視窗看到(如果沒有Console視窗則點選Window–>Show Views,選擇Console確定即可)。
編譯完成後會生成對應的庫存放在libs目錄下,則你可以開始在Java裡面呼叫剛才定義的ndkOne和ndkTwo函式實現具體的功能。

ORB_SLAM2移植

先新建一個Android工程,建議不要把版本相容到4.0以下,會出現一些奇奇怪怪的事情,這裡我選擇新建4.4.2的工程。
然後複製原工程的res資料夾替換掉自己工程下的資料夾,這裡包括了所需的圖片、存放在res/values/strings.xml的字元、res/layout下的各種佈局;複製原工程的AndroidManifest.xml替換新建工程下的xml,複製完之後,意料之中報錯啦,這是因為還沒有連結OpenCV庫,而在佈局裡有用到。

OpenCV庫配置(參考官方文件)
先去官網下載opencv4android:
OpenCV官網下載
解壓後,可以參考/docs/opencv_tutorials.pdf 感覺比很多部落格講的都更清楚
根據例程測試過問問題後,匯入workspace,然後右鍵我們的ORB_SLAM2_Android工程->properties->Android,在右下Library裡Add OpenCV庫,如下圖所示:

OPENCV
這樣之後就暫時沒有報錯了

按照之前Android移植基礎裡講的辦法生成小錘子、jni、libs資料夾,然後複製原工程的.java檔案到src,如下圖所示:
.Java
其中OrbNdkHelper.java檔案是關鍵的native識別符號所在的類,程式碼如下:

public class OrbNdkHelper {
	
	/**
	 * jni
	 * @param VOCPath
	 * @param calibrationPath
	 */
	public static native void initSystemWithParameters(String VOCPath,String calibrationPath);
	
	public static native int[] startCurrentORB(double curTimeStamp,int[] data,int w,int h);
	public native static int[] startCurrentORBForCamera(double curTimeStamp,long addr,int w,int h);
	public native static void glesInit();  
    public native static void glesRender();  
    public native static void glesResize(int width, int height);

}

然後在命令列cd到工程根目錄/bin/classes,輸入javah orb.slam2.android.nativefunc.OrbNdkHelper生成.h檔案,並複製到jni資料夾,然後複製原工程jni目錄下的其他檔案到本工程的jni,如下圖所示:
jni
這時候大概的檔案都已經匯入完畢了,但是我出現了許多的報錯,以下是填坑過程。

1.orb_slam2_android_nativefunc_OrbNdkHelper.cpp裡有幾個報錯:

JNIEXPORT void JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_initSystemWithParameters
(JNIEnv * env, jclass cls, jstring VOCPath, jstring calibrationPath) {
	const char *calChar = env->GetStringUTFChars(calibrationPath, JNI_FALSE);
	const char *vocChar = env->GetStringUTFChars(VOCPath, JNI_FALSE);
	// use your string
	std::string voc_string(vocChar);
	std::string cal_string(calChar);
	JavaVM *jvm=NULL; //增加的一句
	env->GetJavaVM(&jvm);
	jvm->AttachCurrentThread(&env, NULL);
	s=new ORB_SLAM2::System(voc_string,cal_string,ORB_SLAM2::System::MONOCULAR,true);
	env->ReleaseStringUTFChars(calibrationPath, calChar);
	env->ReleaseStringUTFChars(VOCPath, vocChar);
	init_end=true;
}

這段程式碼裡面的:

JavaVM *jvm=NULL; //增加的一句

是我增加的,如果不增加的話,後兩句會報錯無法識別jvm這個變數,我搜索了全部工程也找不到jvm,所以只能自己先增加一個;
還有一個地方是:

resultPtr = env->GetIntArrayElements(resultArray, 0);

有3個地方用到GetIntArrayElements這個函式,全工程的第二個引數是false,這個在我這裡是報錯的,然後我百度了挺久,發現這個函式的宣告是:

 jint* GetIntArrayElements(jintArray array, jboolean* isCopy)

原工程的flase不能被程式認定為jboolean格式,然後在jni.h順藤摸瓜找到了以下定義:

#define JNI_FALSE   0
#define JNI_TRUE    1

於是把原來的“false”改成“JNI_FALSE”或者0,就可以通過了!

2.在用NDK-r10b版本的時候ORB_SLAM2資料夾和第三方資料夾出現大量報錯,很多變數無法找到,這裡的解決辦法是:
改到r13b最新版本,然後在Application.mk檔案裡,NDK_TOOLCHAIN_VERSION := 4.8–>改到4.9

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions -mfloat-abi=softfp -mfpu=neon -std=gnu++0x -Wno-deprecated \
-ftree-vectorize -ffast-math -fsingle-precision-constant
NDK_TOOLCHAIN_VERSION := 4.9
APP_CFLAGS := --std=c++11
APP_ABI :=armeabi-v7a
APP_OPTIM := release
APP_SHORT_COMMANDS      := true