前方高能!不可描述的电影里主演竟是知名女星?告诉你怎么做到的

还记得吗?去年冬天,在国外 AI 圈有个事情闹得很火:知名论坛 Reddit 上忽然出现一个叫 deepfakes 的大神,借助神经网络实现了人脸替换,让一些好莱坞女星“出演”了 一部分不可描述的电影。

怎么样?是不是被这种换脸效果惊呆了,其实这种事情借用Python也可以做到。

下面就来看看这种“换脸”技术是怎么实现的吧!


在本文,我们会介绍如何通过一段简短的 Python 脚本(200行)将一张图片中面部特征自动替换为另外一张图片中的面部特征。也就是实现下面这样的效果:


image

具体过程分为四个步骤:

  • 检测面部标志;
  • 旋转、缩放和平移图 2 以适应图 1;
  • 调整图 2 的白平衡以匹配图 1;
  • 将图 2 的特征融合到图 1 中;

本脚本的完整代码地址见文末。

使用dlib提取面部标志

本脚本使用 dlib 的 Python bindings 来提取面部标志:


image

dlib 实现了 Vahid Kazemi 和 Josephine Sullivan 所著论文《One Millisecond Face Alignment with an Ensemble of Regression Tree》一文中描述的算法。算法本身非常复杂,但是通过 dlib的接口实现它非常简单:


image

get_landmarks() 函数 以 numpy 数组的形式接收图像,并返回一个 68×2 的元素矩阵。矩阵的每一行与输入图像中特定特征点的 x,y 坐标相对应。

特征提取器(predictor)需要一个大概的边界框作为算法的输入。这将由传统的面部检测器(detector)提供。该面部检测器会返回一个矩形列表,其中每一个矩形与图像中的一张人脸相对应。

生成 predictor 需要预先训练好的模型。该模型可在 dlib sourceforge repository 下载。

https://sourceforge.net/projects/dclib/files/dlib/v18.10/shape_predictor_68_face_landmarks.dat.bz2/download

用普氏分析法(Procrustes Analysis)实现人脸对齐

现在我们已经有两个面部标志矩阵,其中的每一行都含有某个面部特征的坐标(如第 30 行给出了鼻尖的坐标)。我们现在只要弄明白如何旋转、平移和缩放第一个向量的所有点,使其尽可能匹配第二个向量中的点。同理,同样的变换可用于将第二张图叠加在第一张图上。

为使其更加数学化,我们设 T,s 和 R,并求如下等式最小值:


image

其中,R 是一个 2×2 的正交矩阵,s 是一个标量,T 是一个二维向量,pi 和 qi 是之前计算出的面部标志矩阵行标和列标。

事实证明,这类问题用常规普氏分析法(Ordinary Procrustes Analysis)可以解决:


image

我们逐步分析一下代码:

  1. 将输入矩阵转换为浮点型。这也是后续步骤的必要条件。
  2. 将每一个点集减去它的矩心。一旦为这两个新的点集找到了一个最佳的缩放和旋转方法,这两个矩心c1和c2就可以用来找到完整的解决方案。
  3. 同样,将每一个点集除以它的标准偏差。这消除了缩放偏差。
  4. 使用奇异值分解(singular value decomposition)计算旋转部分。请参阅维基百科有关Orthogonal Procrustes Problem的文章,以了解它的具体工作原理。
  5. 将整个变换过程以仿射变换矩阵形式返回。

之后,返回结果可以插入 OpenCV 的 cv2.warpAffine 函数,将第二个图片映射到第一个图片上:


image

校正第二张图片的颜色

如果此时我们试图直接叠加面部特征,很快会发现一个问题:


image

这样肯定是没法儿看的…


image

两幅图像之间不同的肤色光线造成了覆盖区域边缘的不连续。所以我们尝试修正它:


image

现在效果怎么样?我们瞅瞅:


image

这不是更奇怪了么…


image

此函数试图改变图 2 的颜色来匹配图 1,也就是用 im2 除以 im2 的高斯模糊,然后乘以 im1 的高斯模糊。在这里我们使用了颜色平衡( RGB scaling colour-correction),但不是直接使用全图的常数比例因子,而是采用每个像素的局部比例因子。

通过这种方法也只能在某种程度上修正两图间的光线差异。比如说,如果图 1 的光线来自某一边,但图 2 的光线非常均匀,校色后图 2 也会出现有一边暗一些的情况。

也就是说,这是一个相当粗糙的解决方案,而且关键在于大小适当的高斯内核。如果太小,图 2 中会出现图 1 的面部特征。如果太大,内核会跑到被像素覆盖的面部区域之外,并变色。这里的内核大小为瞳距的 0.6 倍。

将图 2 的特征融合到图 1 中

用一个蒙版(mask)来选择图 2 和图 1 应被最终显示的部分:


image

值为 1 (白色)的地方为图 2 应显示的区域,值为 0 (黑色)的地方为图 1 应显示的区域。值在 0 和 1 之间的地方为图 1 图 2 的混合区域。

这是生成上述内容的代码:


image

image

我们来分析一下:

  • 常规的 get_face_mask() 函数定义是:为一张图像和一个标志矩阵生成一个蒙版。蒙版会画出两个白色的凸多边形:一个是眼睛周围的区域,一个是鼻子和嘴部周围的区域。之后,蒙版的边缘区域向外羽化 11 个像素,这可以帮助消除剩下的不连续部分。
  • 为图 1 图 2 生成面部蒙版。使用与步骤 2 中的转换,可以使图 2 的蒙版转换至图 1 的坐标空间。
  • 之后,对所有元素取最大值操作,将这两个蒙版合二为一。这样做是为了保证图 1 的特征也能被覆盖的同时图 2 特征能显示出来。

最后,将蒙版应用于最终图像:

<pre style=”margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;”>

output_im = im1 * (1.0 - combined_mask) + warped_corrected_im2 * combined_mask

</pre>


image
https://www.jianshu.com/p/a0bbca534f4f

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论