即将进入涉及大量数学知识的阶段,先读这篇博文放松一下。
长文干货!走近人脸检测:从 VJ 到深度学习(上)
2016-04-12 邬书哲
人脸检测的开始和基本流程
具体来说,人脸检测的任务就是判断给定的图像上是否存在人脸, 如果人脸存在,就给出全部人脸所处的位置及其大小。由于人脸检测在实际应用中的重要意义,早在上世纪70年代就已经有人开始研究,然而受当时落后的技术条件和有限的需求所影响,直到上世纪90年代,人脸检测技术才开始加快向前发展的脚步,在新世纪到来前的最后十年间,涌现出了大量关于人脸检测的研究工作,这时期设计的很多人脸检测器已经有了现代人脸检测技术的影子,例如可变形模板的设计(将人脸按照五官和轮廓划分成多个相互连接的局部块)、神经网络的引入(作为判断输入是否为人脸的分类模型)等。这些早期的工作主要关注于检测正面的人脸,基于简单的底层特征如物体边缘、图像灰度值等来对图像进行分析,结合关于人脸的先验知识来设计模型和算法(如五官、肤色),并开始引入一些当时已有的的模式识别方法。虽然早期关于人脸检测的研究工作离实际应用的要求还有很远,但其中进行检测的流程已经和现代的人脸检测方法没有本质区别。给定一张输入图像,要完成人脸检测这个任务,我们通常分成三步来进行:
检测人脸的过程就是不断地执行上面三步,直到遍历所有需要观察的窗口。
-
- 不是 :如果所有的窗口都被判断为不包含人脸,那么就认为所给的图像上不存在人脸,
- 是 :否则就根据判断为包含人脸的窗口来给出人脸所在的位置及其大小。
那么,如何来选择我们要观察的窗口呢?
所谓眼见为实,要判断图像上的某个位置是不是一张人脸,必须要观察了这个位置之后才知道,因此,选择的窗口应该覆盖图像上的所有位置。显然,最直接的方式就是让观察的窗口在图像上从左至右、从上往下一步一步地滑动,从图像的左上角滑动到右下角——这就是所谓的滑动窗口范式,你可以将它想象成是福尔摩斯(检测器)在拿着放大镜(观察窗口)仔细观察案发现场(输入图像)每一个角落(滑动)的过程。
别看这种用窗口在图像上进行扫描的方式非常简单粗暴,它的确是一种有效而可靠的窗口选择方法,以至于直到今天,滑动窗口范式仍然被很多人脸检测方法所采用,而非滑动窗口式的检测方法本质上仍然没有摆脱对图像进行密集扫描的过程。
对于观察窗口,还有一个重要的问题就是:窗口应该多大?
我们认为一个窗口是一个人脸窗口当且仅当其恰好框住了一张人脸,即窗口的大小和人脸的大小是一致的,窗口基本贴合人脸的外轮廓。
那么问题来了,即使是同一张图像上,人脸的大小不仅不固定,而且可以是任意的,这样怎么才能让观察窗口适应不同大小的人脸呢?
一种做法当然是采用多种不同大小的窗口,分别去扫描图像,但是这种做法并不高效。换一个角度来看,其实也可以将图像缩放到不同的大小,然后用相同大小的窗口去扫描。这就是所谓的构造图像金字塔的方式。图像金字塔这一名字非常生动形象,将缩放成不同大小的图像按照从大到小的顺序依次往上堆叠,正好就组成了一个金字塔的形状。
通过构建图像金字塔,同时允许窗口和人脸的贴合程度在小范围内变动,我们就能够检测到不同位置、不同大小的人脸了。另外需要一提的是,对于人脸而言,我们通常只用正方形的观察窗口,因此就不需要考虑窗口的长宽比问题了。
收集证据,提取特征
选好了窗口,我们开始对窗口中的图像区域进行观察。在处理图像的过程中,这个收集证据的环节我们称之为特征提取,特征就是我们对图像内容的描述。由于机器看到的只是一堆数值,能够处理的也只有数值,因此对于图像所提取的特征具体表示出来就是一个向量,称之为特征向量,其每一维是一个数值,这个数值是根据输入(图像区域)经由某些计算(观察)得到的,例如进行求和、相减、比较大小等。总而言之,特征提取过程就是从原始的输入数据(图像区域颜色值排列组成的矩阵)变换到对应的特征向量的过程,特征向量就是我们后续用来分析和寻找真相的证据。
特征是人脸么 之 人脸分类器
特征提取之后,就到了决断的时刻:判别当前的窗口是否恰好包含一张人脸。我们将所有的窗口划分为两类:
-
- 一类是恰好包含人脸的窗口,称之为人脸窗口,
- 剩下的都归为第二类,称之为非人脸窗口,
而最终判别的过程就是一个对当前观察窗口进行分类的过程。
因为我们的证据是由数值组成的特征向量,所以我们是通过可计算的数学模型来寻找真相的。
用来处理分类问题的数学模型我们通常称之为分类器,分类器以特征向量作为输入,通过一系列数学计算,以类别作为输出。
每个类别会对应到一个数值编码,称之为这个类别对应的标签,如将人脸窗口这一类编码为1,而非人脸窗口这一类编码为-1;
分类器就是一个将特征向量变换到类别标签的函数。
但,分类器的阈值往往是很难确定的
尽管我们对目标非常明确,我们也仍然没法给出一个最优的参数取值,因为我们并不使用机器所采用的二进制语言系统,我们并不懂什么才是对机器最好的。
于是我们只有一种选择:
-
- 把我们的目标告诉机器,举一些例子向其进行解释,
- 然后让机器自己去学习这个参数,
- 最后我们给机器设计一场考试,测试其是否满足了我们的要求。
我们从一些图像上选出一部分人脸和非人脸窗口的样例,用对应的类别标签对其进行标注,然后将这些样例划分成两个集合,一个集合作为分类器学习所使用的训练集,另一个集合作为最终考查分类器能力的测试集,同时我们设定一个目标:希望分类的准确率能够在80%以上。(典型的机器学习方法)
由于采用滑动窗口的方式需要在不同大小的图像上的每一个位置进行人脸和非人脸窗口的判别,而对于一张大小仅为480*320的输入图像,窗口总数就已经高达数十万,面对如此庞大的输入规模,如果对单个窗口进行特征提取和分类的速度不够快,就很容易使得整个检测过程产生巨大的时间开销,也确实就因为如此,早期所设计的人脸检测器处理速度都非常慢,一张图像甚至需要耗费数秒才能处理完成——视频的播放速度通常为每秒25帧图像,这给人脸检测投入现实应用带来了严重的障碍。
人脸检测技术的突破:VJ人脸检测器及其发展
VJ人脸检测之所以器能够获得成功,极大地提高人脸检测速度,其中有三个关键要素:
-
- 特征的快速计算方法——积分图,
- 有效的分类器学习方法——AdaBoost,
- 高效的分类策略——级联结构的设计。
VJ人脸检测器采用Haar特征来描述每个窗口。
-
- 所谓Haar特征,其实就是在窗口的某个位置取一个矩形的小块,
- 然后将这个矩形小块划分为黑色和白色两部分,并分别对两部分所覆盖的像素点(图像上的每个点称为一个像素)的灰度值求和,
- 最后用白色部分像素点灰度值的和减去黑色部分像素点灰度值的和,得到一个Haar特征的值。
Haar特征 (Haar-like特征)
反映了局部区域之间的相对明暗关系,能够为人脸和非人脸的区分提供有效的信息,例如眼睛区域比周围的皮肤区域要暗,通过Haar特征就可以将这一特点表示出来。
http://www.cnblogs.com/ello/archive/2012/04/28/2475419.html 人脸检测方法主要有两大类:基于知识和基于统计。 基于知识的方法主要利用先验知识将人脸看作器官特征的组合,根据眼睛、眉毛、嘴巴、鼻子等器官的特征以及相互之间的几何位置关系来检测人脸。 基于统计的方法则将人脸看作一个整体的模式——二维像素矩阵,从统计的观点通过大量人脸图像样本构造人脸模式空间,根据相似度量来判断人脸是否存在。 在这两种框架之下,发展了许多方法。目前随着各种方法的不断提出和应用条件的变化,将知识模型与统计模型相结合的综合系统将成为未来的研究趋势。”(来自论文《基于Adaboost的人脸检测方法及眼睛定位算法研究》) 基于知识的人脸检测方法 Ø 模板匹配 Ø 人脸特征 Ø 形状与边缘 Ø 纹理特性 Ø 颜色特征 基于统计的人脸检测方法 Ø 主成分分析与特征脸 Ø 神经网络方法 Ø 支持向量机 Ø 隐马尔可夫模型 Ø Adaboost算法 虽说haar分类器采用了Boosting的算法,但在OpenCV中,Haar分类器与Boosting没有采用同一套底层数据结构。 《Learning OpenCV》中有这样的解释:“Haar分类器,它建立了boost筛选式级联分类器。它与ML库中其他部分相比,有不同的格局,因为它是在早期开发的,并完全可用于人脸检测。” 在2001年,Viola和Jones两位大牛发表了经典的《Rapid Object Detection using a Boosted Cascade of Simple Features》【1】和《Robust Real-Time Face Detection》【2】,在AdaBoost算法的基础上,使用Haar-like小波特征和积分图方法进行人脸检测,他俩不是最早使用提出小波特征的,但是他们设计了针对人脸检测更有效的特征,并对AdaBoost训练出的强分类器进行级联。 这可以说是人脸检测史上里程碑式的一笔了,也因此当时提出的这个算法被称为Viola-Jones检测器。 又过了一段时间,Rainer Lienhart和Jochen Maydt两位大牛将这个检测器进行了扩展【3】,最终形成了OpenCV现在的Haar分类器。 AdaBoost是Freund 和Schapire在1995年提出的算法,是对传统Boosting算法的一大提升。Boosting算法的核心思想,是将弱学习方法提升成强学习算法,也就是“三个臭皮匠顶一个诸葛亮”,它的理论基础来自于Kearns 和Valiant牛的相关证明【4】 在此不深究了。反正我是能多简略就多简略的把Haar分类器的前世今生说完鸟,得出的结论是,大牛们都是成对儿的。。。(啊哈哈哈哈哈。。。)额,回到正题,Haar分类器 = Haar-like特征 + 积分图方法 + AdaBoost + 级联; 阿 Haar分类器算法的要点如下: ① 使用Haar-like特征做检测。 ② 使用积分图(Integral Image)对Haar-like特征求值进行加速。 ③ 使用AdaBoost算法训练区分人脸和非人脸的强分类器。 ④ 使用筛选式级联把强分类器级联到一起,提高准确率。 论文列表: 【1】《Rapid Object Detection using a Boosted Cascade of Simple Features》 【2】《Robust Real-Time Face Detection》 【3】《An Extended Set of Haar-like Features for Rapid Object Detection》 【4】《Crytographic Limitations on Learning Boolean Formulae and Finite Automata》 【5】《A Theory of the Learnable》 【6】《The Computational Complexity of Machine Learning》 【7】《The Strength of Weak Learnability》 【8】《Boosting a weak learning algorithm》 【9】《A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting》
戏说Haar特征
Viola牛们提出的Haar-like特征。
Lienhart等牛们提出的Haar-like特征。
其他Haar-like特征。
将上面的任意一个矩形放到人脸区域上,
然后,(白色区域的像素和 - 黑色区域的像素和)的值 = 暂且称之为人脸特征值。
如果你把这个矩形放到一个非人脸区域,那么计算出的特征值应该和人脸特征值是不一样的,而且越不一样越好,所以这些方块的目的就是把人脸特征量化,以区分人脸和非人脸。
Haar特征本质上是局部区域像素值的一种线性组合,其相对应的更一般的形式则是不指定线性组合的系数,允许系数为任意实数,这被称之为线性特征。
LBP特征 - 二值编码特征
其直接基于像素灰度值进行计算,特点是在编码时考虑的是两个值的相对大小,并且按照一定的空间结构来进行编码,局部组合二值特征就是在LBP特征的启发下设计的;
从计算上来看,提取LBP特征比提取Haar特征要快,但是Haar特征对于人脸和非人脸窗口的区分能力更胜一筹。
SURF特征
简化的SURF特征是一种和Haar特征相类似的特征,但是其计算的是局部区域中像素点的梯度和,并在求和的过程中考虑了梯度方向;
所谓梯度,最简单的一种情形就是指同一行上两个不同位置像素值的差比上它们水平坐标的差;
SURF特征比Haar特征更为复杂,因此计算代价更高,但是由于其表达能力更强,因此能够以更少数目的特征来达到相同的区分度,在一定程度上弥补了其在速度上的不足。
HOG特征
HOG特征也是一种基于梯度的特征,其对一个局部区域内不同方向的梯度进行统计,计算梯度直方图来表示这个区域。
积分通道特征
积分通道特征和多通道的Haar特征有些类似,但是其使用的通道更加多样化,将通道的概念推广为由原图像变换而来并且空间结构和原图像对应的任何图像。
聚合通道特征
聚合通道特征则在积分通道特征的基础上进一步加入了对每个通道进行下采样的操作,实现局部区域信息的聚合。
在过去十几年的探索过程中,涌现出的特征不胜枚举,这里只选取了部分比较有代表性和反映了人们探索思路的特征进行举例。
这里所有列举的特征都有一个共同的特点:都由科研工作者根据自己的经验手工设计,这些特征的设计反映了人们对问题的理解和思考。
虽然随着不断的改进,设计出的特征已经日臻完善,但直到现在,人们在特征上的探索还远没有结束。
AdaBoost算法
为了增加区分度,可以对多个矩形特征计算得到一个区分度更大的特征值。
"那么什么样的矩形特征怎么样的组合到一块可以更好的区分出人脸和非人脸呢?" -- 这就是AdaBoost算法要做的事了。
这里我们先放下积分图这个概念不管,为了让我们的思路连贯,我直接开始介绍AdaBoost算法。
我比较喜欢查算法的户口,所以新写了一章查了下去。 AdaBoost的老祖宗可以说是机器学习的一个模型,它的名字叫PAC(Probably Approximately Correct)。 PAC模型是计算学习理论中常用的模型,是Valiant牛在我还没出生的1984年提出来的【5】,他认为“学习"是模式明显清晰或模式不存在时仍能获取知识的一种“过程”,并给出了一个【从计算角度】来获得这种“过程"的方法,这种方法包括: (1) 适当信息收集机制的选择; (2) 学习的协定; (3) 对能在合理步骤内完成学习的概念的分类。 PAC学习的实质就是在样本训练的基础上,使算法的输出以概率接近未知的目标概念。 PAC学习模型是考虑: 1) 样本复杂度(指学习器收敛到成功假设时至少所需的训练样本数) 2) 计算复杂度(指学习器收敛到成功假设时所需的计算量) 的一个基本框架。 成功的学习被定义为形式化的概率理论。(来自论文《基于Adaboost的人脸检测方法及眼睛定位算法研究》)简单说来,PAC学习模型不要求你每次都正确,只要能在多项式个样本和多项式时间内得到满足需求的正确率,就算是一个成功的学习。 基于PAC学习模型的理论分析,Valiant牛提出了Boosting算法【5】,Boosting算法涉及到两个重要的概念就是弱学习和强学习, 所谓弱学习,就是指一个学习算法对一组概念的识别率只比随机识别好一点, 所谓强学习,就是指一个学习算法对一组概率的识别率很高。 现在我们知道所谓的弱分类器和强分类器就是弱学习算法和强学习算法。 弱学习算法是比较容易获得的,获得过程需要数量巨大的假设集合,这个假设集合是基于某些简单规则的组合和对样本集的性能评估而生成的, 强学习算法是不容易获得的, 然而,Kearns 和Valiant 两头牛提出了弱学习和强学习等价的问题 【6】 并证明了只要有足够的数据,弱学习算法就能通过集成的方式生成任意高精度的强学习方法。这一证明使得Boosting有了可靠的理论基础,Boosting算法成为了一个提升分类器精确性的一般性方法。【4】 1990年,Schapire牛提出了第一个多项式时间的算法【7】, 1年后,Freund牛又提出了一个效率更高的Boosting算法【8】。 然而,Boosting算法还是存在着几个主要的问题, 其一、Boosting算法需要预先知道弱学习算法学习正确率的下限即弱分类器的误差, 其二、Boosting算法可能导致后来的训练过分集中于少数特别难区分的样本,导致不稳定。 针对Boosting的若干缺陷,Freund和Schapire牛于1996年前后提出了一个实际可用的自适应Boosting算法AdaBoost【9】,AdaBoost目前已发展出了大概四种形式的算法: 1) Discrete AdaBoost(AdaBoost.M1)、 2) Real AdaBoost、 3) LogitBoost、 4) Gentle AdaBoost, 至此,AdaBoost的身世之谜就这样揭开了。
戏说AdaBoost
"弱弱变强,强强联手“ -- 级联分类器
原因一:
造成人脸检测速度慢的根本原因还在于输入规模过大,动辄需要处理几十上百万的窗口,如果这样的输入规模是不可避免的,那么有没有可能在处理的过程中尽快降低输入规模呢?
如果能够通过粗略地观察快速排除掉大部分窗口,只剩下少部分窗口需要进行仔细的判别,则总体的时间开销也会极大地降低。
从这样的想法出发,VJ人脸检测器采用了一种级联结构来达到逐步降低输入规模的目的。
原因二:
通过AdaBoost算法辛苦的训练出了强分类器,然而在现实的人脸检测中,只靠一个强分类器还是难以保证检测的正确率,这个时候,需要一个豪华的阵容,训练出多个强分类器将它们强强联手,最终形成正确率很高的级联分类器这就是我们最终的目标Haar分类器。
那么训练级联分类器的目的就是为了检测的时候,更加准确,这涉及到Haar分类器的另一个体系,检测体系 -- 以现实中的一幅大图片作为输入,然后对图片中进行多区域,多尺度的检测。
-
- 所谓多区域,是要对图片划分多块,对每个块进行检测.
- 由于训练的时候用的照片一般都是20*20左右的小图片,所以对于大的人脸,还需要进行多尺度的检测。
- 多尺度检测机制一般有两种策略:
-
- 一种是不改变搜索窗口的大小,而不断缩放图片,这种方法显然需要对每个缩放后的图片进行区域特征值的运算,效率不高,
- 另一种方法,是不断初始化搜索窗口size为训练时的图片大小,不断扩大搜索窗口,进行搜索,解决了第一种方法的弱势。
在区域放大的过程中会出现同一个人脸被多次检测,这需要进行区域的合并,这里不作探讨。
无论哪一种搜索方法,都会为输入图片输出大量的子窗口图像,这些子窗口图像经过筛选式级联分类器会不断地被每一个节点筛选,抛弃或通过。
级联强分类器的策略是,将若干个强分类器由简单到复杂排列,希望经过训练使每个强分类器都有较高检测率,而误识率可以放低。
比如几乎99%的人脸可以通过,但50%的非人脸也可以通过,这样如果有20个强分类器级联,那么:
他们的总识别率为0.99^20 98%,错误接受率也仅为0.5^20 0.0001%。
这样的效果就可以满足现实的需要了,但是如何使每个强分类器都具有较高检测率呢,为什么单个的强分类器不可以同时具有较高检测率和较高误识率呢?
下面我们讲讲级联分类器的训练。( 主要参考了论文《基于Adaboost的人脸检测方法及眼睛定位算法研究》)
设: K 是一个级联检测器的层数, D 是该级联分类器的检测率, F 是该级联分类器的误识率, di 是第i层强分类器的检测率, fi 是第i层强分类器的误识率。
如果要训练一个级联分类器达到给定的F值和D值,只需要训练出每层的d值和f值,这样:
d^K = D f^K = F
级联分类器的要点就是如何训练每层强分类器的d值和f值达到指定要求。
AdaBoost训练出来的强分类器一般具有较小的误识率,但检测率并不很高,
一般情况下,高检测率会导致高误识率,这是强分类阈值的划分导致的:
-
- 要提高强分类器的检测率既要降低阈值,
- 要降低强分类器的误识率就要提高阈值,
这是个矛盾的事情。
据参考论文的实验结果,增加分类器个数可以在提高强分类器检测率的同时降低误识率,所以级联分类器在训练时要考虑如下平衡:
-
- 一是弱分类器的个数和计算时间的平衡,
- 二是强分类器检测率和误识率之间的平衡。
具体说来,VJ人脸检测器将多个分类器级联在一起,从前往后,分类器的复杂程度和计算代价逐渐增大。
对于给定的一个窗口,先由排在最前面也最简单的分类器对其进行分类,如果这个窗口被分为非人脸窗口,那么就不再送到后面的分类器进行分类,直接排除,
否则就送到下一级分类器继续进行判别,直到其被排除,或者被所有的分类器都分为人脸窗口。
这样设计的好处是显而易见的,每经过一级分类器,下一级分类器所需要判别的窗口就会减少,使得只需要付出非常少的计算代价就能够排除大部分非人脸窗口。
从另一个角度来看,这实际上也是根据一个窗口分类的难度动态地调整了分类器的复杂程度,这显然比所有的窗口都用一样的分类器要更加高效。
积分图 -- 加速器
它是Haar分类器能够实时检测人脸的保证。
以Viola牛提出的最基本四个特征为例,在一个24×24size的窗口中任意排列至少可以产生数以10万计的特征 (超过160,000个),对这些特征求值的计算量是非常大的。
而积分图就是只遍历一次图像就可以求出图像中所有区域像素和的快速算法,大大的提高了图像特征值计算的效率。
积分图是一种能够描述全局信息的矩阵表示方法。积分图的构造方式是位置(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分类器的检测效率
简略的探讨下Haar分类器的检测效率。 尝试过的几种方法: 1)尝试检测算法与跟踪算法相结合,原本以为Camshift是个轻量级的算法,但是正如我后来看到的,建立反向投影图的效率实在不高,在PC上效果不错,但是在ios上速度很慢,这个我后来发现可能是因为ios浮点运算效率不高的原因。但是即便速度能上去,靠Camshift跟踪算法太依赖肤色了,导致脖子,或是手什么的干扰很严重,这个调起来很费神,也不一定能调好。 2)修改OpenCV中Haar检测函数的参数,效果非常明显,得出的结论是,搜索窗口的搜索区域是提高效率的关键。 3)根据2)的启发,我打算利用YCbCr颜色空间,粗估肤色区域,以减少人脸的搜索面积,但是后来苦于没能高效率的区分出肤色区域,放弃了该方法。 4)换了策略,考虑到视频中人脸检测的特殊性,上一帧人脸的位置信息对下一帧的检测有很高的指导价值,所以采有帧间约束的方法,减少了人脸搜索的区域,并且动态调整Haar检测函数的参数,得到了较高的效率。 5)其他关于算法之外的优化需要根据不同的处理器做具体的优化。
Haar分类器的检测效率
分类器及其学习方法的进化