Python人脸替换黑魔法

2024-01-28 03:40
文章标签 python 替换 人脸 黑魔法

本文主要是介绍Python人脸替换黑魔法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

知乎上看到的,mark一下~原文链接

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



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


景略集智:大意了!居然有人用AI技术制作假AV!​zhuanlan.zhihu.com图标 景略集智:AI已经决定了,ta就是未来每一届奥斯卡最佳男主。​zhuanlan.zhihu.com图标


怎么样,是不是被这种换脸的效果惊到了?其实即便是不借助神经网络,我们用 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 TooManyFacesif len(rects) == 0:raise NoFacesreturn 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 下载。

sourceforge.net/project


用普氏分析法(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 -= c1points2 -= c2s1 = numpy.std(points1)s2 = numpy.std(points2)points1 /= s1points2 /= s2U, S, Vt = numpy.linalg.svd(points1.T * points2)R = (U * Vt).Treturn 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 += 1im1_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 = 11def 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.0im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)return immask = 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.com/matthewearl/


参考资料: matthewearl.github.io/2

weixin.qq.com/r/80QiOi3 (二维码自动识别)

这篇关于Python人脸替换黑魔法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/652366

相关文章

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python正则表达式匹配和替换的操作指南

《Python正则表达式匹配和替换的操作指南》正则表达式是处理文本的强大工具,Python通过re模块提供了完整的正则表达式功能,本文将通过代码示例详细介绍Python中的正则匹配和替换操作,需要的朋... 目录基础语法导入re模块基本元字符常用匹配方法1. re.match() - 从字符串开头匹配2.

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

通过Docker容器部署Python环境的全流程

《通过Docker容器部署Python环境的全流程》在现代化开发流程中,Docker因其轻量化、环境隔离和跨平台一致性的特性,已成为部署Python应用的标准工具,本文将详细演示如何通过Docker容... 目录引言一、docker与python的协同优势二、核心步骤详解三、进阶配置技巧四、生产环境最佳实践

Python一次性将指定版本所有包上传PyPI镜像解决方案

《Python一次性将指定版本所有包上传PyPI镜像解决方案》本文主要介绍了一个安全、完整、可离线部署的解决方案,用于一次性准备指定Python版本的所有包,然后导出到内网环境,感兴趣的小伙伴可以跟随... 目录为什么需要这个方案完整解决方案1. 项目目录结构2. 创建智能下载脚本3. 创建包清单生成脚本4

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、