1. 程式人生 > >opencv人臉檢測程式碼應用與分析(PC端和Android端)

opencv人臉檢測程式碼應用與分析(PC端和Android端)

(1)OpenCV人臉檢測C++程式流程:

OpenCV的人臉檢測程式採用了Viola & Jones人臉檢測方法,主要是呼叫訓練好的瀑布級聯分類器cascade來進行模式匹配。

cvHaarDetectObjects先將影象灰度化,根據傳入引數判斷是否進行canny邊緣處理(預設不使用),再進行匹配。匹配後收集找出的匹配塊,過濾噪聲,計算相鄰個數如果超過了規定值(傳入的min_neighbors)就當成輸出結果,否則刪去。

匹配迴圈:將匹配分類器放大scale(傳入值)倍,同時原圖縮小scale倍,進行匹配,直到匹配分類器的大小大於原圖,則返回匹配結果。匹配的時候呼叫cvRunHaarClassifierCascade來進行匹配,將所有結果存入CvSeq* pcvSeqFaces(可動態增長元素序列),將結果傳給cvHaarDetectObjects。

cvRunHaarClassifierCascade函式整體是根據傳入的影象和cascade來進行匹配。並且可以根據傳入的cascade型別不同(樹型、stump(不完整的樹)或其他的),進行不同的匹配方式。函式cvRunHaarClassifierCascade 用於對單幅圖片的檢測。在函式呼叫前首先利用cvSetImagesForHaarClassifierCascade設定積分圖和合適的比例係數 (>= 視窗尺寸)。當分析的矩形框全部通過級聯分類器每一層的時返回正值(這是一個候選目標),否則返回0或負值。

Haar分類器的訓練是獨立於人臉檢測過程的。分類器的訓練分為兩個階段:

A.建立樣本,用OpenCV自帶的creatsamples.exe完成。

B.訓練分類器,生成xml檔案,由OpenCV自帶的haartraining.exe完成。

同時,OpenCV中採用的訓練演算法adaboost是gentle adaboost,為最適合人臉檢測的方案。

基於 OpenCV 的人臉檢測主要完成 3 部分功能 , 即載入分類器、 載入待檢測圖象以及檢測並標示。本演算法使用 OpenCV 中提供的 “ haarcascade_frontalface_alt. xml ” 檔案儲存的目標檢測分類 , 用 cvLoad 函式載入後 , 進行強制型別轉換。OpenCV 中提供的用於檢測影象中目標的函式是 cvHaarDetectObjects , 該函式使用指標對某目標物體 ( 如人臉) 訓練的級聯分類器在圖象中找到包含目標物體的矩形區域 , 並將這些區域作為一序列的圓形框返回,實現程式碼如下 :

#include <opencv2/opencv.hpp> 
#include <cstdio> 
#include <cstdlib> 
#include <Windows.h>
int main(){
	// 載入Haar特徵檢測分類器 
    // haarcascade_frontalface_alt.xml系OpenCV自帶的分類器,下面是檔案路徑 
       const char *pstrCascadeFileName ="D:\\Program Files\\opencv\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml";
	// 建立瀑布級聯分類器cascade來進行模式匹配                                       
    CvHaarClassifierCascade *pHaarCascade =NULL; 
    pHaarCascade =(CvHaarClassifierCascade*)cvLoad(pstrCascadeFileName); 
	// 載入影象
	// 建立一個IpIImage 影象資料結構進行處理
    const char *pstrImageName ="101.jpg"; 
    IplImage *pSrcImage = cvLoadImage(pstrImageName,CV_LOAD_IMAGE_UNCHANGED); 
    // 輸出影象 
    IplImage *pGrayImage =cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1); 
    cvCvtColor(pSrcImage, pGrayImage,CV_BGR2GRAY); 
    // 人臉識別與標記 
    if (pHaarCascade != NULL) 
    {        
        CvScalar FaceCirclecolors[] =  
        { //標記人臉框的顏色
            {{0, 0, 255}}, {{0, 128,255}},  {{0, 255, 255}},  {{0, 255, 0}},  {{255, 128, 0}},  {{255, 255, 0}}, {{255, 0, 0}},  {{255, 0, 255}} 
        }; 
        //建立一個新的記憶體儲存區 , 引數為0 則採用預設設定
        CvMemStorage *pcvMStorage =cvCreateMemStorage(0); 
        cvClearMemStorage(pcvMStorage); 
        // 處理的開始和結束時間 
        DWORD dwTimeBegin, dwTimeEnd; 
        dwTimeBegin = GetTickCount(); 
			  // 人臉識別核心函式(opencv自帶)cvHaarDetectObjects
              //返回一個包含檢測結果(圖片中的位置)資訊的結構體資料 pcvSeqFaces
              //具體演算法實現見opencv原始碼D:\ProgramFiles\opencv\opencv\sources\modules\objdetect\src\haar.cpp
        CvSeq *pcvSeqFaces = cvHaarDetectObjects(pGrayImage,pHaarCascade, pcvMStorage); 
        dwTimeEnd = GetTickCount(); 
        printf("人臉個數: %d   識別用時: %d ms\n", pcvSeqFaces->total,dwTimeEnd - dwTimeBegin); 
        // 開始標記
        for(int i = 0; i<pcvSeqFaces->total; i++) 
        {  // 逐次獲取位置資訊
            CvRect* r =(CvRect*)cvGetSeqElem(pcvSeqFaces, i); 
            CvPoint center; 
            int radius; 
            center.x = cvRound((r->x +r->width * 0.5)); 
            center.y = cvRound((r->y +r->height * 0.5));  
            radius = cvRound((r->width +r->height) * 0.25); 
            cvCircle(pSrcImage, center, radius,FaceCirclecolors[i % 8], 2); 
        } 
       cvReleaseMemStorage(&pcvMStorage); //釋放記憶體儲存區
    } 
	const char *pstrWindowsTitle = "人臉識別"; 
	//建立處理結果展示視窗
    cvNamedWindow(pstrWindowsTitle,CV_WINDOW_AUTOSIZE); 
    cvShowImage(pstrWindowsTitle,pSrcImage); 
    cvWaitKey(0); 
    cvDestroyWindow(pstrWindowsTitle);  //釋放視窗資源
    cvReleaseImage(&pSrcImage);   //釋放圖片資源
    cvReleaseImage(&pGrayImage); 
return 0;
}

(2)OpenCV 移植到Android

首先,需確定搭建配置完成 Android 開發環境所需的 JDK、EclipseIDE、Android SDK 和 ADT。其次,確定安裝 CDT 外掛,以供 Eclipse 能夠開發 C++。

Android 需要使用JNI 編寫原生代碼,並使用 Android NDK進行交叉編譯。Android NDK 有多種版本,本實驗使用的計算機是 32 位的 Windows 作業系統,所以選擇版本為 android-ndk-r10-windows-x86。

OpenCV從 2.2 版本後支援在 Android 下開發,本系統採用的是 OpenCV-2.4.10 版本。在該版本解壓縮後的資料夾下,有4 個子資料夾,它們分別是 sdk、samples、doc 和apk。Sdk 是OpenCV所需的類庫;samples 是 OpenCV 應用的例子;doc 是OpenCV 類庫的使用說明、api文件和一些影象等;apk 是一些對應於各核心版本的 OpenCV_2.4. 10_Manager_2.9 應用安裝包。該應用是用來管理 OpenCV 類庫,若手機中無此應用,或未將該應用移植到自己編寫的 Android 應用程式中,將無法載入 OpenCV

類庫,從而導致程式無法執行下去。

在 Eclipse 上需匯入進 OpenCV-2.4. 10 裡的 sdk 資料夾下所有內容作為一個專案,在所需開發的專案的 Properties Android 裡新增庫,該庫即為匯入進來的 OpenCV 的sdk 專案。

在 Android 平臺上使用 OpenCV 進行人臉檢測,需要使用NDK 工具編譯使用 JNI 編寫的原生代碼,並將編譯後生成的 .so動態庫載入到 Android 應用程式中。因此實現過程分為兩部分:(1) 在Android 應用程式中編寫相關的 Java 程式碼;(2) 是使用JNI編寫原生代碼並呼叫 OpenCV 中的相關函式,然後通過 NDK 編譯生成可供 Java 程式碼呼叫的動態庫,最後通過 Android SDK 開發成應用程式。實現過程如圖 4 所示。

 

圖 4實現過程圖

主要程式碼(主要方法說明,具體見原始碼--即OpenCV-2.4.10-android-sdk(自行下載)提供的示例工程):

public class FdActivity extends Activityimplements CvCameraViewListener2 {
    //動態檢測回撥方法,進行載入庫、IO、檢測準備等操作
   private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
       @Override
       public void onManagerConnected(int status) {
           switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    //載入本地動態檢測庫,mOpenCvCameraView物件初始化後呼叫
                   System.loadLibrary("detection_based_tracker");
                    try {
                        //載入cascade分類器資源,涉及到IO
                        InputStream is =getResources().openRawResource(R.raw.lbpcascade_frontalface);
                        File cascadeDir =getDir("cascade", Context.MODE_PRIVATE);
                       mCascadeFile =new File(cascadeDir, "lbpcascade_frontalface.xml");
                        FileOutputStream os =new FileOutputStream(mCascadeFile);
                        byte[] buffer = newbyte[4096];
                        int bytesRead;
                        while ((bytesRead =is.read(buffer)) != -1) {
                            os.write(buffer, 0,bytesRead);
                        }
                        is.close();
                        os.close();
                       mJavaDetector = newCascadeClassifier(mCascadeFile.getAbsolutePath());
                        if(mJavaDetector.empty()) {
                            Log.e(TAG,"Failed to load cascade classifier");
                            mJavaDetector =null;
                        } else
                        mNativeDetector = newDetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);
                       cascadeDir.delete();
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.e(TAG, "Failedto load cascade. Exception thrown: " + e);
                    }
                   mOpenCvCameraView.enableView(); //檢測準備完畢
                } break;
                default:
                {
                   super.onManagerConnected(status);
                } break;
           }
       }
    };
    //構造方法
    public FdActivity() {…}

	//重寫方法,android資源初始化
    @Override
    public void onCreate(Bundle savedInstanceState) {…}
    //開啟攝像頭檢測人臉相關配置方法
	public voidonCameraViewStarted(int width, int height) {…}
	//停用攝像頭檢測人臉相關配置方法
	public void onCameraViewStopped(){…}
	//實現動態從攝像頭取幀讀取,轉換為灰度圖進行分析,檢測出人臉之後圈出顯示
    public Mat onCameraFrame(CvCameraViewFrameinputFrame) {
        mRgba = inputFrame.rgba();//轉化為RGB格式
        mGray = inputFrame.gray();//轉化為灰度圖
        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 (mDetectorType == JAVA_DETECTOR) {
            if (mJavaDetector != null)
		//多人臉檢測
               mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2, newSize(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
        }
        else if (mDetectorType ==NATIVE_DETECTOR) {
            if (mNativeDetector != null)
		//單個人臉檢測
                mNativeDetector.detect(mGray,faces);
        }
        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);
        return mRgba;
	}
   private void setMinFaceSize(float faceSize) {…} //設定檢測人臉大小方法
   private void setDetectorType(int type) {…} //設定檢測器型別(是否檢測多個人臉)方法
}

待修正,請見諒