人脸特征检测(face feature detection)也称为 “facial landmark detection”, “facial keypoint detection” and “face alignment”,是在人脸检测的基础上,对人脸上的特征点例如眼睛、鼻子、嘴巴等进行定位。
人脸关键点检测有很多应用。如下作了一些列举:
(1)Facial feature detection improves face recognize(面部特征检测改善面部识别)
人脸特征点可以用来将人脸对齐到平均人脸(mean face shape),这样在对齐之后所有图像中的人脸特征点位置几乎相同。有论文验证了用对齐后的图像训练的人脸识别算法更加有效。
(2)Head pose estimation(头部姿势估计)
知道特征点的定向,可以估计头部姿势,即人脸朝向问题。
(3)Face Morphing(人脸变形)
人脸特征点对齐人脸之后,可以有两张人脸图像生成一张新的融合人脸图像。
(4)Virtual Makeover(虚拟化妆)
现在很多软件正在用的给图像上的人物化妆的功能就是基于人脸识别的。
(5)Face Replacement(人脸交换)
人脸对换:基于脸部监测点,将一张人脸换成另外一张人脸。
卷积神经网络可以用于分类和回归任务,做分类任务时最后一个全连接层的输出维度为类别数,接着Softmax层采用Softmax Loss计算损失函数,而如果做回归任务,最后一个全连接层的输出维度则是要回归的坐标值的个数,采用的是欧几里何损失Euclidean Loss。
这里基于《Deep Convolutional Network Cascade for Facial Point Detection》论文进行讲述,链接地址。
训练卷积神经网络来回归特征点坐标。如果只采用一个网络来做回归训练的话,会发现得到的特征点坐标不够准确,采用级联回归。CNN的方法,进行分段式特征点定位,可以更快速、准确的定位人脸特征点。如果采用更大的网络,特征点的预测会更加准确、鲁棒,但耗时会增加;为了在速度和性能上找到一个平衡点,使用较小的网络,所以使用级联的思想,先进行粗检测,然后微调特征点。具体思路如下:
(1)首先在整个人脸图像(红色方框)上训练一个网络来对人脸特征点坐标进行粗回归,实际采用的网络其输入大小为39*39的人脸区域灰度图,预测时可以得到特征点的大概位置;如上图level1,在绿色框中,预测出5个点;第一层分为三波,分别是对五个点、左右眼和鼻子、鼻子和嘴巴。
(2)设计另一个回归网络,以人脸特征点(取得是level1训练之后得到的特征点)周围的局部区域图像(level2和level3中的黄色区域)作为输入进行训练,实际采用的网络为其输入大小为15*15的特征点局部区域灰度图,以预测到更加准确的特征点位置。这里level3比level2定义的输入区域要小一点。
另外需要注意的是,回归采用的欧几里得损失,在计算坐标时,使用的是相对坐标而不是绝对坐标,即每一次坐标计算,相对坐标是相对于上图所示的黄色框的边界进行的,绝对坐标是基于绿色边框边界进行的。
除此之外,在level1训练时,还对训练集进行了增广。除了做镜像之外,还对人脸框进行了两组缩放和四组平移,以及两组小角度的旋转,然后再将人脸框区域裁剪成39*39大小的区域。
下面是level1的实现:
#!/usr/bin/env python2.7
# coding: utf-8
"""
This file convert dataset from http://mmlab.ie.cuhk.edu.hk/archive/CNN_FacePoint.htm
We convert data for LEVEL-1 training data.
all data are formated as (data, landmark), and landmark is ((x1, y1), (x2, y2)...)
"""
import os
import time
import math
from os.path import join, exists
import cv2
import numpy as np
import h5py
from common import shuffle_in_unison_scary, logger, createDir, processImage
from common import getDataFromTxt
from utils import show_landmark, flip, rotate
TRAIN = 'dataset/train'
OUTPUT = 'train'
if not exists(OUTPUT): os.mkdir(OUTPUT)
assert(exists(TRAIN) and exists(OUTPUT))
def generate_hdf5(ftxt, output, fname, argument=False):
data = getDataFromTxt(ftx