【换脸详细教程】手把手教你进行AI换脸:换脸流程及源码详解

news2024/11/24 9:19:37

目录

  • 1. 换脸基本原理
  • 2 人脸检测及可视化
  • 3. 人脸轮廓点检测及可视化
  • 4. 人脸图像变换--仿射变换
  • 5. 生成遮罩并直接替换人脸
  • 6. 人脸颜色校正

最近AI换脸貌似比较火爆,就稍微研究了一下相关了内容。AI换脸是一个娱乐性比较强的应用,这种错位感让人觉得非常有趣。很多人都以为这是什么黑科技,但这里想告诉大家,AI换脸其实很简单。只需要你会一点Python基础,就可以实现自己的AI换脸程序。

本文将详细介绍图片换脸的基本原理以及实际的换脸过程。感兴趣的小伙伴可以一起试试哦~所有内容均已经打包好,获取方式如下:

关注GZH:阿旭算法与机器学习,回复:“换脸”即可获取本文源码、测试文件等内容,欢迎共同学习交流

1. 换脸基本原理

为了做人脸相关的工作,首先我们需要获得人脸的轮廓点。通常轮廓点检测也被称为 Face Alignment。本文的AI换脸实现就是建立在68个人脸轮廓点(Landmarks)的基础之上。而 Face Alignment 必须依赖一个人脸检测器提供人脸位置的矩形框,即 Face Detection。也就是说,本文所介绍的换脸或者其他精细化人脸操作的基础是 Face Detection + Face Alignment。但是,这两个技术并不需要我们亲自开发,dlib已经提供了效果不错、使用简便的第三方库。

AI换脸技术路线图的主要步骤如下:
在这里插入图片描述

我们想要将源图像B中的人脸放置到目标图像A中,这就需要将B中的人脸变换到A中人脸所在位置,然后截取对应区域并替换。如上图所示,这一步骤可以由OpenCV支持完成。最后,由于两张图像的人脸可能存在光照、色相等方面的不一致,为了替换后更加逼真,所以需要对图像B的颜色进行一些调整。

2 人脸检测及可视化

目标检测(Object Detection)一直都是计算机视觉应用的基础,这也是每次出现新的目标检测算法总能引起很大轰动的原因。完成人脸相关的任务,那自然就是需要人脸检测了。dlib提供了一个人脸检测器,可以使用如下方式获取:
face_detector = dlib.get_frontal_face_detector()
对输入的两幅带有人脸的图像进行 Face Detection,就可以获得其中人脸位置的矩形框,代码如下:

# coding:utf-8
import os, sys
import numpy as np
import cv2, dlib

img1_path = 'imgs/2.jpg'
img2_path = 'imgs/1.jpg'

face_detector = dlib.get_frontal_face_detector()
img1 = cv2.imread(img1_path, cv2.IMREAD_COLOR)
img2 = cv2.imread(img2_path, cv2.IMREAD_COLOR)
face1 = face_detector(img1, 1)
face2 = face_detector(img2, 1)
print(face1, face2)

输出结果:

rectangles[[(171, 201) (438, 468)]] rectangles[[(171, 231) (438, 498)]]

fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(img1)
rect = face1[0]
plt.plot([rect.left(), rect.right(), rect.right(), rect.left(), rect.left()],
         [rect.top(), rect.top(), rect.bottom(), rect.bottom(), rect.top()])
plt.subplot(1,2,2)
plt.imshow(img2)
rect = face2[0]
plt.plot([rect.left(), rect.right(), rect.right(), rect.left(), rect.left()],
         [rect.top(), rect.top(), rect.bottom(), rect.bottom(), rect.top()])
plt.show()

在这里插入图片描述

3. 人脸轮廓点检测及可视化

此处直接使用dlib提供的模型文件shape_predictor_68_face_landmarks。下载解压之后,就可以使用以下代码载入
landmark_detector = dlib.shape_predictor(LANDMARK_MODEL_PATH)
关于dlib.shape_predictor更多的使用方法,可以访问官网的检测说明。对输入的这两幅图像检测人脸轮廓点的代码及检测效果如下:

LANDMARK_MODEL_PATH = os.path.expanduser('models/shape_predictor_68_face_landmarks.dat')
landmark_detector = dlib.shape_predictor(LANDMARK_MODEL_PATH)
pts1 = landmark_detector(img1, face1[0])
pts1 = np.array([[pt.x,pt.y] for pt in pts1.parts()])
pts2 = landmark_detector(img2, face2[0])
pts2 = np.array([[pt.x,pt.y] for pt in pts2.parts()])
fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(img1)
rect = face1[0]
plt.plot([rect.left(), rect.right(), rect.right(), rect.left(), rect.left()],
         [rect.top(), rect.top(), rect.bottom(), rect.bottom(), rect.top()])
plt.scatter(np.squeeze(pts1[:, 0]), np.squeeze(pts1[:, 1]), linewidths=1)
plt.subplot(1,2,2)
plt.imshow(img2)
rect = face2[0]
plt.plot([rect.left(), rect.right(), rect.right(), rect.left(), rect.left()],
         [rect.top(), rect.top(), rect.bottom(), rect.bottom(), rect.top()])
plt.scatter(np.squeeze(pts2[:, 0]), np.squeeze(pts2[:, 1]), linewidths=1)
plt.show()

在这里插入图片描述

4. 人脸图像变换–仿射变换

现在我们需要将上图中右边的人脸放到左边图像的人脸处,但很显然,两张人脸的大小和位置都不一样。无法直接操作,所以需要进行图像变换,才能将第二幅图中的人脸投影到第一幅中人脸的位置。

现在,我们已经得到了两张人脸上的landmark坐标,那么求解变换就不是一个很难的问题。第一,我们可以在这68个点中选择三个稳定的点构成3个匹配点对,然后利用OpenCV中的接口求解一个仿射变换矩阵。当然,这种方式会导致对轮廓点的利用率很低,而且结果常常不是太稳定。第二,我们可以假设一个特殊的仿射变换 —— 相似变换可以达到目的,那么就能够利用这68个点对完成普氏分析。其流程是,先计算缩放因子,然后求解一个旋转变换,最后再求解平移变换,这样就可以构成一个完整的相似变换,变换的代码与结果如下:

def compute_affine_param(pts1, pts2):
    pts1, pts2 = np.mat(pts1), np.mat(pts2)
    pts1, pts2 = pts1.astype(np.float64), pts2.astype(np.float64)
    # centrallize
    center1 = np.mean(pts1, axis=0)
    center2 = np.mean(pts2, axis=0)
    pts1 -= center1
    pts2 -= center2
    print()
    # normalize
    std1 = np.std(pts1)
    std2 = np.std(pts2)
    pts1 /= std1
    pts2 /= std2
    # compute rotation param by svd
    U, S, V = np.linalg.svd(pts1.transpose() * pts2)
    R = (U * V).transpose()
    # concat affine tranformation matrix
    tmp = (std2/std1)*R
    T = center2.transpose() - tmp*(center1.transpose())
#     print(T, tmp, T.shape, tmp.shape, center1.shape, center2.shape)
    affine = np.vstack([np.hstack([tmp, T]), np.matrix([0.0, 0.0, 1.0])])
    return affine
affine_params = compute_affine_param(pts1, pts2)

# 仿射变换
w_img2 = np.zeros(img1.shape, dtype=img2.dtype)
cv2.warpAffine(img2, affine_params[:2], (w_img2.shape[1], w_img2.shape[0]), 
               dst=w_img2, flags=cv2.WARP_INVERSE_MAP, borderMode=cv2.BORDER_TRANSPARENT)

fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(img2)
plt.subplot(1,2,2)
plt.imshow(w_img2)

在这里插入图片描述

5. 生成遮罩并直接替换人脸

可以选择人脸的一些部位轮廓点生成遮罩,这里选择两只眼睛、眉毛、鼻子和嘴巴这几部分组成一个遮罩的区域。生成遮罩的算法可以直接调用OpenCV中的cv2.fillConvexPoly()函数完成。在两张原始图像上直接生成的遮罩思的代码与效果如下:

JAW_IDX = list(range(0, 17))
FACE_IDX = list(range(17, 68))
RIGHT_BROW_IDX = list(range(17, 22))
LEFT_BROW_IDX = list(range(22, 27))
NOSE_IDX = list(range(27, 35))
RIGHT_EYE_IDX = list(range(36, 42))
LEFT_EYE_IDX = list(range(42, 48))
MOUTH_IDX = list(range(48, 61))

COVER_IDX = [LEFT_BROW_IDX + RIGHT_EYE_IDX + LEFT_EYE_IDX
             + RIGHT_BROW_IDX + NOSE_IDX + MOUTH_IDX]
def get_mask(img_shape, pts):
    img = np.zeros(img_shape[:2], dtype=np.float64)
    for idx in COVER_IDX:
        cv2.fillConvexPoly(img, cv2.convexHull(pts[idx]), color=1)
    img = np.array([img, img, img]).transpose([1,2,0])
    img = (cv2.GaussianBlur(img, (13, 13), 0) > 0) * 1.0
    img = cv2.GaussianBlur(img, (13, 13), 0)
    return img

mask1 = get_mask(img1.shape, pts1)
mask2 = get_mask(img2.shape, pts2)
fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(mask1)
plt.subplot(1,2,2)
plt.imshow(mask2)
plt.show()

在这里插入图片描述
根据这两个遮罩,可以查看一下图像A和图像B中选中的区域:

fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow((mask1*img1).astype(np.uint8))
plt.subplot(1,2,2)
plt.imshow((mask2*img2).astype(np.uint8))
plt.show()

在这里插入图片描述
从上图中可以看出,这就是我们需要替换的区域,但是很明显,这样的遮罩是不能够直接使用的,因为人脸的位置没有对齐。所以需要对源人脸图像B的遮罩作上面已经求解的相似变换。然后对比图像A和warp图像B的遮罩,取并集形成一个统一的遮罩,这样就能保证遮罩操作的区域是在两幅图像中相同的位置。
目标图像A的遮罩与统一遮罩的对比效果图如下:

w_mask2 = np.zeros(img1.shape, dtype=img2.dtype)
cv2.warpAffine(mask2, affine_params[:2], (w_img2.shape[1], w_img2.shape[0]),
               dst=w_img2, flags=cv2.WARP_INVERSE_MAP, borderMode=cv2.BORDER_TRANSPARENT)
mix_mask = np.max([mask1, w_mask2], axis=0)
fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(mask1)
plt.subplot(1,2,2)
plt.imshow(mix_mask)
plt.show()

在这里插入图片描述
显示统一的遮罩区域:

fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow((mix_mask*img1).astype(np.uint8))
plt.subplot(1,2,2)
plt.imshow((mix_mask*w_img2).astype(np.uint8))
plt.show()

在这里插入图片描述
直接替换的效果图如下:

rough_dst_img = img1 * (1.0 - mix_mask) + w_img2 * mix_mask
fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(img1)
plt.subplot(1,2,2)
plt.imshow(rough_dst_img.astype(np.uint8))
plt.show()

在这里插入图片描述
从结果可以发现:人脸确实给替换上了,但是脸色就太诡异了。这种状况很常见,因为很难保证任意的两个人脸图像的光照、色相以及对比度等状况完全一致。因此需要对人脸图像B进行颜色调整。

6. 人脸颜色校正

这里使用一种比较简单的颜色平衡方法。矫正图像B的颜色之后,效果如下:

def adjust_color(img1, img2, pts, blur_factor=0.5):
    mean_le = np.mean(pts[LEFT_EYE_IDX], axis=0)
    mean_re = np.mean(pts[RIGHT_EYE_IDX], axis=0)
    blur_degree = int(blur_factor * np.linalg.norm(mean_le - mean_re))
    print(blur_degree)
    if blur_degree % 2 == 0:
        blur_degree += 1
    blur_img1 = cv2.GaussianBlur(img1, (blur_degree, blur_degree), 0)
    blur_img2 = cv2.GaussianBlur(img2, (blur_degree, blur_degree), 0)
    blur_img1 = blur_img1.astype(np.float64)
    blur_img2 = blur_img2.astype(np.float64)
    blur_img2 += (blur_img2 <= 1.0) * 128
    img2 = img2.astype(np.float64)
    return img2 * blur_img1 / blur_img2

adjust_img = adjust_color(img1, w_img2, pts1, blur_factor=0.5)
fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(img1)
plt.subplot(1,2,2)
plt.imshow(adjust_img.astype(np.uint8))
plt.show()

在这里插入图片描述

对颜色矫正之后的图像B使用已经生成的统一遮罩,并得到最终的换脸结果:

dst_img = img1 * (1.0 - mix_mask) + adjust_img * mix_mask
fig = plt.figure(figsize=(9.6, 5.4))
plt.subplot(1,2,1)
plt.imshow(img1)
plt.subplot(1,2,2)
plt.imshow(dst_img.astype(np.uint8))
plt.show()

在这里插入图片描述
上面换脸的结果整体看起来只能说勉强凑活,比没进行颜色矫正后的结果好了一点。但是还是需要说明,这里使用的颜色平衡的方式仍然非常粗糙。如果想要让融合之后的效果更加逼真,可能需要考虑在这方面花费更多的功夫。

本文所有内容均已经打包好,获取方式如下:

关注下方GZH:【阿旭算法与机器学习】,回复:【换脸】
即可获取本文源码、测试文件等内容,欢迎共同学习交流

如果文章对你有帮助,感谢点赞+关注!
好啦,以上便是换脸的整个流程,小伙伴们一起实操起来吧,欢迎共同学习交流~
小伙伴们有什么想法欢迎评论区留言交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/663464.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

搭建高性能数据库集群之一:主从复制

一、概述 1. 数据库主从概念、优点、用途   主从数据库是什么意思呢&#xff0c;主是主库的意思&#xff0c;从是从库的意思。数据库主库对外提供读写的操作&#xff0c;从库对外提供读的操作。 数据库为什么需要主从架构呢&#xff1f;   高可用&#xff0c;实时灾备&am…

手把手教你撸一个接口自动化测试平台(一)

项目构思&#xff1a;开发一个web版的接口自动化测试平台 功能包括&#xff1a;接口导入、自动化测试用例生成、自动化测试报告、可持续集成 项目框架&#xff1a;django vue mysql 技术要求&#xff1a;熟悉django后台开发、熟悉vue开发 第一节&#xff1a;搭建django 项…

被ChatGPT戏耍的周末

被ChatGPT戏耍的周末 1. 被ChatGPT戏耍全过程2. 拆穿ChatGPT的把戏3. AIGC与内容安全 1. 被ChatGPT戏耍全过程 电动垂直起降飞行器&#xff08;eVTOL&#xff0c;Electric Vertical Takeoff and Landing&#xff09;技术越来越成熟&#xff0c;为了解下相关产品我周末打开了Cha…

OpenGLES:相机实时滤镜四宫格、九宫格

一.概述 今天继续OpenGLES的学习 今天在之前博文《OpenGLES&#xff1a;GLSurfaceView实现Android Camera预览》 的基础上&#xff0c;使用OpenGLES实现相机 四宫格滤镜 和 九宫格滤镜。 二.四宫格 先定义几个名词&#xff1a; 之前博文中实现的相机普通预览叫&#xff1a;…

【人工智能】“AI + 算力 = 最强龙头”,你怎么看?

文章目录 一、AI 与算力相辅相成1.1 AI 和算力的概念1.2 AI 和算力的应用领域1.3 AI 需要算力的支持1.4 AI 和算力的结合带来的巨大价值1.4.1 人脸识别1.4.2 语音识别1.4.3 自动驾驶1.4.4 医疗诊断1.4.5 自然语言处理 二、AI算力催生“最强龙头”2.1 “最强龙头”的概念2.2 AI …

Unity核心4——SpriteShape

Sprite Shape 是精灵形状的意思&#xff0c;它主要是方便我们以节约美术资源为前提&#xff0c;制作 2D 游戏场景地形或者背景的 ​ 在 Window --> Package Manager 中搜索 2D&#xff0c;选择 2D Sprite Shape&#xff0c;导入项目 一、Sprite Shape Profile 精灵形状概述文…

FPGA基础知识-用户自定义原语

目录 学习目标 学习内容 1.UDP的组成 2.UDP定义规则 3.表示组合逻辑的UDP 4.表示时序逻辑的UDP 5.UDP表中的缩写符号 6.UDP设计指南 学习时间 学习总结 学习目标&#xff1a; 提示&#xff1a;这里可以添加学习目标 理解编写UDP的规则,明白UDP的各个组成部分。 学…

设计师常用的网页设计素材网站大全

设计师不仅需要源源不断的灵感&#xff0c;还需要与时俱进的网页设计素材。 本文推荐4个非常不错的设计素材网站 即时设计资源社区 ​即时设计资源社区是国内优秀的网页设计素材网站&#xff0c;内置阿里、字节、腾讯、京东、谷歌、华为等设计系统&#xff0c;超过3000UI组件…

DDoS攻击导致Azure和Outlook中断

微软已经证实&#xff0c;最近Azure、Outlook和OneDrive门户网站的中断是由于针对该公司服务的第7层DDoS攻击造成的。 这些攻击是由微软追踪到的一个名为Storm-1359的攻击组织造成的&#xff0c;他们自称是匿名苏丹。 故障发生在6月初&#xff0c;Outlook.com的网络门户在6月…

分布式配置中心Apollo中Namespace的类型整理

Namespace的类型 Namespace类型有三种&#xff1a; 【1】私有类型 【2】公共类型 【3】关联类型&#xff08;继承类型&#xff09; &#xff08;1&#xff09;私有类型 私有类型的Namespace具有private权限。例如上文提到的“application” Namespace就是私有类型。 &…

【计算机网络】运输层端口号、复用与分用

1、复用和分用 2.端口号 3.举例 4.详细学习视频 https://www.bilibili.com/video/BV1c4411d7jb?p58&vd_source621b166d35a3636b23f3c4d270272c53

WSL子系统启动报错 Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE

今天琢磨着WindowsLinux子系统研究研究新东西&#xff0c;结果当我启动WSL时却出现了下面的提示&#xff1a; WSL启动报错 由于未安装所需的特性&#xff0c;无法启动操作。 Error code: Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE问题排查 于是分析…

Audio API 实现音频播放器

市面上实现音频播放器的库有很多&#xff0c;比如wavesurfer.js、howler.js等等&#xff0c;但是都不支持大音频文件处理&#xff0c;100多M的文件就有可能导致程序崩溃。总之和我目前的需求不太符合&#xff0c;所以打算自己实现一个音频播放器&#xff0c;这样不管什么需求 在…

软件工程是否迎来iPhone时刻?

“软件工程是否迎来iPhone时刻&#xff1f;” 是2023K全球软件研发行业创新峰会上海站主会场的Panel discussion的主题&#xff0c;出场的几位嘉宾给出了不同的答案&#xff0c;其中有两位嘉宾给出了“No”&#xff0c;一位给出了“塞班时刻”&#xff08;后来给我朋友圈投票是…

设计模式—“状态变化”

在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?"状态变化"模式为这一问题提供了解决方案。 典型模式有:Memento、State 一、State 动机 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而…

18-BOM对象

一、是什么 &#x1f355;&#x1f355;&#x1f355;BOM (Browser Object Model)&#xff0c;浏览器对象模型&#xff0c;提供了独立于内容与浏览器窗口进行交互的对象 其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退&#xff0c;前进&#xff0c;刷新&#xff0…

SSM幼儿园管理系统的设计与实现-计算机毕设 附源码86673

SSM幼儿园管理系统的设计与实现 摘 要 21世纪时信息化的时代&#xff0c;几乎任何一个行业都离不开计算机&#xff0c;将计算机运用于幼儿管理系统也是十分常见的。过去使用手工的管理方式对幼儿园进行管理&#xff0c;造成了管理繁琐、难以维护等问题&#xff0c;如今使用计算…

搜索表单的触发方式

1、按键盘触发 配套监听 _keydownHandler: function (event) { // 获取表单数据 let formValue this.$[frm-mach-break].serializeMyForm(); let params { machineName: formValue.mach_id }; this.requestAjax(ajx-view-mach-break, params); }, onKeypress: function ({ ke…

抖音seo账号矩阵系统源码sign解密.技术

抖音SEO矩阵系统源码是一种用于优化抖音视频内容的工具&#xff0c;可以帮助用户提高抖音视频的搜索排名和流量&#xff0c;从而增加视频曝光和转化率。该系统包括两部分&#xff0c;即数据收集和分析模块以及SEO策略和实施模块。 返回示例 错误&#xff1a; { "ec…

搜索算法(五) DFS BFS 练习题

练习题 1.力扣https://leetcode.cn/problems/surrounded-regions/这题和417类似&#xff0c;都是从边界朝内部搜索&#xff0c;417用的是DFS&#xff0c;这里为了练习&#xff0c;就用BFS。 首先从四条边界得到‘O’的坐标&#xff0c;加入队列。接着一层一层搜索&#xff0c…