開發配置
OpenCV的例程中已經帶有了人臉檢測的例程,位置在:OpenCV\samples\facedetect.cpp文件,OpenCV的安裝與這個例子的測試可以參考我之前的博文Linux 下編譯安裝OpenCV。
網上能夠找到關於OpenCV人臉檢測的例子也比較多,大多也都是基於這個例程來更改,只是多數使用的是OpenCV 1.0的版本,而OpenCV2.0以后由於模塊結構的更改,很多人並沒有將例程運行起來。如果是新版的OpenCV跑舊的例程,編譯運行出錯的話,需要確保:
- #include "opencv2/objdetect/objdetect.hpp" 頭文件被引用,老的頭文件包含可能會提示找不到定義
- libopencv_objdetect243.dll.a 庫需要加入鏈接
之前找了幾個例程,不盡如人意,於是決定還是改自帶的例程更靠譜,更多的信息,已經在程序中添加注釋,參見程序吧。
pro文件的工程配置,具體路徑按照安裝路徑更改,Linux下也一樣。
人臉檢測基礎知識整理
下面整理下人臉檢測的相關知識。
人臉檢測從整體來看分為四個部分:
1、Face detection 人臉識別,即識別出這是人的臉,而不管他是誰的。
2、Face preprocessing 面部預處理,即提取出臉部圖像。
3、Collect and learn faces 臉部的特征采集和學習
4、Face recognition 臉部識別,找出最相近的相近臉部圖像。
“基於知識的方法主要利用先驗知識將人臉看作器官特征的組合,根據眼睛、眉毛、嘴巴、鼻子等器官的特征以及相互之間的幾何位置關系來檢測人臉。基於統計的方法則將人臉看作一個整體的模式——二維像素矩陣,從統計的觀點通過大量人臉圖像樣本構造人臉模式空間,根據相似度量來判斷人臉是否存在。在這兩種框架之下,發展了許多方法。目前隨着各種方法的不斷提出和應用條件的變化,將知識模型與統計模型相結合的綜合系統將成為未來的研究趨勢。”(來自論文《基於Adaboost的人臉檢測方法及眼睛定位算法研究》)
人臉檢測算法的可靠性很大程度上依賴於分類器的設計,在2001年,Viola和Jones兩位大牛發表了經典的《Rapid Object Detection using a Boosted Cascade of Simple Features》【1】和《Robust Real-Time Face Detection》【2】,在AdaBoost算法的基礎上,使用Haar-like小波特征和積分圖方法進行人臉檢測,他倆不是最早使用提出小波特征的,但是他們設計了針對人臉檢測更有效的特征,並對AdaBoost訓練出的強分類器進行級聯。這可以說是人臉檢測史上里程碑式的一筆了,也因此當時提出的這個算法被稱為Viola-Jones檢測器。又過了一段時間,Rainer Lienhart和Jochen Maydt兩位大牛將這個檢測器進行了擴展【3】,最終形成了OpenCV現在的Haar分類器。在OpenCV2.0中又擴充了基於LBP特征的人臉檢測器,某些情況下LBP特征比Haar來的更為快速。
在進行識別時首先通過大量的具有比較明顯的haar特征(矩形)的物體圖像用模式識別的方法訓練出分類器,分類器是個級聯的,每級都以大概相同的識別率保留進入下一級的具有物體特征的候選物體,而每一級的子分類器則由許多haar特征構成(由積分圖像計算得到,並保存下位置),有水平的、豎直的、傾斜的,並且每個特征帶一個閾值和兩個分支值,每級子分類器帶一個總的閾值。識別物體的時候,同樣計算積分圖像為后面計算haar特征做准備,然后采用與訓練的時候有物體的窗口同樣大小的窗口遍歷整幅圖像,以后逐漸放大窗口,同樣做遍歷搜索物體;每當窗口移動到一個位置,即計算該窗口內的haar特征,加權后與分類器中haar特征的閾值比較從而選擇左或者右分支值,累加一個級的分支值與相應級的閾值比較,大於該閾值才可以通過進入下一輪篩選。當通過分類器所有級的時候說明這個物體以大概率被識別。
程序設計
如果單純是對功能進行實現,有了官方自帶的例程做參考,移植實現並不是很難,幾乎不用費太大的功夫,自帶例程對照着OpenCV參考手冊還是比較好理解,這部分例程已經成功在Linux(Ubuntu和嵌入式Linux)以及Windows下實現,后面實驗室基於Qt設計的實驗軟件,也整合了進去。
程序參考本文后面給出的參考程序,當然最權威的還是軟件自帶例程,實現人臉檢測的另外一個關鍵就是訓練文件,基於Haar和LBP特征的人臉檢測可以自動的對大量數據圖片進行訓練,訓練結果存儲在XML文件中以供使用,這些級聯分類器一般需要訓練上千幅人臉圖片和上萬幅非人臉圖片,這些訓練過程往往需要很長的時間(LBP特征需要幾個小時,Harr特征可能甚至需要一個星期)不過OpenCV已經提供了不同種類的訓練好的文件,因此我們可以方便的通過載入這些訓練好的級聯分類器XML文件來實現人臉、眼睛、鼻子等檢測。
OpenCV的訓練文件在源碼目錄的data文件夾下,里面包含haarcascades、hogcascades、lbpcascades,在haarcascades文件下包含大量的針對不同目標的訓練文件,如下圖所示:
文件名已經體現了文件的功能,因此只需要載入對應的文件即可。
到這里還只是進行了一個非常初步的研究,下一步的學習和識別還需要多多積累。
實驗
根據自帶例程,將人臉檢測算法加入我所做的實驗軟件中,分別選擇不同的分類器進行實驗,下圖為實驗結果。
人臉檢測實驗
實現特定的器官檢測右眼、鼻子等檢測
測試程序
QT += core QT -= gui TARGET = cvcap CONFIG += console CONFIG -= app_bundle TEMPLATE = app SOURCES += main.cpp INCLUDEPATH+=D:\OpenCV\build\include INCLUDEPATH+=D:\OpenCV\build\include\opencv LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_core243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_highgui243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_imgproc243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_video243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_objdetect243.dll.a
主程序,具體地方都已經注釋。這里是打開攝像頭讀取數據,同樣可以自己打開圖片。
#include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/objdetect/objdetect.hpp> #include <QDebug> using namespace cv; void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip ); int main() { VideoCapture cap(0); //打開默認攝像頭 if(!cap.isOpened()) { return -1; } Mat frame; Mat edges; CascadeClassifier cascade, nestedCascade; bool stop = false; //訓練好的文件名稱,放置在可執行文件同目錄下 cascade.load("haarcascade_frontalface_alt.xml"); nestedCascade.load("haarcascade_eye_tree_eyeglasses.xml"); while(!stop) { cap>>frame; detectAndDraw( frame, cascade, nestedCascade,2,0 ); if(waitKey(30) >=0) stop = true; } return 0; } void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip ) { int i = 0; double t = 0; //建立用於存放人臉的向量容器 vector<Rect> faces, faces2; //定義一些顏色,用來標示不同的人臉 const static Scalar colors[] = { CV_RGB(0,0,255), CV_RGB(0,128,255), CV_RGB(0,255,255), CV_RGB(0,255,0), CV_RGB(255,128,0), CV_RGB(255,255,0), CV_RGB(255,0,0), CV_RGB(255,0,255)} ; //建立縮小的圖片,加快檢測速度 //nt cvRound (double value) 對一個double型的數進行四舍五入,並返回一個整型數! Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); //轉成灰度圖像,Harr特征基於灰度圖 cvtColor( img, gray, CV_BGR2GRAY ); //改變圖像大小,使用雙線性差值 resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); //變換后的圖像進行直方圖均值化處理 equalizeHist( smallImg, smallImg ); //程序開始和結束插入此函數獲取時間,經過計算求得算法執行時間 t = (double)cvGetTickCount(); //檢測人臉 //detectMultiScale函數中smallImg表示的是要檢測的輸入圖像為smallImg,faces表示檢測到的人臉目標序列,1.1表示 //每次圖像尺寸減小的比例為1.1,2表示每一個目標至少要被檢測到3次才算是真的目標(因為周圍的像素和不同的窗口大 //小都可以檢測到人臉),CV_HAAR_SCALE_IMAGE表示不是縮放分類器來檢測,而是縮放圖像,Size(30, 30)為目標的 //最小最大尺寸 cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30)); //如果使能,翻轉圖像繼續檢測 if( tryflip ) { flip(smallImg, smallImg, 1); cascade.detectMultiScale( smallImg, faces2, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); r++ ) { faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height)); } } t = (double)cvGetTickCount() - t; // qDebug( "detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.) ); for( vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++ ) { Mat smallImgROI; vector<Rect> nestedObjects; Point center; Scalar color = colors[i%8]; int radius; double aspect_ratio = (double)r->width/r->height; if( 0.75 < aspect_ratio && aspect_ratio < 1.3 ) { //標示人臉時在縮小之前的圖像上標示,所以這里根據縮放比例換算回去 center.x = cvRound((r->x + r->width*0.5)*scale); center.y = cvRound((r->y + r->height*0.5)*scale); radius = cvRound((r->width + r->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } else rectangle( img, cvPoint(cvRound(r->x*scale), cvRound(r->y*scale)), cvPoint(cvRound((r->x + r->width-1)*scale), cvRound((r->y + r->height-1)*scale)), color, 3, 8, 0); if( nestedCascade.empty() ) continue; smallImgROI = smallImg(*r); //同樣方法檢測人眼 nestedCascade.detectMultiScale( smallImgROI, nestedObjects, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH //|CV_HAAR_DO_CANNY_PRUNING |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++ ) { center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale); center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale); radius = cvRound((nr->width + nr->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } } cv::imshow( "result", img ); }