背景介绍
iPhone十周年纪念之作iPhoneX刚刚发布,其搭载的“刷脸解锁”功能再次将“人脸识别”技术带入大众视野。
借iPhoneX的东风,今天给大家介绍一下人脸检测的关键特征:Haar特征,并讲解如何快速计算待检测图像对应的积分图。
iPhoneX
Haar特征
想象一下现在你手上有一张图像需要用来做人脸检测,在人脸检测时有一个子窗口在待检测的图片中不断地移动,计算出对应位置的特征。将计算出的特征送到人脸分类器(本文主要讲解Haar特征及其计算,分类器的训练不涉及)中进行判断,通过筛选的区域则判断为人脸,反之则不是人脸。
那么,这个特征如何表示呢?
Viola、Lienhart等大牛提出了Haar特征。
其实,Haar特征本身并不复杂,就是用图中黑色矩形区域内所有像素值的和减去白色矩形区域内所有像素值的和,得到的值称为人脸特征值,如果Haar矩形放到非人脸区域,那么计算出的值和人脸特征值是不一样的。
但是,在实际使用Haar特征的过程中我们发现,Haar矩形特征是与矩形模版类别、矩形位置和矩形大小这三个变量的函数。随着矩形模板类别、大小和位置的变化,使得在检测的过程中会产生大量的特征值,如:在24*24像素大小的检测窗口内产生的矩形特征数量就超过10万个了。那么,如何可以快速计算出大量的Haar特征值呢?
下面,就需要介绍人脸检测中的神器——积分图!
积分图
首先给出积分图的定义:对于一张积分图ii(i,j),其位置(i,j)处的值ii(i,j)是是原图像i(i,j)左上角方向所有像素的和。
积分图的构建算法:
1. 用s(i,j)表示行方向的累加和,初始化s(i,-1)=0;
2. 用ii(i,j)表示一个积分图像,初始化ii(-1,j)=0;
3. 逐行扫描图像,计算每个像素(i,j)行方向的累加和s(i,j)和积分图像ii(i,j)的值:
s(i,j)=s(i,j-1)+i(i,j)
ii(i,j)=ii(i-1,j)+s(i,j)
4. 扫描图像一遍,当到达图像右下角像素时,积分图像ii就构造好了。
构造好积分图后,记图像中某一矩形区域A的四个顶点为a,b,c,d(左上角标为a,其余顶点按照顺时针次序标记),矩形区域A的像素和S(比如用某个Haar特征模板扫描图像时,需要计算白色区域或黑色区域的像素和)就可以这样计算:S=ii(a)+ii(c)-ii(b)-ii(d)(建议读者自行画图体会这个公式的妙处~)
这样,对于任意大小的矩形模板,只需查找积分图像4次就可以求得任意矩形的像素值的和,大大减少了计算量!
同时,只需要遍历一边图像,就可以计算得到所有子窗口的的Haar特征值,真可谓神器也!
#include <iostream>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include "opencv2/objdetect/objdetect.hpp"using namespace cv;using namespace std;string face_cascade_name = "haarcascade_frontalface_alt2.xml";CascadeClassifier face_cascade;string window_name = "人脸实时检测程序";void detectAndDisplay(Mat frame){ std::vector<Rect> faces; Mat frame_gray; cvtColor(frame, frame_gray, CV_BGR2GRAY); equalizeHist(frame_gray, frame_gray); 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), 4, 8, 0); } imshow(window_name, frame);}int main(int argc, char* argv[]){ VideoCapture cap(0); if (!cap.isOpened()) return -1; Mat edges; //namedWindow("edges", 1); if (!face_cascade.load(face_cascade_name)){ printf("[error] 无法加载级联分类器文件!\n"); return -1; } int nTick = 0; for (;;) { if (!cap.isOpened()) {//等等摄像头打开 continue; } Mat frame; nTick = getTickCount(); cap >> frame; // get a new frame from camera if (frame.data == NULL) {//等到捕获到数据 continue; } cvtColor(frame, edges, CV_BGR2BGRA); detectAndDisplay(edges); if (waitKey(30) >= 0) break; } return 0;}
运行效果
(PS.保护舍友安全,舍友太帅,已经有女朋友了......)
联系客服