Haar-like特征最早是由Papageorgiou等应用于人脸表示,Viola和Jones在此基础上,使用3种类型4种形式的特征。
Haar特征分为三类:边缘特征、线性特征、中心特征和对角线特征,组合成特征模板。特征模板内有白色和黑色两种矩形,并定义该模板的特征值为白色矩形像素和减去黑色矩形像素和。Haar特征值反映了图像的灰度变化情况。例如:脸部的一些特征能由矩形特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述特定走向(水平、垂直、对角)的结构。
对于图中的A, B和D这类特征,特征数值计算公式为:v=Sum白-Sum黑,而对于C来说,计算公式如下:v=Sum白-2*Sum黑;之所以将黑色区域像素和乘以2,是为了使两种矩形区域中像素数目一致。
通过改变特征模板的大小和位置,可在图像子窗口中穷举出大量的特征。上图的特征模板称为“特征原型”;特征原型在图像子窗口中扩展(平移伸缩)得到的特征称为“矩形特征”;矩形特征的值称为“特征值”。
矩形特征可位于图像任意位置,大小也可以任意改变,所以矩形特征值是矩形模版类别、矩形位置和矩形大小这三个因素的函数。故类别、大小和位置的变化,使得很小的检测窗口含有非常多的矩形特征,如:在24*24像素大小的检测窗口内矩形特征数量可以达到16万个。这样就有两个问题需要解决了:(1)如何快速计算那么多的特征?---积分图大显神通;(2)哪些矩形特征才是对分类器分类最有效的?---如通过AdaBoost算法来训练:https://www.cnblogs.com/henuliulei/p/10719208.html
积分图就是只遍历一次图像就可以求出图像中所有区域像素和的快速算法,大大的提高了图像特征值计算的效率。
积分图主要的思想是将图像从起点开始到各个点所形成的矩形区域像素之和作为一个数组的元素保存在内存中,当要计算某个区域的像素和时可以直接索引数组的元素,不用重新计算这个区域的像素和,从而加快了计算(这有个相应的称呼,叫做动态规划算法)。积分图能够在多种尺度下,使用相同的时间(常数时间)来计算不同的特征,因此大大提高了检测速度。
我们来看看它是怎么做到的。
积分图是一种能够描述全局信息的矩阵表示方法。积分图的构造方式是位置(i,j)处的值ii(i,j)是原图像(i,j)左上角方向所有像素的和:
积分图构建算法:
1)用s(i,j)表示行方向的累加和,初始化s(i,-1)=0;
2)用ii(i,j)表示一个积分图像,初始化ii(-1,i)=0;
3)逐行扫描图像,递归计算每个像素(i,j)行方向的累加和s(i,j)和积分图像ii(i,j)的值
s(i,j)=s(i,j-1)+f(i,j)
ii(i,j)=ii(i-1,j)+s(i,j)
4)扫描图像一遍,当到达图像右下角像素时,积分图像ii就构造好了。
积分图构造好之后,图像中任何矩阵区域的像素累加和都可以通过简单运算得到如图所示。
设D的四个顶点分别为α、β、γ、δ,则D的像素和可以表示为
Dsum = ii( α )+ii( β)-(ii( γ)+ii( δ ));
而Haar-like特征值无非就是两个矩阵像素和的差,同样可以在常数时间内完成。所以矩形特征的特征值计算,只与此特征矩形的端点的积分图有关,所以不管此特征矩形的尺度变换如何,特征值的计算所消耗的时间都是常量。这样只要遍历图像一次,就可以求得所有子窗口的特征值。
这个链接里的文件实现了对每个像素的haar特征值的计算:https://files.cnblogs.com/files/henuliulei/ConsoleApplication2.7z
Lienhart R.等对Haar-like矩形特征库作了进一步扩展,加入了旋转45。角的矩形特征。扩展后的特征大致分为4种类型:边缘特征、线特征环、中心环绕特征和对角线特征:
在特征值的计算过程中,黑色区域的权值为负值,白色区域的权值为正值。而且权值与矩形面积成反比(使两种矩形区域中像素数目一致);
积分图构建算法:
1)用s(i,j)表示行方向的累加和,初始化s(i,-1)=0;
2)用ii(i,j)表示一个积分图像,初始化ii(-1,i)=0;
3)逐行扫描图像,递归计算每个像素(i,j)行方向的累加和s(i,j)和积分图像ii(i,j)的值
s(i,j)=s(i,j-1)+f(i,j)
ii(i,j)=ii(i-1,j)+s(i,j)
4)扫描图像一遍,当到达图像右下角像素时,积分图像ii就构造好了。
如下图:
Sum(D)=ii(x4,y4)−ii(x2,y2)−ii(x3,y3)+ii(x1,y1)
利用opencv自带的基于haar特征的人脸识别的xml文件识别人脸代码:
1 #include
2 #include
3 //#include
4 #include
5 #include
6 #include
7 #include "opencv2/core/core.hpp"
8 #include
9
10 using namespace cv;
11 using namespace std;
12
13 #define W 1920
14 #define H 1080
15
16 string face_cascade_name = "F:\\OpenCV\\opencv\\build\\etc\\haarcascades\\haarcascade_frontalface_alt.xml";//此处只检测到了脸,检测其他器官可以添加其他的分类器
17 CascadeClassifier face_cascade;
18 int facesnum = 0;
19 void DectectorAndDis(Mat frame)
20 {
21 Mat face_gray = Mat::zeros(H, W, CV_8UC3);
22 vector faces;
23 cvtColor(frame, face_gray, CV_BGR2GRAY);//RGB转化为灰度
24 equalizeHist(face_gray, face_gray);//直方图均衡化
25 face_cascade.detectMultiScale(face_gray, faces, 1.75, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
26 for (int i = 0; i < faces.size(); i++) {
27 Point CvBox2D(int(faces[i].x + faces[i].width*0.5), int(faces[i].y + faces[i].height*0.5));
28 ellipse(frame, CvBox2D, Size(int(faces[i].width*0.5), int(faces[i].height*0.5)), 0, 0, 360, Scalar(255, 0, 0), 4, 8, 0);
29 }
30 if (facesnum != faces.size())
31 {
32 cout << "人脸数:" << faces.size() << endl;
33 facesnum = faces.size();
34 }
35
36 imshow("读取视频", frame);
37 }
38 int main(int argc, char* argv[])
39 {
40 ///cv::Mat test = cv::imread("E:/code/Myprojects/ConsoleApplication1/test.jpg",0);
41 //cv::Mat test1;
42 //resize(test, test, Size(W, H));
43 //imshow("test", test);
44 //cv::Canny(test, test1, 1, 3, 3);
45 //imshow("test1", test1);
46 //test.convertTo(test, CV_32SC1);
47
48 VideoCapture capture;
49 capture.open(0);
50
51 Mat frame = Mat::zeros(H, W, CV_8UC3);
52 Mat frameaftcanny = Mat::zeros(H, W, CV_8UC3);
53 face_cascade.load(face_cascade_name);
54 while (1)
55 {
56 capture >> frame;
57
58 //cv::Canny(frame, frameaftcanny, 100, 300, 3);
59 //imshow("边缘检测", frameaftcanny);
60 DectectorAndDis(frame);
61 cv::waitKey(50);
62 }
63 waitKey(0);
64 return 0;
65 }