開發配置
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 ); }