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) {…} //設定檢測器型別(是否檢測多個人臉)方法
}
待修正,請見諒