《计算机视觉》—— 换脸

  • 效果如下:
    在这里插入图片描述
  • 完整代码:
import cv2
import dlib
import numpy as np

JAW_POINTS = list(range(0, 17))
RIGHT_BROW_POINTS = list(range(17, 22))
LEFT_BROW_POINTS = list(range(22, 27))
NOSE_POINTS = list(range(27, 35))
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_EYE_POINTS = list(range(42, 48))
MOUTH_POINTS = list(range(48, 61))
FACE_POINTS = list(range(17, 68))

# 关键点集
POINTS = [LEFT_BROW_POINTS + RIGHT_EYE_POINTS +
          LEFT_EYE_POINTS + RIGHT_BROW_POINTS + NOSE_POINTS + MOUTH_POINTS]

# 处理为元组,后续使用方便
POINTStuple = tuple(POINTS)


def getFaceMask(im, keyPoints):  # 根据关键点获取脸部掩膜
    im = np.zeros(im.shape[:2], dtype=np.float64)
    for p in POINTS:
        points = cv2.convexHull(keyPoints[p])   # 获取凸包
        cv2.fillConvexPoly(im, points, color=1)     # 填充凸包,数字在0~1之间
    # 单通道im构成3通道im(3,行,列),改变形状(行、列、3)适应0penCV
    im = np.array([im, im, im]).transpose((1, 2, 0))
    im = cv2.GaussianBlur(im, (25, 25), 0)  # 需要根据具体调整
    return im


""" 求出b脸仿射变换到a脸的变换矩阵M,此处用到的算法难以理解,大家可直接跳过 """


def getM(points1, points2):
    points1 = points1.astype(np.float64)    # int8转换为浮点数类型
    points2 = points2.astype(np.float64)    # 转换为浮点数类型

    c1 = np.mean(points1, axis=0)       # 归一化:(数值-均值)/标准差
    c2 = np.mean(points2, axis=0)       # 归一化:(数值-均值)/标准差,均值不同,主要是脸五官位置大小不同
    points1 -= c1   # 减去均值
    points2 -= c2   # 减去均值
    s1 = np.std(points1)    # 方差计算标准差
    s2 = np.std(points2)    # 方差计算标准差

    points1 /= s1       # 除标准差,计算出归一化的结果
    points2 /= s2       # 除标准差,计算出归一化的结果

    # 奇异值分解,Singular Value Decomposition
    U, S, Vt = np.linalg.svd(points1.T * points2)
    R = (U * Vt).T      # 通过U和Vt找到R
    return np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T))


def getKeyPoints(im):       # 获取关键点
    rects = detector(im, 1)     # 获取人脸方框位置
    shape = predictor(im, rects[0])     # 获取关键点
    s = np.matrix([[p.x, p.y] for p in shape.parts()])
    return s

""" 修改b图的颜色值,与a图相同 """
def normalColor(a, b):
    ksize = (111, 111)      #非常大的核,去噪等运算时为11就比较大了
    aGauss = cv2.GaussianBlur(a, ksize, 0)     # 对a进行高斯滤波
    bGauss = cv2.GaussianBlur(b, ksize, 0)     # 对b进行高斯滤波
    weight = aGauss / bGauss        # 计算目标图像调整颜色的权重值,存在0除警告,可忽略。
    where_are_inf = np.isinf(weight)
    weight[where_are_inf] = 0
    return b * weight


a = cv2.imread("dlrb_3.jpg")    # 换脸A图片
b = cv2.imread("zly.jpg")       # 换脸B图片

detector = dlib.get_frontal_face_detector()     # 构造脸部位置检测器
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")   # 获取人脸关键点定位模型

aKeyPoints = getKeyPoints(a)        # 获取A图片的68关键点
bKeyPoints = getKeyPoints(b)        # 获取B图片的68关键点

bOriginal = b.copy()    # 不对原来的图片b进行破坏和修改

aMask = getFaceMask(a, aKeyPoints)      # 获取图片A的人脸掩膜
cv2.imshow('aMask', aMask)
cv2.waitKey()

bMask = getFaceMask(b, bKeyPoints)      # 获取图片B的人脸掩膜
cv2.imshow('bMask', bMask)
cv2.waitKey()

"""求出b脸仿射变换到a脸的变换矩阵M"""
M = getM(aKeyPoints[POINTStuple], bKeyPoints[POINTStuple])

"""将b的脸部(bmask)根据M仿射变换到a上"""
dsize = a.shape[:2][::-1]
# 目标输出与图像a大小一致
# 需要注意,shape是(行、列),warpAffine参数dsize是(列、行)
# 使用a.shape[:2][::-1],获取a的(列、行)

# 函数warpAffine(src,M,dsize,dst=None, flags=None, borderMode=None, borderValue=None)
# src:输入图像
# M:运算矩阵,2行3列的,
# dsize:运算后矩阵的大小,也就是输出图片的尺寸
# dst:输出图像
# flags:插值方法的组合,与resize函数中的插值一样,可以查看cv2.resize
# borderMode:边界模式,BORDER_TRANSPARENT表示边界透明
# borderValue:在恒定边框的情况下使用的borderValue值;默认情况下,它是 0
bMaskWarp = cv2.warpAffine(bMask, M, dsize, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
cv2.imshow("bMaskWarp", bMaskWarp)
cv2.waitKey()

"""获取脸部最大值(两个脸模板香加)"""
mask = np.max([aMask, bMaskWarp], axis=0)
cv2.imshow("mask", mask)
cv2.waitKey()

""" 使用仿射矩阵M,将b映射到a """
bWrap = cv2.warpAffine(b, M, dsize, borderMode=cv2.BORDER_TRANSPARENT, flags=cv2.WARP_INVERSE_MAP)
cv2.imshow("bWrap", bWrap)
cv2.waitKey()

""" 求b图片的仿射到图片a的颜色值,b的颜色值改为a的颜色 """
bcolor = normalColor(a, bWrap)
cv2.imshow("bcolor", bcolor)
cv2.waitKey()

""" ===========step8:换脸(mask区域用bcolor,非mask区城用a)============= """
out = a * (1.0 - mask) + bcolor * mask

# =========输出原始人脸、换脸结果===============
cv2.imshow("a", a)
cv2.imshow("b", bOriginal)
cv2.imshow("out", out/255)
cv2.waitKey()
cv2.destroyAllWindows()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PON架构(全光网络)

目前组网架构 世界上有一种最快的速度又是光,以前传统以太网络规划满足不了现在的需求。 有线网 无线网 全光网络方案 场景 全光网络分类 以太全光网络 PON(Pas-sive-Optical Network 无源光网络) 再典型的中大型高校网络中 推荐万兆入…

电脑技巧:Rufus——最佳USB启动盘制作工具指南

目录 一、功能强大,兼容性广泛 二、界面友好,操作简便 三、快速高效,高度可定制 四、安全可靠,社区活跃 在日常的电脑使用中,无论是为了安装操作系统、修复系统故障还是进行其他需要可引导媒体的任务,拥…

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件 一、简介二、.NET Reflector的主要功能包括:1. **反编译**: 反编译是将已编译的.NET程序集(如.dll或.exe文件)转换回可读的源代码。这使得开发者可以查看和学习第三方库的实现细节&…

【分立元件】电阻的额定电压和最高电压

在文章:【分立元件】贴片电阻的额定功率中我们讲到使用电阻器时,不仅要注意额定功率,还要注意电压相关的一些项目。 本文我们将对与电阻基本参数关联的额定电压和元件最高电压这两个术语及其定义(包括它们之间的关系)进行解说。 额定电压 如下所示国巨片式电阻规…

排序算法 —— 希尔排序

目录 1.希尔排序的由来 2.希尔排序的思想 3.希尔排序的实现 实现分析 实现代码 代码优化 4.希尔排序的总结 1.希尔排序的由来 希尔排序是对直接插入排序的优化。在直接插入排序算法中,如果数据是有序or接近有序的时候,直接插入排序算法的时间复杂…

跨时钟域处理(单bit)_2024年10月21日

慢时钟域同步到快时钟域:打两拍 在快时钟域clk下对慢时钟域信号进行打两拍(亚稳态概率很低) 脉冲宽度改变,但不影响同步结果 快时钟域同步到慢时钟域(两种方法) ① 脉冲展宽同步 在快时钟域clk下对快时…

LeetCode199. 二叉树的右视图(2024秋季每日一题 47)

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3: 输入: [] 输出: [] 提示:…

软件压力测试如何进行?深圳软件测试机构分享

软件压力测试是每个重要软件测试工作的一部分,是一种基本的软件质量保证行为。压力测试不是在常规条件下运行手动或自动测试,而是在计算机数量较少或系统资源匮乏的条件下运行测试。通常要进行软件压力测试的资源包括内部内存、CPU 可用性、磁盘空间和网…

Libevent源码剖析-event

1 简介 本文来重点介绍下libevent中的event事件,在类unix系统中编写网络程序时,我们经常需要处理3类事件-IO事件&signal事件&timer事件,libevent通过reactor来注册&调度&处理IO事件,并且也将signal和timer事件借助…

ESP32开发__ESP-IDF, ESP-ADF官网下载,安装及环境配置

前言 不说废话,直面“干货”。最近公司项目涉及基于 ESP32 系列芯片开发,那我们新手小白如何准备相关工作及快速入门,本篇文章旨在:介绍ESP32,指导用户搭建 ESP32 硬件开发的软件环境( ESP-IDF V5.2.1 和 …

解码专业术语——应用系统开发项目中的专业词汇解读

文章目录 引言站点设置管理具体要求包括: Footer管理基于URL的权限控制利用数据连接池优化数据库操作什么是数据连接池?优化的优势 利用反射改造后端代码,AJAX反射的作用及其在后端代码中的应用AJAX 实现前后端无刷新交互 引言 创新实践项目二…

Bash 中的 ${} 和 $() 有什么区别 ?

Bash (Bourne-Again SHell) 是一种流行的 Unix SHell,用于编写脚本。如果您使用 Bash 脚本,那么了解不同的语法元素对于提高脚本的效率和避免错误是很重要的。 在本文中,我们将解释 Bash 中 ${} 和 $() 语法之间的区别,并向您展示…

「iOS」——AFNetworking的简单使用

iOS学习 前言原生网络请求使用AFNetworking库进行网络请求具体使用 单例创建的原因单例使用 总结 前言 我们之前学习了通过OC原生内容进行网络申请,AFNetworkikng第三方库的使用,可以极大地简化网络申请的代码量。 原生网络请求 网络请求主要分为上面五…

JAVA单列集合

List系列集合:添加的元素是 有序、可重复、有索引 Set系列集合:添加的元素是 无序、不重复、无索引 Collection Collection是单列集合的接口,它的功能是全部单列集合都可以继承使用的 public boolean add(E e) 把给定的对象添加到当前集合中 public void …

【大数据学习 | Zookeeper】Zookeeper的选举机制

zookeeper的选举机制分为第一次启动和非第一次启动两种情况。 1. 选举机制 - > 第一次启动 (1)服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为 LOOKIN…

docker run 命令解析

docker run 命令解析 docker run 命令用于从给定的镜像启动一个新的容器。这个命令可以包含许多选项,下面是一些常用的选项: -d:后台运行容器,并返回容器ID;-i:以交互模式运行容器,通常与 -t …

中国信通院联合中国电促会开展电力行业企业开源典型实践案例征集

自2021年被首次写入国家“十四五”规划以来,开源技术发展凭借其平等、开放、协作、共享的优秀创作模式,正持续成为推动数字技术创新、优化软件生产模式、赋能传统行业转型升级、助力企业降本增效的重要引擎。电力是国民经济的重要基础性产业,…

Atlassian Team ‘24 Europe:推出Rovo、开发人员AI助手、新版Jira等多款AI创新,重塑团队协作

过去一周,Atlassian Team 24 Europe在巴塞罗那盛大举行!展示了Atlassian在推动团队协作和业务发展方面的最新成果与前沿见解。 本文,Atlassian联合创始人兼首席执行官Mike Cannon-Brookes为我们分享了Atlassian在AI创新和系统化工作两项关键任…

2019年计算机网络408真题解析

第一题: 解析:OSI参考模型第5层完成的功能 首先,我们需要对OSI参考模型很熟悉:从下到上依次是:物理层-数据链路层-网络层- 运输层-会话层-表示层-应用层,由此可知,题目要问的是会话层的主要功能…

python 爬虫 入门 二、数据解析(正则、bs4、xpath)

目录 一、待匹配数据获取 二、正则 三、bs4 (一)、访问属性 (二)、获取标签的值 (三)、查询方法 四、xpath 后续:登录和代理 上一节我们已经知道了如何向服务器发送请求以获得数据&#x…