最近耗费了巨大的心思为客户设计了人脸识别系统。这是我第一次利用人工智能技术为客户和自己产生收益。虽说人脸识别技术到目前为止已经非常成熟,但从“知行合一”的角度而言,很多人并没有真正掌握其根本原理,之所以有这个结论是因为,我相信绝大多数技术工作者自己无法通过编码来实现一套可商用的人脸识别系统,对技术而言,你做不到就等于你不懂。
如果你在网上搜索人脸识别,你会得到很多链接和文章。此类技术文章知乎上很多,在我看来那全都是假把式。那些人把原理”介绍“得头头是道但却根本没有给出一行代码,因此在我看来那些技术文章“装逼”的性质远多于技术探讨,我想把刚动手实现过的系统,其设计原理,特别是如何编码实现娓娓道来,在技术实践上,代码是唯一的通行证,任何没有具体可写代码的“描述”,“阐述”,“分享”其本质都可能是耍流氓。
言归正传,人脸识别基本分为三步。第一步是要掌握如何从图像中识别出人脸。这部分包括从图像中框选出人脸的矩形范围,同时获取人脸中两只眼睛,一个鼻子,两边嘴角等五个关键特征,这些关键特征也成为landmark,如下图所示:
第二步是将识别到的人脸区域图像进行特定运算最终得出一个高纬度的向量。第三部是将两个图片或从不同源头获得图像后进行前两部,然后将所得的向量进行欧几里得距离运算,当两个人脸向量之间的距离小于特定阈值时就认为两个人脸是同一个人,要不然就是不同人。
我们先从第一步,也就是从图像中识别人脸所在区域开始。这部分工作也称为aligment。从图像中识别人脸这项工作做得做好的来自于中国学者的工作,其中效果最好的则是来自论文,这篇论文的所提算法的基本思想是,使用三个卷积网络依次对图片进行识别。第一个网络叫P-NET,它的目的是识别出图片中可能包含人脸的区域。算法对它的要求是,可以识别错误但不能遗漏,也就是P-NET扫描图片后会给出一系列矩形区域,它认为这些区域里面的图像就是人脸。
算法不要求P-NET非常精准。它可以识别出错,也就是它可以将某部分不是人脸的区域识别为人脸,但它不能遗漏是人脸的区域。接下来还有两个网络分别是R-NET和O-NET,这两个网络同样是卷积网络,它们作用在P-NET结果的基础上。P-NET赋值筛选出一系列有可能是人脸的区域,R-NET对这些区域进行过滤,去除那些不属于人脸的区域,O-NET与R-NET作用相同,只不过它作用在R-NET的输出结果上,O-NET最终输出结果就是系统识别出的人脸所在区域。
我们先看P-NET的基本结构:
从上图可以看到,它接收规格为1212的图像输入,最终输出三个结果,第一个结果给出1212区域内的图像是人脸以及不是人脸的概率。注意它输出两个概率,这一点是原来使我困惑的地方,因为我认为你只要输出一个概率就行,另一个概率简单计算就可以。后来明白到,这种设计其实是为了提升网络的训练效果,让网络输出两个概率就可以使用cross-entropy函数来进行训练,其对应的损失函数如下:
公式中下标i指的是输入的第i个区域。y(i)用于表明该区域是否是人脸,它只有两个值,如果输入的12*12区域包含人脸,那么y(i)取值1,如果不是人脸那么取值0.如果区域i是人脸,那么要调整网络参数,使得输出的第一个值尽可能的大,如果区域内不是人脸,那么要调整网络参数,使得它输出的第二个数值要尽可能的大。
单单让网络判断给定区域是否是人脸还不足以训练出好效果。该算法的一个特点是,将是否是人脸的判断与人脸所在区域的计算结合起来,这样能大大提升网络识别的效率。因此网络输出的第二部分用于计算人脸矩形范围,它输出4个值,前两个是人脸所在矩形区域的左上角坐标,剩下两个值是矩形的右下角坐标
如果网络计算的第一部分结果,也就是输入区域是否包含人脸的概率超过了给定阈值,算法才会进行第二部分运算,假设输入的数据包含人脸,那么输入数据中还会给出人脸所在区域的矩形左上角和右下角坐标的准确数据,那么算法会调整网络参数,使得第二部分输出的坐标值与给定的准确坐标值的差异尽可能小,因此使用的损失函数如下:
其中第一个y(i)是网络给出的人脸区域左上角和右下角对应的四个坐标数值,第二个y(i)对应的是正确的人脸所在区域左上角和右下角坐标值,当上面公式计算结果越小就表明网络给出的人脸区域越准确。
第三部分用于计算五个特征点的坐标。由于每个坐标对应两个数值(x,y),于是第三部分对应含有10个元素的向量。于是如果输入的区域包含人脸,那么网络还要计算人脸五个关键特征对应的坐标,并且要让计算的坐标与训练数据给定的坐标尽可能相近,其对应损失函数如下:
其中第一个y(i)对应网络输出的5个关键特征点坐标形成的向量,第二个y(i)是训练数据给出的关键特征点的形成的向量,当上面公式计算结果越小就表明网络给出特征点坐标越准确。在训练P-NET时,算法要将这三部分损失以一定的比率结合起来。
回头看网络的结构。如果输入的图片规格为1212,第一个卷积层的规格为33,它扫描的步长为1,因此输出结果的数量为(12-3 1)/1 = 10,也就是经过第一层卷积后输出结果的规格为(10, 10, 10),最后一个10是因为第一层卷积曾有10个卷积核。然后将输出结果经过内核规格为(2*2)的池化层,这一层会把输入数据的规格缩小一倍,于是经过池化层后,输出数据规格就是(5,5,10).
第三层还是一个(33),扫描步长为1的卷积层,长和宽为5的输入经过它后,输出的数据规格为(5-3 1)/1=3,由于第二层卷积包含16个卷积内核,于是输出结果为(3,3,16),最后经过一个规格为(33),扫描步长为1的卷积层,由于输入数据的规格为(3,3,16),因此经过它后输出的数据规格为(3-3 1)/1,由于它包含32个卷积核,因此最终输出结果为(1,1,32),最后这部分输出再分别与三个规格为1*1的卷积层运算,于是得出上图的三部分输出。
当你阅读了上面的描述后,肯定还会觉得很恍惚,这就是代码存在的必要。后面我们会用代码实现上面的算法描述,只有读了代码你才能扫清文字描述所产生的困惑。
最后还需要搞清楚的是,网络针对的输入数据规格为1212,也就是它只能判断1212区域内的图像是否是人脸。但如果图像中包含人脸,但人脸所占据的区域范围超过1212该怎么办。相应的解决办法是缩放,假设人脸区域范围是2020,,那么算法会按照一定的比率对图片进行缩小。例如第一次先将图片缩小为1616,此时人脸所在区域还是超过图片所能判断的区域,于是再次对图片进行缩小,这次缩小到1212,此时图片就在人脸所能识别的范围了,这种将图片不断缩小所形成的图片序列也被算法称为图片金字塔,对应论文里就是image pyramid。
在具体应用时,输入的图片规格不是1212,而是任意规格。当输入图片规格不是1212时,P-NET的作用就相当于使用一个1212的扫描框,先横向扫描图片,每次间隔2个像素,然后再纵向扫描图片,每次间隔2个像素,也就相当于对给定图像,先横向依次扣出1212的图片区域,判断区域内是否包含人脸,然后回到起点并往下挪动2个像素,然后再横向依次扣出1212的图像进行判断,于是当把规格不是1212的图像输入P-NET时,P-NET就相当于一个扫描区域为12*12,扫描步长为2的卷积层。
所以假设当输入P-NET的图像规格为1616时,P-NET会选取cell((16-12 1)/2) =3,也就是将图像分成(3,3)个区域,每个区域规格为(1212),然后P-NET会依次判断这些区域里面是否包含人脸。
在后面我们使用代码实现时,当前所有描述的让你一时无法明白的知识点会变得清晰起来。