人脸识别,大致可以分为以下四个步骤:

  • 人脸检测:从图片中准确定位到人脸,并以矩形框将其裁剪出来;
  • 人脸矫正(对齐): 检测到的人脸,可能角度不是很正,需要使其对齐,比如旋转,缩放;
  • 特征提取:对矫正后的人脸进行特征提取,现在做法通常都是基于一个CNN模型;
  • 人脸比对:对两张人脸图像提取的特征向量进行对比,计算相似度。

上述第一步,目前主流的做法都来自如Faster RCNN或者SSD等通用目标检测的一些改进网络,大致可以直接将人脸检测就看成特定目标的检测;这里主要介绍人脸校正部分,且介绍其中一种方法,也是MTCNN使用的方法,该方法简单快速,不需要去先建立3D模型然后进行映射。当然该方法效果么,从其原理上,主要解决的依然是正脸的对齐,无法很好解决侧脸的对齐(这时候可以用GAN或者3D建模去恢复)。

0 引言

MTCNN中采用的人脸矫正方法,是假设拿到的人脸几乎都是正脸,不过此时正脸有尺度不等,角度偏移等。而且需要预先设定一个平均脸,即目标脸的位置,标记出平均脸的所有关键点应该处于的位置,然后将所有人脸矫正到该平均脸上。

1 Procrustes Analysis普氏分析法

下述概念部分参考自《Master Opencv...读书笔记》非刚性人脸跟踪 II

人脸由眼睛、鼻子、嘴巴、下巴等部位构成,正是因为这些部位形状、大小和相对位置的各种变化,才使得人脸表情千差万别,因此可以对这些部位的形状和结构关系进行几何描述,作为人脸表情识别的重要特征。这里,几何关系就是指预定义点集的空间组态模式,而这些点与脸部器官在几何空间存在对应关系(比如眼角、鼻尖、眉毛)。

Facial geometry,通过两种元素的参数化配置组成:

  • 全局变形(刚性):指人脸在图像中的分布,允许人脸出现在图像中任意位置,包括人脸的坐标(x,y)、角度、大小;
  • 局部变形(非刚性):指不同人和不同表情之间脸部形状的不同,与全局形变不同,人脸的高度结构化特征对局部形变产生了极大的约束。

全局变形可以由二维空间的函数表达,并且可以应用于任何类型的对象;然而局部形变只针对特定目标,需要从训练集中去学习得到。

简单的仿射变换包括:平移,旋转,缩放。普氏分析法是一种用来分析形状分布的方法。数学上来讲,就是不断迭代,寻找标准形状(canonical shape),并利用最小二乘法寻找每个样本形状到这个标准形状的仿射变化方式。(可参照维基百科的GPA算法)



图1.1 平移,旋转,缩放的三种仿射变换方式示意

如上图所示,我们假设一张2D图片上每个点都为坐标\((x,y)\),我们对坐标上表示的值不感兴趣,我们只是对其坐标位置感兴趣。这里表现的就是原始目标上每个像素点的仿射映射

  • Procrustes analysis的作用可以看作是一种对原始数据的预处理,目的是为了获取更好的局部变化模型作为后续模型学习的基础。如下图所示:
  • 每一个人脸特征点可以用一种单独的颜色表示;
  • 经过归一化变化,人脸的结构越来越明显,即脸部特征簇的位置越来越接近他们的平均位置;
  • 经过一系列迭代,尺度和旋转的归一化操作,这些特征簇变得更加紧凑,它们的分布越来越能表达人脸表情的变化。【剔除刚性部分、保留柔性部分】

  • 下图为不同大小、不同长宽比的矩形,经过归一化过程后,各个样本点分布服从一定概率分布趋势

在图1.1中,将这三种方法合并到一个式子中:

\[\begin{align}
\begin{bmatrix} u \\ v \\ \end{bmatrix} & = \begin{bmatrix} a_2 & a_1 & a_0 \\ b_2 & b_1 & b_0 \\ \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \\ \end{bmatrix} \\
& = s\begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta \\ \end{bmatrix} \begin{bmatrix} x \\ y \\ \end{bmatrix}+\begin{bmatrix} t_1 \\ t_2 \\ \end{bmatrix} \\
& = sR\begin{bmatrix} x \\ y \\ \end{bmatrix}+T
\end{align}
\]

上述式子中\(s\)就是缩放因子,\(\theta\)就是旋转角度,\(t\)表示平移的量,\(R\)是一个正交矩阵\(R^TR=I\)

所以现在问题变成了:如何旋转,平移和缩放第一个向量,使其尽可能对齐第二个向量的点,即通过使用仿射变换将第一个图像进行变换,然后覆盖第二个图像。并判断变换后的第一个图像与第二个图像的距离,并使其距离最小

假设两个形状矩阵分别为\(p\),\(q\),矩阵每一行表示一个特征点(人脸中即为像素点)的(x,y)坐标,假设有N个特征点坐标,则\(p\in R^{N \times 2}\),\(q\in R^{N \times 2}\)。对应的数学形式为:

\[Loss = \sum_i^N||sRp_i^T+T-q_i^T||^2
\]

求其最小,也就是

\[\begin{aligned}
& \underset{s,R,T}{\arg min}||sRp^T+T-q^T||_F \\
& s.t. \, R^TR=I
\end{aligned}\]

其中\(||\cdot||_F\)是Frobenius范数,这里就是L2范数。

人脸对齐:Procrustes analysis中所述,对两边的点集进行消除平移消除缩放之后,旋转的角度部分可以变成求解下面式子:

\[\begin{aligned}
& \underset{R}{\arg min}||RA-B||_F \\
& s.t. \, R^TR=I
\end{aligned}\]

此时根据Ordinary_Procrustes_analysis中的Orthogonal_Procrustes_problem所述,式子解为:

\[\begin{aligned}
& M = BA^T \\
& svd(M) = U\Sigma V^T \\
& R = UV^T \\
\end{aligned}\]

那么仿射变换矩阵为:

2 代码

switching-eds-with-python第一部分所述:

  • 将矩阵数值类型转换成float;
  • 每个特征点减去当前形状的中心点,消除平移的影响(一旦找到了处理后的特征点集和的最优缩放因子和角度,这里的中心点c1和c2可以用来找回完整解);
  • 每个特征点除以当前形状的尺度因子,消除尺度的缩放影响;
  • 使用SVD计算旋转角度;
  • 返回一个仿射变换矩阵的完整矩阵。
  1. '''a.py
  2. https://matthewearl.github.io/2015/07/28/switching-eds-with-python/ 中代码是有问题的;
  3. 因为numpy的矩阵相乘需要numpy.dot,直接的相乘是逐元素相乘 '''
  4. def transformation_from_points(points1, points2):
  5. '''0 - 先确定是float数据类型 '''
  6. points1 = points1.astype(numpy.float64)
  7. points2 = points2.astype(numpy.float64)
  8. '''1 - 消除平移的影响 '''
  9. c1 = numpy.mean(points1, axis=0)
  10. c2 = numpy.mean(points2, axis=0)
  11. points1 -= c1
  12. points2 -= c2
  13. '''2 - 消除缩放的影响 '''
  14. s1 = numpy.std(points1)
  15. s2 = numpy.std(points2)
  16. points1 /= s1
  17. points2 /= s2
  18. '''3 - 计算矩阵M=BA^T;对矩阵M进行SVD分解;计算得到R '''
  19. # ||RA-B||; M=BA^T
  20. A = points1.T # 2xN
  21. B = points2.T # 2xN
  22. M = np.dot(B, A.T)
  23. U, S, Vt = numpy.linalg.svd(M)
  24. R = np.dot(U, Vt)
  25. '''4 - 构建仿射变换矩阵 '''
  26. s = s2/s1
  27. sR = s*R
  28. c1 = c1.reshape(2,1)
  29. c2 = c2.reshape(2,1)
  30. T = c2 - np.dot(sR,c1) # 模板人脸的中心位置减去 需要对齐的中心位置(经过旋转和缩放之后)
  31. trans_mat = numpy.hstack([sR,T]) # 2x3
  32. return trans_mat

在找到对应的仿射映射矩阵后,可以通过opencv的warpAffine进行映射。

  1. '''上述函数实现的时候,注意模板脸和需要对其的人脸的顺序
  2. landmarks1: 检测出来需要对齐的人脸关键点;
  3. landmarks2:对齐的模板人脸,即平均脸关键点'''
  4. trans_mat = transformation_from_points(landmarks1, landmarks2)
  5. def warp_im(in_image, trans_mat, dst_size):
  6. output_image = cv2.warpAffine(in_image,
  7. trans_mat,
  8. dst_size, # (cols, rows)
  9. borderMode=cv2.BORDER_TRANSPARENT)
  10. return output_image

3 例子

假设当前图片包含4个人脸,如下图,



通过mxnet_mtcnn_face_detection进行检测得到对应的4个人脸边界框和对应的5个关键点

  1. '''边界框[x1, y1, x2, y2, score] '''
  2. [222.22601686, 43.14613463, 280.12375677, 123.65308259, 1. ],
  3. [ 53.22718975, 30.1167623 , 105.30491075, 98.62653339, 0.99999237],
  4. [374.89622349, 44.23550975, 432.30359537, 125.07026242, 0.99998629],
  5. [497.07639685, 32.2071521 , 548.87478065, 105.17269786, 0.99970442]
  6. '''points 关键点[x1, x2 ... x5, y1, y2 ..y5] '''
  7. [255.76176 , 278.4415 , 274.27048 , 255.08255 , 273.5981 , 73.90924 , 75.331924, 92.13313 , 103.375435, 105.174866],
  8. [ 82.20165 , 102.67773 , 99.4637 , 82.03625 , 100.24012 , 55.405067, 55.737637, 69.84144 , 82.090454, 81.33897 ],
  9. [389.51086 , 416.09406 , 397.90732 , 388.24335 , 408.37756 , 68.65562 , 72.8951 , 87.913956, 102.8963 , 105.9071 ],
  10. [513.3108 , 537.25714 , 525.7555 , 514.9328 , 535.16156 , 61.96994 , 61.56072 , 77.64981 , 89.027435, 88.96181 ]

这里修改MTCNN中的对齐代码

  1. def extract_image_chips1( img, points, desired_size=256, padding=0):
  2. """
  3. crop and align face
  4. Parameters:
  5. ----------
  6. img: numpy array, input image
  7. points: numpy array, n x 10 (x1, x2 ... x5, y1, y2 ..y5)
  8. desired_size: default 256
  9. padding: default 0
  10. Retures:
  11. -------
  12. crop_imgs: list, n
  13. cropped and aligned faces
  14. """
  15. crop_imgs = []
  16. for ind,p in enumerate(points):
  17. # 当前图片中一共有len(points)个人脸
  18. shape =[]
  19. for k in range(len(p)//2):
  20. shape.append(p[k])
  21. shape.append(p[k+5])
  22. if padding > 0:
  23. padding = padding
  24. else:
  25. padding = 0
  26. # 平均脸(模板脸)的5个关键点坐标
  27. mean_face_shape_x = [0.224152, 0.75610125, 0.490127, 0.254149, 0.726104]
  28. mean_face_shape_y = [0.2119465, 0.2119465, 0.628106, 0.780233, 0.780233]
  29. from_points = []
  30. to_points = []
  31. for i in range(len(shape)//2):
  32. x = (padding + mean_face_shape_x[i]) / (2 * padding + 1) * desired_size
  33. y = (padding + mean_face_shape_y[i]) / (2 * padding + 1) * desired_size
  34. to_points.append([x, y])
  35. from_points.append([shape[2*i], shape[2*i+1]])
  36. # 构建人脸关键点矩阵
  37. from_mat = np.asarray(from_points)
  38. to_mat = np.asarray(to_points)
  39. # 计算from_mat映射到to_mat的仿射变换矩阵,是一个2x3的矩阵
  40. trans_mat = transformation_from_points(from_mat,to_mat)
  41. # 进行仿射变换,并取当前中心向外(desired_size,desired_size)大小的区域
  42. dst_size = (desired_size,desired_size)
  43. chips = warp_im(img, trans_mat, dst_size)
  44. crop_imgs.append(chips)
  45. return crop_imgs
  46. chips = extract_image_chips1(img, points, 144, 0.37)
  1. 对应的四个旋转矩阵为
  2. array([[ 1.64844171e+00, 8.79546584e-02, -3.74864686e+02],
  3. [-8.79546584e-02, 1.64844171e+00, -4.19148490e+01]])
  4. array([[ 1.84494424e+00, -1.19245336e-02, -9.78834105e+01],
  5. [ 1.19245336e-02, 1.84494424e+00, -4.60283424e+01]])
  6. array([[ 1.48460564e+00, 2.23927075e-01, -5.41851323e+02],
  7. [-2.23927075e-01, 1.48460564e+00, 4.27248780e+01]])
  8. array([[ 1.79634282e+00, -3.48299747e-03, -8.71374899e+02],
  9. [ 3.48299747e-03, 1.79634282e+00, -5.51812653e+01]])

结果如下图所示。

conference:

face alignment[Ordinary Procrustes Analysis]的更多相关文章

  1. Procrustes Analysis普氏分析法

    选取N幅同类目标物体的二维图像,并用上一篇博文的方法标注轮廓点,这样就得到训练样本集: 由于图像中目标物体的形状和位置存在较大偏差,因此所得到的数据并不具有仿射不变性,需要对其进行归一化处理.这里采用 ...

  2. python AI换脸 用普氏分析法(Procrustes Analysis)实现人脸对齐

    1.图片效果 2.原代码 # !/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2015 Matthew Earl # # Permis ...

  3. 用200行Python代码“换脸”

    介绍 本文将介绍如何编写一个只有200行的Python脚本,为两张肖像照上人物的“换脸”. 这个过程可分为四步: 检测面部标记. 旋转.缩放和转换第二张图像,使之与第一张图像相适应. 调整第二张图像的 ...

  4. 学习换脸:Switching Eds: Face swapping with Python, dlib, and OpenCV

    学习GitHub上比较火换脸博客,原英文版:https://matthewearl.github.io/2015/07/28/switching-eds-with-python/ 系统win10,x6 ...

  5. 人脸识别中的Procruster analysis应用

    本文中,我们通过Procrustes analysis来处理特征点,Procrustes analysis算法可以参考:http://en.wikipedia.org/wiki/Procrustes_ ...

  6. ECCV 2014 Results (16 Jun, 2014) 结果已出

    Accepted Papers     Title Primary Subject Area ID 3D computer vision 93 UPnP: An optimal O(n) soluti ...

  7. R语言︱常用统计方法包+机器学习包(名称、简介)

    一.一些函数包大汇总 转载于:http://www.dataguru.cn/thread-116761-1-1.html 时间上有点过期,下面的资料供大家参考基本的R包已经实现了传统多元统计的很多功能 ...

  8. PDM:Training Models of Shape from Sets of Examples

    这篇论文介绍了一种创建柔性形状模型(Flexible Shape Models)的方法--点分布模型(Point Distribution Model).该方法使用一系列标记点来表示形状,重要的是根据 ...

  9. ML—R常用多元统计分析包(持续更新中……)

    基本的R包已经实现了传统多元统计的很多功能,然而CRNA的许多其它包提供了更深入的多元统计方法,下面要综述的包主要分为以下几个部分: 1) 多元数据可视化(Visualising multivaria ...

随机推荐

  1. 一位ML工程师构建深度神经网络的实用技巧

    一位ML工程师构建深度神经网络的实用技巧 https://mp.weixin.qq.com/s/2gKYtona0Z6szsjaj8c9Vg 作者| Matt H/Daniel R 译者| 婉清 编辑 ...

  2. Android 自定义AlertDialog的实现

    Android默认的AlertDialog太单调,我们可以通过继承原生的Dialog来实现自定义的Dialog. 本文的自定义Dialog和原生的AlertDialog的创建方式类似,通过一个静态Bu ...

  3. 口碑点餐相关问题FAQ

    1.菜品上传中:出现重复错误或者违禁词 检查并修改商家中心本次上传中的重复菜品,或者删除口碑掌柜以及第三方平台已添加的重复菜品(重复菜品临时快捷办法:修改菜品名称) 2.手持pos 打开自动接单,无响 ...

  4. spring静态代理和动态代理

    本节要点: Java静态代理 Jdk动态代理 1 面向对象设计思想遇到的问题 在传统OOP编程里以对象为核心,并通过对象之间的协作来形成一个完整的软件功能,由于对象可以继承,因此我们可以把具有相同功能 ...

  5. 漫说996icu黑名单

    以实际行动声援996icu项目. https://github.com/996icu/996.ICU/blob/master/blacklist/blacklist.md 996公司黑名单,京东,华为 ...

  6. JavaWeb 过滤器——验证登录 防止未登录进入界面

    昨天刚刚完成老师布置的一个Web小项目,项目中用到了两个过滤器(编码过滤.登录过滤) 比如电商网页中有些不需要登录也能访问(首页.商品详细信息...),其他都需要过滤在会话作用域(session)中是 ...

  7. 三、Tableau筛选器的使用

    一.使用筛选器制作联动效果 注意,下面两幅图以‘是否盈利’来添加颜色标签 图一:地图 图二:月度销售客户细分 图三:月度销售产品类别 1.新增仪表盘:将图一.图二.图三拉进同一个仪表盘 2.在图一原图 ...

  8. 简单易懂的程序语言入门小册子(7):基于文本替换的解释器,加入continuation,重构解释器

    或许在加入continuation之前要先讲讲费这么大劲做这个有什么意义. 毕竟用不用continuation的计算结果都是一样的. 不过,这是一个兴趣使然的系列,学习这些知识应该完全出于好奇与好玩的 ...

  9. 信号量的基本概念与使用semget,semop

    1.信号量的基本概念 信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的同步访问. 临界资源可以简单的理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源 可以是一段代码 ...

  10. 通过一篇YAML来学习YAML

    yaml 兼容 json,至今已发展至 1.2版.支持N多种编程语言.官网:http://www.yaml.org/ 格式在线校验:http://nodeca.github.io/js-yaml/ 下 ...