我们现在使用的模型实现人脸检测,在2080TI上,大概13帧每秒,慢是慢了点,不过胜在精度比较高,如上图所示,都能正确识别,关键点也很准确。这是人脸检测。 在人脸检测之后,如果我们需要做人脸比对或者匹配,通常就需要先进行人脸对齐,这样在提取特征会更有效。所谓人脸对齐,其实就是将原来倾斜等的人脸转换成端正的。如下图:
左图就是原图,而纠正之后的头像应该类似右图。原来的人脸对齐算法是从一个开源项目里拿来用的,因为效果还可以,所以就一直用着,最近发现这个算法有点问题,才抽时间这个算法撸了一遍。
这里其实就是将人脸的5个关键点,通过某种映射,通常是线性变换,映射成一个标准的人脸,5个原始关键点:
X = [
[828.1219482421875, 356.0898132324219],
[901.5404052734375, 369.9261779785156],
[844.2774047851562, 405.2549133300781],
[820.4654541015625, 441.8576965332031],
[874.6876831054688, 454.26739501953125]
]
标准的人脸关键点坐标是这样的:
Y = [
[30.2946, 51.6963],
[65.5318, 51.5014],
[48.0252, 71.7366],
[33.5493, 92.3655],
[62.7299, 92.2041]
]
从X映射到Y,其实就是需要找到一个矩阵M,使得:
Y = M*X
这里的矩阵M就是我们要找的变换矩阵,只要找到一个M,那么对于原图像中的所有点,都能找到对应的映射点。
import numpy as np
from skimage import transform
X, Y = np.array(X), np.array(Y)
tform = transform.SimilarityTransform()
# 程序直接估算出转换矩阵M
tform.estimate(X, Y)
M = tform.params[0:2, :]
打印M的输出如下:
array([[ 4.62207723e-01, 9.64380058e-02, -3.85718987e+02],
[-9.64380058e-02, 4.62207723e-01, -3.31742763e+01]])
这就是转换矩阵。如果是人脸截取的话,还差最后一步:
# 截取一个112*112的头像
import cv2
warped = cv2.warpAffine(img, M, (112, 112), borderValue=0.0)
至于是怎么估算转换矩阵M的,这里涉及到SVD分解(SVD就像一个幽灵,是不是就会跑出来),有机会可以写一篇。
展示一下人脸纠正后的效果:
感觉对于侧脸的对齐效果不太好,会有点偏,有时间再优化优化。
Python解决问题很简单,不过我们有必要认识一下其背后的仿射变换。其在百科上的定义如下: 仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。 更加严谨的定义如下:
简单说:仿射变换 = 线性变换 + 平移。(注意不是平移+线性变换)
平移变换
平移变换其实就是所有点都向同一个方向移动一个距离,例如对于平面内的任意点(x, y):
new_x = x + e
new_y = y + f
如果使用矩阵来表示,这个需要使用增广矩阵的形式:
所谓线性变换如下:
显然线性变换有一个明显的特征就是,无论怎么变,原点始终不变,就是如果输入的是原点(0,0),则输出的必定是(0,0)。 不过如果这样,我们无法跟平移叠加,需处理成增广矩阵的形式:
这样线性变换和平移变换就能叠加了。计算也不复杂,就是线性变换的矩阵乘以平移矩阵,最后的结果如下:
注意这里是先做线性变换,再平移,如果把他们交换顺序,其结果是不一样的。 简化表示:
等价于:
其中仿射变换矩阵为:
因为最后一行的格式都是固定的,前面是0,最后一个1,所以前面Python实现时得到的变换矩阵并没有最后一行。
图片来自 https://www.cnblogs.com/shine-lee/p/10950963.html ps: 1. skimage的接口说明:https://cloud.tencent.com/developer/section/1415102#stage-100055709