Android NDK和OpenCV整合開發 (1) 環境搭建

Android NDK和OpenCV整合開發 (1) 環境搭建

##### 第一部分 搭建環境

[注:如下全部下載的sdk都保存在虛擬機的/home/xface/tools目錄下,也能夠到百度網盤下載,地址是http://pan.baidu.com/s/1mg2Wdx2,不一樣版本的配置方式可能有些變化,若是不是很清楚版本問題的話,推薦使用虛擬機中使用的版本]html

img

1.配置Java環境java

①下載Oracle JDK,虛擬機中下載的版本是JDK1.7.0_40android

②下載以後解壓便可,解壓路徑爲/home/xface/android/jdk1.7.0git

③打開終端,輸入sudo gedit /etc/profile,在文件末尾添加下面內容github

JAVA_HOME=/home/xface/android/jdk1.7.0
export PATH=$JAVA_HOME/bin:$PATH

以下圖所示,後面環境配置中添加內容也是如此算法

img

④重啓虛擬機,打開終端輸入java -version進行測試(重啓虛擬機也能夠等待下面的Android SDK和Android NDK環境都配置好了以後再重啓也行)小程序

img

2.配置Android SDK環境windows

①下載Android Developer Tools,虛擬機中下載的是20130729版本oracle

②下載以後解壓便可,解壓路徑爲/home/xface/android/adt-bundleapp

③打開終端,輸入sudo gedit /etc/profile,在文件末尾添加下面內容

ANDROID_SDK_ROOT=/home/xface/android/adt-bundle/sdk
export PATH=${PATH}:${ANDROID_SDK_ROOT}/platform-tools:${ANDROID_SDK_ROOT}/tools

④由於Android SDK是32位的,而虛擬機中Ubuntu系統是64位的,因此須要添加ia32-libs庫,在終端中執行下面命令(須要耗費漫長的時間等待)

sudo apt-get update
sudo apt-get install ia32-libs

⑤重啓虛擬機,打開終端輸入adb version進行測試

img

3.配置Android NDK環境

①下載Android NDK,虛擬機中下載的是r9c版本

②下載以後解壓便可,解壓路徑爲/home/xface/android/adt-bundle/ndk

③打開終端,輸入sudo gedit /etc/profile,在文件末尾添加下面內容

ANDROID_NDK_ROOT=/home/xface/android/adt-bundle/ndk
export PATH=${PATH}:${ANDROID_NDK_ROOT}

④重啓虛擬機,打開終端輸入ndk-build -v進行測試

img

4.配置OpenCV環境

①下載OpenCV for Android,虛擬機中使用的是2.4.4版本

②下載以後解壓便可,解壓路徑爲/home/xface/android/opencv_sdk

5.配置ADT開發環境

①運行/home/xface/android/adt-bundle/eclipse目錄中的eclipse程序,設置默認的工做空間的路徑,虛擬機中設置的路徑爲/home/xface/android/workspace

②打開window->preferences,查看Android SDK和NDK的配置,若是路徑有問題則須要修改過來

Android SDK路徑的設置

img

Android NDK路徑的設置

img

③打開window->preferences,找到左側的C/C++ Build->Environment添加下面兩個環境變量:

NDKROOT=/home/xface/android/adt-bundle/ndk
OPENCVROOT=/home/xface/android/opencv_sdk

img

④按以下步驟配置萬能的javah工具的方法(這裏javah工具的用途是根據Java類生成C++頭文件)

(1)在菜單Run->External Tools->External Tools Configurations中新建Program,命名爲javah

(2)Location設置爲/usr/bin/javah [若是javah命令不是在這個位置,能夠試試${system_path:javah}]

(3)Working Directory設置爲${project_loc}/bin/classes [適用於Android項目開發]

(4)Arguments設置爲-jni -verbose -d "${project_loc}${system_property:file.separator}jni" ${java_type_name}

(5)OK,之後只要選中要進行"反編譯"的Java Class,而後運行這個External Tool就能夠了!

img

⑤爲了提升編寫代碼的速度,打開window->preferences,找到左側Java->Editor->Content Assist,在Auto activation triggers for Java中添加26個英文字母,這樣,在編寫Java代碼時任何一個字母被按下的話都會出現智能代碼提示。

img

⑥爲了驗證環境沒有問題,能夠嘗試新建一個Android Project並運行於移動設備上,虛擬機中eclipse下的項目xfacetest即是用來測試環境是否配置成功的默認Android應用程序,能夠嘗試插上手機,選中項目xfacetest點擊右鍵,選擇Run As -> Android Application,若是都沒問題了,說明開發環境搭建成功了。

##### 第二部分 運行XFace

[注:實驗使用的XFace項目源代碼是稍微精簡的版本,能夠到百度網盤下載,地址是http://pan.baidu.com/s/1mg2Wdx2,下載以後解壓便可,原始的XFace項目託管於Github,地址是http://github.com/hujiaweibujidao/XFace.git]

XFace是一個小型的人臉識別程序,主要功能就是註冊和識別人臉,界面分爲3個,首先是主界面,使用者選擇要進行的操做,sign up是註冊,輸入用戶名而後保存頭像便可;sign in是登陸,其實就是人臉識別的過程。

img

XFace的源碼保存在虛擬機中/home/xface/android/xface目錄下,包括兩個項目,一個是OpenCV Library - 2.4.4,這是XFace所需的OpenCV庫項目,另外一個是XFace,這個XFace核心的Android應用程序。下面介紹如何將這兩個項目導入到Eclipse開發環境中,並在手機上運行。

1.運行Eclipse,選擇File->Import...,在導入窗口中,選擇General下面的Existing Projects into Workspace,而後點擊Next->,在以後的窗口中,點擊Browser...,選中/home/xface/android/xface/下的OpenCV Library - 2.4.4文件夾,建議勾選Copy projects into workspace(能夠防止意外操做致使項目出現問題沒法修復時能夠刪除該項目從新將其導入進來),點擊Finish便可,以下圖所示:

img

2.按照步驟1中的導入操做導入/home/xface/android/xface/下的XFace項目,導入以後,若是報出問題,能夠嘗試如下步驟:選中項目XFace,點擊右鍵,選擇Properties,在屬性配置窗口中,選擇左側的Android項,查看下面的Library的配置,若是有錯誤,則選中錯誤的項,點擊Remove;若是內容爲空則點擊Add...,在彈出的窗口中選中步驟1中添加的OpenCV Library - 2.4.4項目便可,效果以下圖所示:

img

3.至此,開發環境搭建和項目導入部分都完成了,下面能夠進行XFace程序了。首先插入設備(手機),若是是在虛擬機中運行,要確保手機是和虛擬機鏈接的,而不是和主機鏈接的(能夠經過虛擬機右下角狀態欄中USB設備按鈕或者菜單虛擬機中的USB和Bluetooth進行設置);而後,選中XFace項目,點擊右鍵,選擇Run As -> Android Application,而後選中插入的手機,點擊OK便可。有些狀況下可能在列表中沒有出現設備,能夠嘗試如下步驟:首先要確保手機開啓了USB調試功能(通常是設置->開發人員選項->選中USB調試);其次能夠嘗試從新插入手機或者重啓Eclipse;若仍是不行嘗試在終端輸入adb kill-serveradb devices命令;若仍是不行的話嘗試重啓電腦。實在是不行的話,將編譯好的apk文件(保存在項目的bin目錄下)拷貝到手機中直接運行。

##### 第三部分 XFace分析

1.項目結構和主要文件功能大體介紹

img

2.關鍵部分介紹

(1)jni下的edu_thu_xface_libs_XFaceLibrary.h文件是由Java類XFaceLibrary.java經過javah工具生成的(如今要想從新生成須要將非native方法註釋起來),Java類只是定義了三個重要的native方法,實際調用的是實現了頭文件edu_thu_xface_libs_XFaceLibrary.h的另外一個C++文件xface.cpp

三個native方法以下:

public static native long nativeInitFacerec(String datapath, String modelpath, int component, double threshold,
            int facerec);
    public static native int nativeFacerec(long xfacerec, String modelpath, long addr, int width, int height);
    public static native int nativeDestoryFacerec(long xfacerec);

對應獲得的頭文件中的三個方法(注意:這裏方法的名稱和參數類型都是嚴格遵照JNI規範的,不能隨便修改)

/*
 * Class:     edu_thu_xface_libs_XFaceLibrary
 * Method:    nativeInitFacerec
 * Signature: (Ljava/lang/String;Ljava/lang/String;IDI)J
 */
JNIEXPORT jlong JNICALL Java_edu_thu_xface_libs_XFaceLibrary_nativeInitFacerec
  (JNIEnv *, jclass, jstring, jstring, jint, jdouble, jint);
/*
 * Class:     edu_thu_xface_libs_XFaceLibrary
 * Method:    nativeFacerec
 * Signature: (JLjava/lang/String;JII)I
 */
JNIEXPORT jint JNICALL Java_edu_thu_xface_libs_XFaceLibrary_nativeFacerec
  (JNIEnv *, jclass, jlong, jstring, jlong, jint, jint);
/*
 * Class:     edu_thu_xface_libs_XFaceLibrary
 * Method:    nativeDestoryFacerec
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_edu_thu_xface_libs_XFaceLibrary_nativeDestoryFacerec
  (JNIEnv *, jclass, jlong);

第一個方法是初始化人臉識別模塊,參數分別是:datapath是已有的人臉數據保存的文件路徑;modelpath是已生成的人臉識別模塊保存的文件路徑;component是人臉識別算法中使用的一個參數,表示主成分數;threshold也是人臉識別算法中使用的一個參數,表示閾值。後面兩個參數目前都是使用默認值,分別是10和0.0。

第二個方法是人臉識別算法,參數分別是:xfacerec人臉識別算法模塊對象的內存地址,以前的嘗試,目前沒有用了,能夠忽視;modelpath是建立的人臉識別模塊數據的文件保存的路徑;addr是當前攝像頭獲得的一幀圖片的灰度圖像的內存地址;width和height分別是要進行識別的人臉圖片壓縮以後的大小,目前是240*360。

第三個方法是銷燬人臉識別對象的方法,主要用於釋放JNI層中開闢的內存空間。

(2)分析FacerecCameraActivity

①人臉檢測模塊

這部分最重要的是private CascadeClassifier mJavaDetector;字段,它的初始化過程在方法onCreate(Bundle savedInstanceState)中,這裏使用了重要的lbpcascade_frontalface.yml文件,該文件本來存放在res/raw目錄下,初始化過程當中將其拷貝到了SD卡中,並使用這個文件建立了CascadeClassifier。代碼片斷:

try {
    // File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
    mCascadeFile = new File(CommonUtil.LBPCASCADE_FILEPATH);
    if (!mCascadeFile.exists()) {// if file not exist, load from raw, otherwise, just use it!
        // load cascade file from application resources
        InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
        // mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
        FileOutputStream os = new FileOutputStream(mCascadeFile);
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = is.read(buffer)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        is.close();
        os.close();
    }
    mJavaDetector = new CascadeClassifier(mCascadeFile.getAbsolutePath());
    if (mJavaDetector.empty()) {
        Log.e(TAG, "Failed to load cascade classifier");
        mJavaDetector = null;
    } else
        Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());
    // mNativeDetector = new DetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);// hujiawei
    // cascadeDir.delete();//
} catch (IOException e) {
    e.printStackTrace();
    Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
}

最後在攝像頭的回調方法onCameraFrame(CvCameraViewFrame inputFrame)中對攝像頭獲得的圖片幀進行人臉檢測,將檢測出來的人臉方框直接繪製在圖片幀上馬上顯示出來(該方法會在每次攝像頭有新的一幀)。代碼片斷以下,其中mRgba是每次獲得的圖片的RGBA格式,mGray是每次獲得的圖片的灰度格式

public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
    // Log.i(TAG, inputFrame.gray().width() + "" + inputFrame.gray().height());
    // landscape 640*480 || portrait [320*240]-> 240*320!
    // when portrait mode, inputframe is 320*240, so pic is rotated!
    mRgba = inputFrame.rgba();
    mGray = inputFrame.gray();
    Core.flip(mRgba.t(), mRgba, 0);//counter-clock wise 90
    Core.flip(mGray.t(), mGray, 0);
    if (mAbsoluteFaceSize == 0) {
        int height = mGray.rows();
        if (Math.round(height * mRelativeFaceSize) > 0) {
            mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
        }
        // mNativeDetector.setMinFaceSize(mAbsoluteFaceSize);//
    }
    MatOfRect faces = new MatOfRect();
    if (mJavaDetector != null) {// use only java detector
        mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2, // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
    }
    Rect[] facesArray = faces.toArray();
    for (int i = 0; i < facesArray.length; i++) {
        Core.rectangle(mRgba, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);
    }
    Core.flip(mRgba.t(), mRgba, 1);//counter-clock wise 90
    Core.flip(mGray.t(), mGray, 1);
    return mRgba;
}

②人臉識別模塊

由於人臉識別過程須要耗費必定的時間,若是每次圖片幀傳入的時候便進行處理,處理完了以後再顯示的話會致使界面卡死,因此人臉識別過程是在另開闢的一個線程中執行的,線程代碼以下,只要攝像頭還在工做,也就是還會傳回圖像的話,那麼這個線程便會取出其灰度圖像傳入到JNI層進行人臉識別操做,並將結果顯示出來,此處消息的傳遞方式使用的是Android中的Handler機制。

new Thread(new Runnable() {
    public void run() {
        Log.i(TAG, "bInitFacerec= " + bInitFacerec + " $$ bExitRecognition= " + bExitRecognition
                + " $$ frameprocessing=" + bFrameProcessing);
        if (!bInitFacerec) {// facerec init?
            long result = XFaceLibrary.initFacerec();// it will take a lot of time!
            Message message = new Message();
            message.arg1 = (int) result;// 1/-1/-2
            message.arg2 = 0;
            handler.sendMessage(message);
            bInitFacerec = true;// no longer init!
        }
        while (!bExitRecognition) {// is recognition exits?
            if (!bFrameProcessing) {// is frame being processing?
                if (null == mGray || mGray.empty()) {//it's hard to say when it is called!
                    Log.i(TAG, "gray mat is null");
                    // return;// return when no data//can not return
                } else {
                    bFrameProcessing = true;
                    Log.i(TAG, "runFacerec! addr = " + mGray.getNativeObjAddr());// 2103032
                    // Log.i(TAG, "data addr=" + mGray.dataAddr() + " $$ native addr=" +
                    // mGray.getNativeObjAddr()
                    // + " $$ native object=" + mGray.nativeObj);// $1 not equal $2,but $2=$3
                    int result = XFaceLibrary.facerec(mGray);
                    Message message = new Message();
                    message.arg1 = result;
                    message.arg2 = 1;
                    handler.sendMessage(message);
                    bFrameProcessing = false;
                }
            }
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}).start();

3.XFace應用程序在手機SD卡中的相關文件

XFace應用程序的使用過程當中會產生一些文件夾和文件,所有存放在SD卡的xface文件夾下。這部份內容能夠參看文件CommonUtil.java文件,在包edu.thu.xface.util下。

camera文件夾存放攝像頭拍照獲得的頭像;

user文件夾存放灰度化和壓縮處理以後的頭像;

demo文件夾存放測試或者示例程序的數據,目前爲空;

facedata.txt文件存放人臉圖片路徑和人物的對應關係,文件中圖片路徑;數字表示該數字編號的人物的頭像圖片所在的路徑;

users.properties文件用來保存用戶的配置和註冊用戶的信息,文件中total表明總共註冊的人數;後面的數字=用戶名表示人物編號與人物名稱的對應關係,1=hujiawei表示1號人物表明用戶hujiawei,再根據facedata.txt文件中的內容即可以知道hujiawei用戶頭像圖片存儲的路徑;最後的facerecognizer=?保存當前使用的人臉識別算法,例如facerecognizer=eigenface表示使用的是特徵臉算法,XFace雖然內置了OpenCV中的三種人臉識別算法,可是目前只有eigenfacefisherface兩種算法可行,第三種lbphface算法暫時不可行。

facerec.yml文件是OpenCV中人臉識別算法用來保存建立的識別模塊數據的文件;

lbpcascade_frontalface.yml文件是OpenCV中進行人臉檢測所須要的數據文件;

##### 第四部分 其餘參考內容

其餘的參考內容:

關於Android開發的書籍和資料

文章最後附有兩份Android開發入門課程PPT,以及一個Android小程序魔力8號球,百度網盤一樣能夠下載

關於在Ubuntu12.04下搭建android開發環境的教程

關於在windows平臺搭建android開發環境的教程

不推薦使用Windows進行開發,由於不只要安裝Cygwin,還要進行不少其餘的配置,若是實在是不得已,能夠嘗試參考這位博主的環境搭建過程

關於android ndk和opencv整合開發以及實例項目運行的教程

介紹Android NDK和OpenCV整合開發的環境搭建過程和實例項目測試,重點能夠參考的是其中的人臉檢測和眼鏡檢測的兩個項目,XFace中的人臉檢測便來源於此。

關於android ndk開發中的各類細節和問題的總結

理解javah工具和Android.mk以及Application.mk文件的配置,若是是在Windows平臺搭建環境的話,須要查看這部分關於C/C++ Genernal -> Paths and Symbols的配置

關於OpenCV中的人臉識別算法 - OpenCV FaceRecognizer documentation

該博客做者是OpenCV2.4以後內置的人臉識別模塊的原做者,他在他的博客中詳細介紹了FaceRecognizer的API以及他使用的人臉識別算法,算法講解部分能夠參考Face Recognition with Python/GNU Octave/Matlab

相關文章
相關標籤/搜索