人臉偵測Face Detection 使用OpenCV 2.4.2

張貼者: Mark | 晚上8:29

人臉偵測-我記得最早從數位相機興起的人臉偵測,那時候好像是買了SONY的相機,它因為有人臉偵測與微笑拍照的功能,從準備拍照到人臉追蹤到微笑拍照,直到拍照之前焦距都會對準到人臉,拍完照片才不會有失焦的情形發生,讓我愛不釋手,到最近工作上使用到才了解,由一篇Paper[Robust Real-Time Face Detection]由 Paul Viola, MICHAEL J. JONES 著作,這篇為2004年在International Journal of Computer Vision會議上發表的論文,也因為這篇論文被引用到多達6000次紀錄,Intel公司也引用這篇論文的方法而做成了OpenCV使用到的人臉偵測相關範例。

imageimage

利用在本篇 Paper 裡 Viola 等人利用了三個演算法,來快速找到人臉。第一個是 Integral Image,第二個是 Ada boost,第三個是 Cascade classifier。這些我們在下面將一
一解釋。

Integral Image
所謂的Integral Image的概念,如Figure 1所示,點(x, y)位置的值為左上角所有灰
色方塊範圍內的pixel的值的加總。

imageFigure 1. Integral Image

imageFigure 2. Rectangle Feature


有了Integral Image值的定義以後,對於一張Image我們會有小小的類似Figure 2的
Feature,這些Feature我們稱之為rectangle feature,這些rectangle feature的大小不
固定,但是白色的方塊一定和灰色的方塊一樣大。這些方塊會像Filter一樣,在
一張Image之中移動。而對於每一個Feature值的計算,就是將白色的Integral的值
減掉灰色方框Integral的值。對於這樣的方式,為何可以找到 Image 中想要的 Feature,可以利用 Simard et al等人的證明來解釋。這個部份可以直接參閱 paper 第四頁的部份。

Ada Boost
-AdaBoost全名為Adaptive Boosting。 AdaBoost是一种迭代算法,其核心思想是針對同一個訓練集(training set)訓練不同的分類器(弱分類器),然後把這些弱分類器集合起來,構成一個更強的最终分類器(強分類器)。

Ada Boost結合Rectangle feature在人臉辨識的algorithm如Algorithm 1所示。首先會
有一堆training的image,分別標示著人臉m張以及非人臉l張。對於每一張image
我們一開始分別給他們 1/m或是 1/l的weight,端看它們是人臉或是非人臉。
接著我們要從一大堆 rectangle feature 裡面取出 T 個 feature 來。因此對下面的步
驟,我們會重複 T 次。
1. 首先將所有的 weight normalize 成加起來為 1。
2. 根據hypotheses function,選出一個error值最小的feature。關於這個
function如Algorithm 1所示。
3. 紀錄可以使 error 值最小的參數。
4. 將weight根據Algorithm 1的公式update。
最後我們求出來的 classifier 就是由 T 個 weak feature 所組成的。因此對於丟入的
一張 image,就會由這 T 個 feature 來投票,每一個 feature 的投票的權重不太一
樣。但只有當加權值大於一半以上的所有分數時,才會認可這一張 image為人臉。
imageAlgorithm 1. Ada Boost in face detection

Cascade Classifier
對於Cascade classifier的概念,就如Figure 3所示。我們一開始將feature分成好幾
個classifier。最前面的classier辨識率最低,但是可以先篩選掉很大一部份不是人
臉的圖片;接下來的Classier處理比較難處理一點的case篩選掉的圖片也不如第一
個classier多了;依此下去,直到最後一個classier為止。最後留下來的就會是我們
想要的人臉的照片。

imageFigure 3. Cascade classifier

然而應該要決定多少個 classifier 呢?這個問題決定於我們所設定的 false positive
rate 以及 detection rate 而定。所謂的 false positive rate 就是我們將人臉的圖片誤
判成不是人臉的圖片的機率。而所謂的 detection rate 則正確找到人臉的準確率。
通常這兩個之間會有 trade off,如果我們想要達到比較高的 detection rate,那麼
false positive rate 可能就會比較高一點;而如果想達到比較低的 false positive
rate,那麼正確率難免就會下降。
整個選取cascade classifier的演算法如Algorithm 2所示。首先我們要決定每一個階
層的classifier的false positive rate以及detection rate。然後我們要決定一個target的
false positive rate以及target detection rate,當所有的整體的false positive rate以及
detection rate達到設定的值以後才會停止。因此對於每一個階層,我們就要選足
夠多的feature來達到false positive rate以及detection rate。

imageAlgorithm 2. Cascade classifier in face detection

OpenCV: (Cascade Classification)Haar Feature-based CascadeClassifier for Object Detection

檢測方法最初由Paul Viola [Viola01]提出,並由Rainer Lienhart [Lienhart02]對這一方法進行了改善. 首先,利用樣本(大約幾百幅樣本圖片)的 harr 特征進行分類器訓練,得到一個級聯的boosted分類器。訓練樣本分為正例樣本和反例樣本,其中正例樣本是指待檢目標樣本(例如人臉或汽車等),反例樣本指其它任意圖片,所有的樣本圖片都被歸一化為同樣的尺寸大小(例如,20x20)。

分類器訓練完以後,就可以應用於輸入圖像中的感興趣區域(與訓練樣本相同的尺寸)的檢測。檢測到目標區域(汽車或人臉)分類器輸出為1,否則輸出為0。為了檢測整副圖像,可以在圖像中移動搜索視窗,檢測每一個位置來確定可能的目標。 為了搜索不同大小的目標物體,分類器被設計為可以進行尺寸改變,這樣比改變待檢圖像的尺寸大小更為有效。所以,為了在圖像中檢測未知大小的目標物體,掃描程式通常需要用不同比例大小的搜索視窗對圖片進行幾次掃描。

分類器中的“級聯”(cascade)是指最終的分類器是由幾個簡單分類器級聯組成。在圖像檢測中,被檢視窗依次通過每一級分類器,這樣在前面幾層的檢測中大部分的候選區域就被排除了,全部通過每一級分類器檢測的區域即為目標區域,目前支持這種分類器的boosting技術有四種: Discrete Adaboost, Real Adaboost, Gentle Adaboost and Logitboost。"boosted" 即指級聯分類器的每一層都可以從中選取一個boosting演算法(權重投票),並利用基礎分類器的自我訓練得到。基礎分類器是至少有兩個葉結點的決策樹分類器。 Haar特征是基礎分類器的輸入,主要描述如下。目前的演算法主要利用下面的Harr特征:

。/。。/。。/ _images / haarfeatures.png

每個特定分類器所使用的特征用形狀、感興趣區域中的位置以及比例繫數(這裡的比例繫數跟檢測時候採用的比例繫數是不一樣的,儘管最後會取兩個繫數的乘積值)來定義。例如在第二行特征(2c)的情況下,響應計算為覆蓋全部特征整個矩形框(包括兩個白色矩形框和一個黑色矩形框)象素的和減去黑色矩形框內象素和的三倍 。每個矩形框內的象素和都可以通過積分圖象很快的計算出來。(察看下麵和對cvIntegral的描述)。

上述都在說明face detection理論的部份,OpenCV就是採用論文的方法加以改進,至於要如何在OpenCV 2.4.2使用face detection呢?方法如下:

1.找尋檔案

objectDetection.cpp (C:\OpenCV_2.4.2\samples\cpp\tutorial_code\objectDetection)

2.新建專案

使用visual 2010新建一個空的win32專案,並加入objectDetection.cpp

3.找尋訓練集

C:\OpenCV_2.4.2\data\haarcascades

  • 人臉訓練集:haarcascade_frontalface_alt.xml
  • 人眼訓練集:haarcascade_eye_tree_eyeglasses.xml

還有嘴巴、鼻子、身體、上半身、下半身等訓練集。

4.改寫程式碼

原本的程式碼只能使用webcam來擷取影像,需改寫為讀取圖檔來獲取影像。

5.執行結果

image

改寫後的程式碼如下:

/**
* @file objectDetection.cpp
* @author A. Huaman ( based in the classic facedetect.cpp in samples/c )
* @brief A simplified version of facedetect.cpp, show how to load a cascade classifier and how to find objects (Face + eyes) in a video stream
*/
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/** Function Headers */
void detectAndDisplay( Mat frame );

/** Global variables */
//-- Note, either copy these two files from opencv/data/haarscascades to your current folder, or change these locations
String face_cascade_name = "haarcascades/haarcascade_frontalface_alt.xml";
String eyes_cascade_name = "haarcascades/haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);
IplImage* pImg=0;

/**
* @function main
*/
int main( int argc, const char** argv )
{
//CvCapture* capture;
//Mat frame;
//IplImage* pImg=0;

//-- 1. Load the cascades

if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };
if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };

//-- 2. 讀入圖像

//pImg=cvLoadImage("E:/PROGS/Porjects/OpenCV/OpenCVtest3/IMAG0332.jpg", CV_LOAD_IMAGE_COLOR); //暫存輸入影像
pImg=cvLoadImage("E:/PROGS/Porjects/OpenCV/OpenCVtest1/lena.jpg", CV_LOAD_IMAGE_COLOR); //暫存輸入影像

//如果讀入圖像失敗
if(!pImg)
{
printf("Could not load image file: %s\n");
}
//顯示圖像
cvShowImage("mainWin", pImg );

//-- 3. 人臉偵測

Mat frame(pImg, 0); //IplImage to Mat , 0是不複製影像,也就是pImg與img的data共用同個記憶體位置,header各自有

detectAndDisplay( frame ); //人臉偵測函式

//等待按鍵,按鍵盤任意鍵返回
cv::waitKey(0);

return 0;

//-- 2. Read the video stream
/*capture = cvCaptureFromCAM( -1 );
if( capture )
{
while( true )
{
frame = cvQueryFrame( capture );

//-- 3. Apply the classifier to the frame
if( !frame.empty() )
{ detectAndDisplay( frame ); }
else
{ printf(" --(!) No captured frame -- Break!"); break; }

int c = waitKey(10);
if( (char)c == 'c' ) { break; }

}
}
return 0;*/
}

/**
* @function detectAndDisplay
*/
void detectAndDisplay( Mat frame )
{
std::vector<Rect> faces;
Mat frame_gray;
Mat faceROI;

cvtColor( frame, frame_gray, CV_BGR2GRAY );
equalizeHist( frame_gray, frame_gray );
//-- Detect faces
face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );

for( int i = 0; i < faces.size(); i++ )
{
Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar( 255, 0, 255 ), 2, 8, 0 );

faceROI = frame_gray( faces[i] ); //detect Face answer

std::vector<Rect> eyes;

//-- In each face, detect eyes
eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) );

for( int j = 0; j < eyes.size(); j++ )
{
Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5, faces[i].y + eyes[j].y + eyes[j].height*0.5 );
int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
circle( frame, center, radius, Scalar( 255, 0, 0 ), 3, 8, 0 );
}
}


   cvShowImage("window", pImg );
}

分享好文: 讚 ! 分享到塗鴉牆
0 意見