人脸检测属于计算机视觉的范畴,早期人们的主要研究方向是人脸识别,即根据人脸来识别人物的身份,后来在复杂背景下的人脸检测需求越来越大,人脸检测也逐渐作为一个单独的研究方向发展起来。
目前人脸检测的方法主要有两大类:基于知识和基于统计。
基于知识的方法 :主要利用先验知识将人脸看作器官特征的组合,根据眼睛、眉毛、嘴巴、鼻子等器官的特征以及相互之间的几何位置关系来检测人脸。主要包括模板匹配、人脸特征、形状与边缘、纹理特性、颜色特征等方法。
基于统计的方法 :将人脸看作一个整体的模式——二维像素矩阵,从统计的观点通过大量人脸图像样本构造人脸模式空间,根据相似度量来判断人脸是否存在。主要包括主成分分析与特征脸、神经网络方法、支持向量机、隐马尔可夫模型、Adaboost算法等。
本文中介绍的Haar分类器方法,包含了Adaboost算法,稍候会对这一算法做详细介绍。所谓分类器,在这里就是指对人脸和非人脸进行分类的算法,在机器学习领域,很多算法都是对事物进行分类、聚类的过程。
我们要探讨的Haar分类器实际上是Boosting算法的一个应用,Haar分类器用到了Boosting算法中的AdaBoost算法,只是把AdaBoost算法训练出的强分类器进行了级联,并且在底层的特征提取中采用了高效率的矩形特征和积分图方法,这里涉及到的几个名词接下来会具体讨论。
在2001年,Viola和Jones两位大牛发表了经典的《Rapid Object Detection using a Boosted Cascade of Simple Features》和《Robust Real-Time Face Detection》,在AdaBoost算法的基础上,使用Haar-like小波特征和积分图方法进行人脸检测,他俩不是最早使用提出小波特征的,但是他们设计了针对人脸检测更有效的特征,并对AdaBoost训练出的强分类器进行级联。这可以说是人脸检测史上里程碑式的一笔了,也因此当时提出的这个算法被称为Viola-Jones检测器。又过了一段时间,Rainer Lienhart和Jochen Maydt两位大牛将这个检测器进行了扩展,最终形成了OpenCV现在的Haar分类器。
AdaBoost是Freund和Schapire在1995年提出的算法,是对传统Boosting算法的一大提升。Boosting算法的核心思想,是将弱学习方法提升成强学习算法,也就是“三个臭皮匠顶一个诸葛亮”。
Haar分类器 = Haar-like特征 + 积分图方法 + AdaBoost +级联;
Haar分类器算法的要点如下:
使用Haar-like特征做检测。 使用积分图(Integral Image)对Haar-like特征求值进行加速。 使用AdaBoost算法训练区分人脸和非人脸的强分类器。 使用筛选式级联把强分类器级联到一起,提高准确率。
一 Haar-like特征
Haar(哈尔)特征分为三类:边缘特征、线性特征、中心特征和对角线特征,组合成特征模板。特征模板内有白色和黑色两种矩形,并定义该模板的特征值为白色矩形像素和减去黑色矩形像素和。Haar特征值反映了图像的灰度变化情况。例如:脸部的一些特征能由矩形特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述特定走向(水平、垂直、对角)的结构。
对于图中的A, B和D这类特征,特征数值计算公式为:v=Σ白-Σ黑,而对于C来说,计算公式如下:v=Σ白-2*Σ黑;之所以将黑色区域像素和乘以2,是为了使两种矩形区域中像素数目一致。我们希望当把矩形放到人脸区域计算出来的特征值和放到非人脸区域计算出来的特征值差别越大越好,这样就可以用来区分人脸和非人脸。
通过改变特征模板的大小和位置,可在图像子窗口中穷举出大量的特征。上图的特征模板称为“特征原型”;特征原型在图像子窗口中扩展(平移伸缩)得到的特征称为“矩形特征”;矩形特征的值称为“特征值”。
上图中两个矩形特征,表示出人脸的某些特征。比如中间一幅表示眼睛区域的颜色比脸颊区域的颜色深,右边一幅表示鼻梁两侧比鼻梁的颜色要深。同样,其他目标,如眼睛等,也可以用一些矩形特征来表示。使用特征比单纯地使用像素点具有很大的优越性,并且速度更快。
矩形特征可位于图像任意位置,大小也可以任意改变,所以矩形特征值是矩形模版类别、矩形位置和矩形大小这三个因素的函数。故类别、大小和位置的变化,使得很小的检测窗口含有非常多的矩形特征,如:在24*24像素大小的检测窗口内矩形特征数量可以达到16万个。这样就有两个问题需要解决了:(1)如何快速计算那么多的特征?---积分图大显神通;(2)哪些矩形特征才是对分类器分类最有效的?---如通过AdaBoost算法来训练。
二、Haar-like特征的计算—积分图
积分图就是只遍历一次图像就可以求出图像中所有区域像素和的快速算法,大大的提高了图像特征值计算的效率。
积分图主要的思想是将图像从起点开始到各个点所形成的矩形区域像素之和作为一个数组的元素保存在内存中,当要计算某个区域的像素和时可以直接索引数组的元素,不用重新计算这个区域的像素和,从而加快了计算(这有个相应的称呼,叫做动态规划算法)。积分图能够在多种尺度下,使用相同的时间(常数时间)来计算不同的特征,因此大大提高了检测速度。
积分图是一种能够描述全局信息的矩阵表示方法。积分图的构造方式是位置( ? , ? ) (i,j) 在使用Adaboost算法训练分类器之前,需要准备好正、负样本,根据样本特点选择和构造特征集。由算法的训练过程可知,当弱分类器对样本分类正确,样本的权重会减小;而分类错误时,样本的权重会增加。这样,后面的分类器会加强对错分样本的训练。最后,组合所有的弱分类器形成强分类器,通过比较这些弱分类器投票的加权和与平均投票结果来检测图像。
3、级联分类器的检测
训练级联分类器的目的就是为了检测的时候,更加准确,这涉及到Haar分类器的另一个体系,检测体系,检测体系是以现实中的一幅大图片作为输入,然后对图片中进行多区域,多尺度的检测,所谓多区域,是要对图片划分多块,对每个块进行检测,由于训练的时候用的照片一般都是20*20左右的小图片,所以对于大的人脸,还需要进行多尺度的检测,多尺度检测机制一般有两种策略:
一种是不改变搜索窗口的大小,而不断缩放图片,这种方法显然需要对每个缩放后的图片进行区域特征值的运算,效率不高; 另一种方法,不断扩大搜索窗口,进行搜索,解决了第一种方法的弱势。
在区域放大的过程中会出现同一个人脸被多次检测,这需要进行区域的合并,这里不作探讨。
无论哪一种搜索方法,都会为输入图片输出大量的子窗口图像,这些子窗口图像经过筛选式级联分类器会不断地被每一个节点筛选,抛弃或通过。
4、总结
从上面所述内容我们可以总结Haar分类器训练的五大步骤:
1、准备人脸、非人脸样本集;
2、计算特征值和积分图;
3、筛选出T个优秀的特征值(即最优弱分类器);
4、把这个T个最优弱分类器传给AdaBoost进行训练。
5、级联,也就是强分类器的强强联手。
在开始前,一定要记住,以20*20窗口为例,就有78,460的特征数量,筛选出T个优秀的特征值(即最优弱分类器),然后把这个T个最优弱分类器传给AdaBoost进行训练得到一个强分类器,最后将强分类器进行级联。
5、XML文件
OpenCV 自带了训练器和检测器。如果你想自己训练一个分类器来检测汽车,飞机等的话,可以使用 OpenCV 构建。其中的细节参考这里:Cascade Classifier Training 。这里我们介绍的XML文件,就是OpenCV自带的检测器,在OpenCV 3的库文件中会包含一个文件夹haarcascades,在我的电脑上路径为D:\Anaconda\pkgs\opencv-3.3.1-py36h20b85fd_1\Library\etc\haarcascades。在这个文件夹下包含了OpenCV的人脸检测的XML文件,这些文件可用于检测静止图像,视频和摄像头所得到图像中的人脸。除此之外还有一个文件夹是lbpcascades,它不是通过Haar特征进行人脸检测,而是采用的LBP特征。
从这些文件名可以知道这些级联适用于检测人脸、眼睛、鼻子和嘴等部位的跟踪,这些文件需要正面、直立的人体图像。
xml文件主要保存相关的特征矩阵,以及各个弱分类器相关的信息,关于各个节点的具体含义可以参考文章haar+adaboost结合讲解(偏重实际) ,这里不做过多的介绍。
六 人脸检测
在这里我们将会学习到使用级联分类器进行人脸检测。在静态图像或视频中检测人脸的操作非常相似。视频人脸检测知识从摄像头读出毎帧图像,然后采用静态图像中的人脸检测方法进行检测,当然,视频人脸检测还涉及其他概念,例如跟踪,而静态图像中人脸检测就没有这样的概念,但是它们的基本理论是一致的。
1、静态图像中的人脸检测
我们首先把haarcascades文件夹复制到当前项目路径下,然后创建.py文件,代码如下:
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 16 10:32:55 2018
@author: lenovo “”"
‘’’ 人脸检测 ‘’’ import cv2 import numpy as np
#1.静态图像中的人脸检测 def StaticDetect(filename): #创建一个级联分类器 加载一个 .xml 分类器文件. 它既可以是Haar特征也可以是LBP特征的分类器. face_cascade = cv2.CascadeClassifier(’./haarcascades/haarcascade_frontalface_default.xml’)
</span><span style="color: #008000;">#</span><span style="color: #008000;">加载图像</span>
img =<span style="color: #000000;"> cv2.imread(filename)
</span><span style="color: #008000;">#</span><span style="color: #008000;">转换为灰度图</span>
gray_img =<span style="color: #000000;"> cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
</span><span style="color: #008000;">#</span><span style="color: #008000;">进行人脸检测,传入scaleFactor,minNegihbors,分别表示人脸检测过程中每次迭代时图像的压缩率以及</span>
<span style="color: #008000;">#</span><span style="color: #008000;">每个人脸矩形保留近似数目的最小值</span>
<span style="color: #008000;">#</span><span style="color: #008000;">返回人脸矩形数组</span>
faces = face_cascade.detectMultiScale(gray_img,1.3,5<span style="color: #000000;">)
</span><span style="color: #0000ff;">for</span> (x,y,w,h) <span style="color: #0000ff;">in</span><span style="color: #000000;"> faces:
</span><span style="color: #008000;">#</span><span style="color: #008000;">在原图像上绘制矩形</span>
img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2<span style="color: #000000;">)
cv2.namedWindow(</span><span style="color: #800000;">'</span><span style="color: #800000;">Face Detected!</span><span style="color: #800000;">'</span><span style="color: #000000;">)
cv2.imshow(</span><span style="color: #800000;">'</span><span style="color: #800000;">Face Detected!</span><span style="color: #800000;">'</span><span style="color: #000000;">,img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#2、视频中的人脸检测 def DynamicDetect(): ‘’’ 打开摄像头,读取帧,检测帧中的人脸,扫描检测到的人脸中的眼睛,对人脸绘制蓝色的矩形框,对人眼绘制绿色的矩形框 ‘’’ #创建一个级联分类器 加载一个 .xml 分类器文件. 它既可以是Haar特征也可以是LBP特征的分类器. face_cascade = cv2.CascadeClassifier(’./haarcascades/haarcascade_frontalface_default.xml’) eye_cascade = cv2.CascadeClassifier(’./haarcascades/haarcascade_eye.xml’)
</span><span style="color: #008000;">#</span><span style="color: #008000;">打开摄像头 </span>
camera =<span style="color: #000000;"> cv2.VideoCapture(0)
cv2.namedWindow(</span><span style="color: #800000;">'</span><span style="color: #800000;">Dynamic</span><span style="color: #800000;">'</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">while</span><span style="color: #000000;">(True):
</span><span style="color: #008000;">#</span><span style="color: #008000;">读取一帧图像</span>
ret,frame =<span style="color: #000000;"> camera.read()
</span><span style="color: #008000;">#</span><span style="color: #008000;">判断图片读取成功?</span>
<span style="color: #0000ff;">if</span><span style="color: #000000;"> ret:
gray_img </span>=<span style="color: #000000;"> cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
</span><span style="color: #008000;">#</span><span style="color: #008000;">人脸检测</span>
faces = face_cascade.detectMultiScale(gray_img,1.3,5<span style="color: #000000;">)
</span><span style="color: #0000ff;">for</span> (x,y,w,h) <span style="color: #0000ff;">in</span><span style="color: #000000;"> faces:
</span><span style="color: #008000;">#</span><span style="color: #008000;">在原图像上绘制矩形</span>
cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2<span style="color: #000000;">)
roi_gray </span>= gray_img[y:y+h,x:x+<span style="color: #000000;">w]
</span><span style="color: #008000;">#</span><span style="color: #008000;">眼睛检测</span>
eyes = eye_cascade.detectMultiScale(roi_gray,1.03,5,0,(40,40<span style="color: #000000;">))
</span><span style="color: #0000ff;">for</span> (ex,ey,ew,eh) <span style="color: #0000ff;">in</span><span style="color: #000000;"> eyes:
cv2.rectangle(frame,(ex</span>+x,ey+y),(x+ex+ew,y+ey+eh),(0,255,0),2<span style="color: #000000;">)
cv2.imshow(</span><span style="color: #800000;">'</span><span style="color: #800000;">Dynamic</span><span style="color: #800000;">'</span><span style="color: #000000;">,frame)
</span><span style="color: #008000;">#</span><span style="color: #008000;">如果按下q键则退出</span>
<span style="color: #0000ff;">if</span> cv2.waitKey(100) & 0xff == ord(<span style="color: #800000;">'</span><span style="color: #800000;">q</span><span style="color: #800000;">'</span><span style="color: #000000;">) :
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">
camera.release()
cv2.destroyAllWindows()
if name ==‘main ’: #filename = ‘./image/img23.jpg’ #StaticDetect(filename) DynamicDetect()
我们来分析一下StaticDetect函数,首先创建一个级联分类器对象,然后加载xml检测器,用来进行人脸检测。
#创建一个级联分类器 加载一个 .xml 分类器文件. 它既可以是Haar特征也可以是LBP特征的分类器.
face_cascade = cv2.CascadeClassifier('./haarcascades/haarcascade_frontalface_default.xml')
然后加载图片文件,并将其转换为灰度图像,因为人脸检测需要这样的色彩空间。
#加载图像
img = cv2.imread(filename)
#转换为灰度图
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
接下来进行人脸检测,需要传入scaleFactor和minNeighbors参数,它们分别表示人脸检测过程中每次迭代时图像的压缩率以及每个人脸矩形保留近似数目的最小值。然后函数返回人脸矩阵数组。我们利用cv2.rectangle函数在原图中把矩形绘制出来。
#进行人脸检测,传入scaleFactor,minNegihbors,分别表示人脸检测过程中每次迭代时图像的压缩率以及
#每个人脸矩形保留近似数目的最小值
#返回人脸矩形数组
faces = face_cascade.detectMultiScale(gray_img,1.3,5)
for (x,y,w,h) in faces:
#在原图像上绘制矩形
img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
cv2.namedWindow('Face Detected!')
cv2.imshow('Face Detected!',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如下,我们可以看到所有的人脸都检测出来了,但是其中还有一个误检测:
2、视频中的人脸检测
上面已经介绍了在静态图像上进行人脸检测,在视频帧上重复进行这个过程就能完成视频中的人脸检测。DynamicDetect函数主要包括:打开摄像头、读取帧、检测人脸、扫描检测到的人脸中的眼睛,并使用不同颜色绘制出矩形框。
#2、视频中的人脸检测
def DynamicDetect():
'''
打开摄像头,读取帧,检测帧中的人脸,扫描检测到的人脸中的眼睛,对人脸绘制蓝色的矩形框,对人眼绘制绿色的矩形框
'''
#创建一个级联分类器 加载一个 .xml 分类器文件. 它既可以是Haar特征也可以是LBP特征的分类器.
face_cascade = cv2.CascadeClassifier('./haarcascades/haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('./haarcascades/haarcascade_eye.xml')
</span><span style="color: #008000;">#</span><span style="color: #008000;">打开摄像头 </span>
camera =<span style="color: #000000;"> cv2.VideoCapture(0)
cv2.namedWindow(</span><span style="color: #800000;">'</span><span style="color: #800000;">Dynamic</span><span style="color: #800000;">'</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">while</span><span style="color: #000000;">(True):
</span><span style="color: #008000;">#</span><span style="color: #008000;">读取一帧图像</span>
ret,frame =<span style="color: #000000;"> camera.read()
</span><span style="color: #008000;">#</span><span style="color: #008000;">判断图片读取成功?</span>
<span style="color: #0000ff;">if</span><span style="color: #000000;"> ret:
gray_img </span>=<span style="color: #000000;"> cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
</span><span style="color: #008000;">#</span><span style="color: #008000;">人脸检测</span>
faces = face_cascade.detectMultiScale(gray_img,1.3,5<span style="color: #000000;">)
</span><span style="color: #0000ff;">for</span> (x,y,w,h) <span style="color: #0000ff;">in</span><span style="color: #000000;"> faces:
</span><span style="color: #008000;">#</span><span style="color: #008000;">在原图像上绘制矩形</span>
cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2<span style="color: #000000;">)
roi_gray </span>= gray_img[y:y+h,x:x+<span style="color: #000000;">w]
</span><span style="color: #008000;">#</span><span style="color: #008000;">眼睛检测</span>
eyes = eye_cascade.detectMultiScale(roi_gray,1.03,5,0,(40,40<span style="color: #000000;">))
</span><span style="color: #0000ff;">for</span> (ex,ey,ew,eh) <span style="color: #0000ff;">in</span><span style="color: #000000;"> eyes:
cv2.rectangle(frame,(ex</span>+x,ey+y),(x+ex+ew,y+ey+eh),(0,255,0),2<span style="color: #000000;">)
cv2.imshow(</span><span style="color: #800000;">'</span><span style="color: #800000;">Dynamic</span><span style="color: #800000;">'</span><span style="color: #000000;">,frame)
</span><span style="color: #008000;">#</span><span style="color: #008000;">如果按下q键则退出</span>
<span style="color: #0000ff;">if</span> cv2.waitKey(100) & 0xff == ord(<span style="color: #800000;">'</span><span style="color: #800000;">q</span><span style="color: #800000;">'</span><span style="color: #000000;">) :
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">
camera.release()
cv2.destroyAllWindows()</span></pre>
这里和上面有些类似,只是在进行眼睛检测的时候多了几个参数。detectMultiScale有许多可选参数;在人脸检测时,默认选项足以检测人脸,但是眼睛是一个比较小的人脸特征,并且胡子或者鼻子的本身阴影以及帧的随机阴影都会产生假阳性。通过限制对眼睛搜索的最小尺寸为40x40像素,可以去掉假阳性。然后测试这些参数,直至应用程序可以满足预期(例如可以尝试指定特征的最大尺寸,或增加比例因子以及近邻的数量)。
下面我们来总结一下detectMultiScale函数:
detectMultiScale(image[,scaleFactor[,minNeighbors[,flags[,minSize[,maxSize]]]]])
image:表示的是要检测的输入图像 scaleFactor:为每一个图像尺度中的尺度参数,默认值为1.1。scaleFactor参数可以决定两个不同大小的窗口扫描之间有多大的跳跃,这个参数设置的大,则意味着计算会变快,但如果窗口错过了某个大小的人脸,则可能丢失物体。 minNeighbors:参数为每一个级联矩形应该保留的邻近个数,默认为3。minNeighbors控制着误检测,默认值为3表明至少有3次重叠检测,我们才认为人脸确实存。 flags:对于新的分类器没有用(但目前的haar分类器都是旧版的,CV_HAAR_DO_CANNY_PRUNING,这个值告诉分类器跳过平滑(无边缘区域)。利用Canny边缘检测器来排除一些边缘很少或者很多的图像区域;CV_HAAR_SCALE_IMAGE,这个值告诉分类器不要缩放分类器。而是缩放图像(处理好内存和缓存的使用问题,这可以提高性能。)就是按比例正常检测;CV_HAAR_FIND_BIGGEST_OBJECTS,告诉分类器只返回最大的目标(这样返回的物体个数只可能是0或1)只检测最大的物,CV_HAAR_DO_ROUGH_SEARCH,他只可与CV_HAAR_FIND_BIGGEST_OBJECTS一起使用,这个标志告诉分类器在任何窗口,只要第一个候选者被发现则结束寻找(当然需要足够的相邻的区域来说明真正找到了。),只做初略检测. minSize:为目标的最小尺寸 maxSize:为目标的最大尺寸
参考文献:
[1]浅析人脸检测之Haar分类器方法:Haar特征、积分图、 AdaBoost 、级联
[2]浅析人脸检测之Haar分类器方法
[3]Haar+cascade AdaBoost分类器学习训练总结
[4]OpenCV中的Haar+Adaboost(一):Haar特征详细介绍
[5]Adaboost算法结合Haar-like特征
[6]ADABOOST做人脸检测程序与原理
[7]AdaBoost 人脸检测介绍(3) : AdaBoost算法流程
[8]Haar特征与积分图(推荐)
[9]haar+adaboost结合讲解(偏重原理)
[10]haar+adaboost结合讲解(偏重实际)