翻译:小马哥

编辑:船长

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

后来根据这个项目又衍生了一个叫 FakeAPP 的桌面应用,可以让尼古拉斯·凯奇这样的明星随心所欲的“出演”任何电影,当然换成任何人的脸部都可以。我们曾详细分享过这些项目:

怎么样,是不是被这种换脸的效果惊到了?其实即便是不借助神经网络,我们用 Python 和一些 Python 库也能实现换脸,只不过替换的是静态图像中的人脸,但凭此也足以显示出 Python 的“神秘力量”。

我们下面就传授一下这门 Python “换脸”大法。

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

具体过程分为四个步骤:

检测面部标志;

旋转、缩放和平移图 2 以适应图 1;

调整图 2 的白平衡以匹配图 1;

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

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

使用dlib提取面部标志

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

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

PREDICTOR_PATH = "/home/matt/dlib-18.16/shape_predictor_68_face_landmarks.dat"

detector = dlib.get_frontal_face_detector()

predictor = dlib.shape_predictor(PREDICTOR_PATH)

def get_landmarks(im):

rects = detector(im, 1)

if len(rects) > 1:

raise TooManyFaces

if len(rects) == 0:

raise NoFaces

return numpy.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])

复制代码

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

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

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

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

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

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

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

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

def transformation_from_points(points1, points2):

points1 = points1.astype(numpy.float64)

points2 = points2.astype(numpy.float64)

c1 = numpy.mean(points1, axis=0)

c2 = numpy.mean(points2, axis=0)

points1 -= c1

points2 -= c2

s1 = numpy.std(points1)

s2 = numpy.std(points2)

points1 /= s1

points2 /= s2

U, S, Vt = numpy.linalg.svd(points1.T * points2)

R = (U * Vt).T

return numpy.vstack([numpy.hstack(((s2 / s1) * R,

c2.T - (s2 / s1) * R * c1.T)),

numpy.matrix([0., 0., 1.])])

复制代码

我们逐步分析一下代码:

1.将输入矩阵转换为浮点型。这也是后续步骤的必要条件。

2.将每一个点集减去它的矩心。一旦为这两个新的点集找到了一个最佳的缩放和旋转方法,这两个矩心c1和c2就可以用来找到完整的解决方案。

3.同样,将每一个点集除以它的标准偏差。这消除了缩放偏差。

4.使用奇异值分解(singular value decomposition)计算旋转部分。请参阅维基百科有关Orthogonal Procrustes Problem的文章,以了解它的具体工作原理。

5.将整个变换过程以仿射变换矩阵形式返回。

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

def warp_im(im, M, dshape):

output_im = numpy.zeros(dshape, dtype=im.dtype)

cv2.warpAffine(im,

M[:2],

(dshape[1], dshape[0]),

dst=output_im,

borderMode=cv2.BORDER_TRANSPARENT,

flags=cv2.WARP_INVERSE_MAP)

return output_im

复制代码

校正第二张图片的颜色

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

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

COLOUR_CORRECT_BLUR_FRAC = 0.6

LEFT_EYE_POINTS = list(range(42, 48))

RIGHT_EYE_POINTS = list(range(36, 42))

def correct_colours(im1, im2, landmarks1):

blur_amount = COLOUR_CORRECT_BLUR_FRAC * numpy.linalg.norm(

numpy.mean(landmarks1[LEFT_EYE_POINTS], axis=0) -

numpy.mean(landmarks1[RIGHT_EYE_POINTS], axis=0))

blur_amount = int(blur_amount)

if blur_amount % 2 == 0:

blur_amount += 1

im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)

im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)

# Avoid divide-by-zero errors.

im2_blur += 128 * (im2_blur <= 1.0)

return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64) /

im2_blur.astype(numpy.float64))

复制代码

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

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

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

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

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

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

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

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

LEFT_EYE_POINTS = list(range(42, 48))

RIGHT_EYE_POINTS = list(range(36, 42))

LEFT_BROW_POINTS = list(range(22, 27))

RIGHT_BROW_POINTS = list(range(17, 22))

NOSE_POINTS = list(range(27, 35))

MOUTH_POINTS = list(range(48, 61))

OVERLAY_POINTS = [

LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS,

NOSE_POINTS + MOUTH_POINTS,

]

FEATHER_AMOUNT = 11

def draw_convex_hull(im, points, color):

points = cv2.convexHull(points)

cv2.fillConvexPoly(im, points, color=color)

def get_face_mask(im, landmarks):

im = numpy.zeros(im.shape[:2], dtype=numpy.float64)

for group in OVERLAY_POINTS:

draw_convex_hull(im,

landmarks[group],

color=1)

im = numpy.array([im, im, im]).transpose((1, 2, 0))

im = (cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0) > 0) * 1.0

im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)

return im

mask = get_face_mask(im2, landmarks2)

warped_mask = warp_im(mask, M, im1.shape)

combined_mask = numpy.max([get_face_mask(im1, landmarks1), warped_mask],

axis=0)

复制代码

我们来分析一下:

常规的 get_face_mask() 函数定义是:为一张图像和一个标志矩阵生成一个蒙版。蒙版会画出两个白色的凸多边形:一个是眼睛周围的区域,一个是鼻子和嘴部周围的区域。之后,蒙版的边缘区域向外羽化 11 个像素,这可以帮助消除剩下的不连续部分。

为图 1 图 2 生成面部蒙版。使用与步骤 2 中的转换,可以使图 2 的蒙版转换至图 1 的坐标空间。

之后,对所有元素取最大值操作,将这两个蒙版合二为一。这样做是为了保证图 1 的特征也能被覆盖的同时图 2 特征能显示出来。

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

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

复制代码

哈,换脸成功!

附:本项目代码地址:Github

python 人脸替换_萌新如何用Python实现人脸替换?相关推荐

  1. 萌新如何用Python实现人脸替换升级看高级程序员一步一步带你进阶

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

  2. python多线程输出_萌新python多线程

    刚开始学习多线程的时候闹了个笑话在使用python线程打印东西的时候发现自己定的的线程数据没有打印,找半天都没有找到问题,也没好意思问,自己憋了半天才发现问题! 代码如下: import thread ...

  3. python浪漫文艺_从零开始,如何用python定时更新炫酷文艺的桌面背景

    先介绍一个网站--句子迷,网站上有众多网友分享的经典句子,我们将搜索我们喜欢的作者,爬取所有该作者的名句. 我们利用selenium来进行可视化的爬虫,首先要pip install selenium, ...

  4. (萌新笔记)python的复习笔记

    简介:python,作为我在暑假入门的第一门语言,我发现它特别简洁和实用,因此我想记录我的python学习过程,现在刚刚大一入门(我比较弱鸡,所以本文可能会有记录很多我初学时遇到的问题,还请大家斟酌观 ...

  5. python 字体颜色_超萌新级的Python学习心得——字体颜色

    概要:本文讲的是Python语言中在命令行中显示不同字体颜色的方法,如有错误,欢迎指正. 正文: 开发过程中,为了方便调试代码,以及处理错误信息,基本上是需要输出一些文本的,例如在使用try-catc ...

  6. python excel 打印文档_教你如何用Python轻轻松松操作Excel、Word、CSV,一文就够了,赶紧码住!!!...

    原标题:教你如何用Python轻轻松松操作Excel.Word.CSV,一文就够了,赶紧码住!!! 作者:奈何缘浅wyj Python 操作 Excel 常用工具 数据处理是 Python 的一大应用 ...

  7. ctfshow_萌新_萌新隐藏题

    https://harvey-blog.com/Safety/525 0x01 萌新认证 加群召唤flag 0x02 萌新_密码 萌新不会 53316C6B5A6A42684D3256695A4456 ...

  8. 手机版python3h如何自制游戏_教你如何用 Python 写一个小游戏

    教你如何用 Python 写一个小游戏 引言 最近 python 语言大火, 除了在科学计算领域 python 有用武之地之外, 在游戏后台等方面, python 也大放异彩, 本篇博文将按照正规的项 ...

  9. python怎么画人像_教你如何用Python画出心目中的自己

    原标题:教你如何用Python画出心目中的自己 引言:人脸图像的生成在各个行业有着重要应用,例如刑事调查.人物设计.教育培训等.然而一幅逼真的人脸肖像,对于职业画家也要至少数小时才能绘制出来:对于从未 ...

最新文章

  1. 等值连接_干货:16种等值线图的解读与应用,用快速规律解题
  2. 解决libuuid.so.1 no version information available问题
  3. word中光标选择一列文字_Word中文字排版对齐很难?只因你没掌握这几招排版技巧!...
  4. python 网盘上传_python学习笔记 day32 实现网盘上传下载功能
  5. Rational rose中实心菱形的画法
  6. 玩linux笔记——持续更新
  7. Javascript 你不知道的事
  8. Spring源码由浅入深系列一 简介
  9. python哪个方向工资高_【看完这五大Python就业方向,你选择哪个?】- 环球网校
  10. threejs精灵(Sprite)
  11. 素材网站|设计师下半年的工作流程至少减少了一半...
  12. 基于Spring Security的认证授权_应用详解_会话管理_Spring Security OAuth2.0认证授权---springcloud工作笔记129
  13. 修改jar 注入_Apache Tika命令注入漏洞挖掘
  14. PHP如何在照片下面写一行字_如何使用php分别插入照片和文字?
  15. cadz轴归零命令_CAD中所有图形实现统一标高(Z轴归零)的方法,统一标高的快捷键命令...
  16. BZOJ 2339 【HNOI2011】 卡农
  17. 对于流媒体的一些认识
  18. Win 10 Visual Studio 2019 C# .net 5 简繁体转换
  19. python 北京出租车收费3.45_北京出租车计费标准和价目表
  20. 统计软件是其他计算机软件吗,电脑统计软件,statistical computing software,音标,读音,翻译,英文例句,英语词典...

热门文章

  1. 石家庄市中考计算机试题,河北中考信息技术Word考题.docx
  2. 「Python语法结构」数据类型与运算符示例(7)
  3. 使用LATTICE的XO3器件进行调试时,reveal报错“Falied to link signal”怎么解决?
  4. android 5.0 按钮颜色,Android 5.0系统默认颜色
  5. 科士达STATIONAIR系列精密空调远程监控解决方案
  6. bat: 删除文件、文件夹
  7. 2022年湖北荆门助理工程师职称评审条件是什么呢? 甘建二
  8. 树莓派魔镜MagicMirror —— 8 MagicMirror基本模块设计
  9. 通配符?,*,**区别
  10. Beef加载msf插件---metasploit对IE浏览器的极光漏洞进行渗透利用