Haar分类器是一个基于树的分类器,它建立了boost筛选式级联分类器。可以使用OpenCV中的“人脸”检测器来检测“基本刚性的”物体(脸,汽车,自行车,人体)。通过成千上万的物体各个角度的训练图像,训练出新的分类器、这个技术被用来设计目前最优的检测算法。因此,对于此类识别的任务,Haar分类器是一个有用的工具。
OpenCV称这个检测器为“Haar分类器”是因为它使用Haar特征或更准确的描述是类Haar的小波特征,该特征由矩形图像区域的加减组成。OpenCV包含一系列的预先训练好的物体识别文件,但也允许使用代码训练兵存储新的物体模型。除了人脸外,训练方法createsamples()和haartraining()和识别方法cvHaarDetectobject()可以用于具有纹理的近似刚性的任何物体。
Viola-Jones识别器使用AdaBoost,但是把它组织为筛选式的级联分类器,每个结点是多个树构成的分类器,且每个结点的正确识别率很高,但是正确拒绝率很低。如果一旦获得目标不在类别中的结论,则计算终止,算法也宣布该位置上没有人脸。因此,只有通过分类器中的所有级别,才会认为物体被检测到。这样的有点是当目标出现频率较低的时候,例如一幅大图中只有一幅小人脸的时候,筛选式的级联分类器可以显著地降低计算量,因为大部分被检测的区域都可以很早的被筛选掉,迅速判断出此处无人脸。
Viola-Jones的筛选式级分类器中,弱分类器是一个多数情况下只有一层的决策树,例如决策stump。一层决策树允许下面形式的决策,判断特征f的值v大于某个阈值t。yes表示可能是人脸,no则表示不是人脸。
该算法把每个boosting分类器组合成筛选式级联的一个结点。如下图:
每个结点的boosting使结点具有高通过率。例如,在检测人脸的时候,几乎所有的人脸99.9%都被检测出并允许通过,但是50%的非人脸也得以通过。这没有关系,因为20个节点使总识别库为 0.99920≈98% ,而错误接受率仅为 0.520≈0.0001%
对正确分类的样本降低权重,对错误分类的样本增大权重,有时候这会令人困惑。其中原因是boosting希望更加关注无法分类的样本,忽略已经指导如何分类的样本,boosting最大化间距。
识别时,原始图像的不同区域都会被扫描。70%~80%的非人脸区域在筛选式级联的前两个结点被拒绝,每个结点使用大约10个决策树,这种快速的早期拒绝提高了人脸检测的速度。
此方法不限于人脸检测,它适用于其他表面有区别的刚性物体的检测。正面人脸,车的前部,侧部和后部都可以用它来检测,但如果是人的侧脸和车的斜视角,则效果不好,主要是因为这些视角在模板中有很多变化,而块特征无法很好地处理这种变化。例如,为了让模型学习侧脸的曲线,侧脸边缘外的背景也会被当做有用信息进行学习。
Haar-like特征
假设在人脸检测时我们需要有这么一个子窗口在待检测的图片窗口中不断的移位滑动,子窗口每到一个位置,就会计算出该区域的特征,然后用我们训练好的级联分类器对该特征进行筛选,一旦该特征通过了所有强分类器的筛选,则判定该区域为人脸。
下面是Viola牛们提出的Haar-like特征。
下面是Lienhart等牛们提出的Haar-like特征。
这些所谓的特征不就是一堆堆带条纹的矩形么,到底是干什么用的?将上面的任意一个矩形放到人脸区域上,然后,将白色区域的像素和减去黑色区域的像素和,得到的值我们暂且称之为人脸特征值,如果你把这个矩形放到一个非人脸区域,那么计算出的特征值应该和人脸特征值是不一样的,而且越不一样越好,所以这些方块的目的就是把人脸特征量化,以区分人脸和非人脸。
为了增加区分度,可以对多个矩形特征计算得到一个区分度更大的特征值,那么什么样的矩形特征怎么样的组合到一块可以更好的区分出人脸和非人脸呢,这就是AdaBoost算法要做的事了。
OpenCV测试源代码:
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip )
{
int i = 0;
double t = 0;
//建立用于存放人脸的向量容器
vector 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, 3, 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 ::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 ::const_iterator r = faces.begin(); r != faces.end(); r++, i++ )
{
Mat smallImgROI;
vector 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 ::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 );
}
在实际应用中,还需要对有旋转角度的人脸进行旋转校正,才能检测出正确的人脸。否则,上面的代码只能检测正面,笔直的人脸。