《计算机视觉》—— 基于 dlib 库的方法将两张人脸图片进行换脸

news2024/11/26 11:03:51

声明:此篇文章所用的明星照片只为用于演示代码的效果,无诋毁她人肖像之意

一、案例实现的思想

  • 此案例的核心是基于人脸68个关键点检测模型来实现的,人脸68个关键带点检测后的效果如下:
    在这里插入图片描述

  • 通过对上图中红色区域的转换,来实现换脸的操作

  • 参照以下链接文章,可以帮助对此片案例内容的理解

    • 基于 dlib 库的人脸68个关键点定位
      • https://blog.csdn.net/weixin_73504499/article/details/142990867?spm=1001.2014.3001.5501
    • 基于 dlib 库的人脸关键部位的轮廓轮廓检测
      • https://blog.csdn.net/weixin_73504499/article/details/143027371?spm=1001.2014.3001.5501
  • 实现步骤与结果如下图:
    在这里插入图片描述

二、编辑代码

步骤解析:

  • 第一步:对人脸的关键部位在68个关键点中的点集确定下来,并将红框内的各部位的点击存储在一个列表中

    """ 根据人脸68个关键点检测模型,将脸部各部位对应的点集以列表形式存储 """
    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)
    
  • 第二步:读取两张人脸图片,并获取两张人脸图片的68个关键点

    def getKeyPoints(im):  # 获取关键点
    	rects = detector(im, 1)  # 获取人脸方框位置
    	shape = predictor(im, rects[0])  # 获取关键点
    	s = np.matrix([[p.x, p.y] for p in shape.parts()])  # 将关键点转换为坐标(x,y)的形式
    	return s
    
    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关键点
    
  • 第三步:分别获取两张人脸红色区域(上图中红色)的掩膜

    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
    
    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

    • 图像的几何变换主要包括:平移、旋转、缩放、剪切、仿射、透视等。

    • 图像的几何变换主要分为:刚性变换、相似变换、仿射变换和透视变换(投影变换)

    • 刚性变换:平移+旋转

    • 相似变换:缩放+剪切

    • 仿射变换:从一个二维坐标系变换到另一个二维坐标系,属于线性变换。通过已知3对坐标点可以求得变换矩阵

    • 透视变换:从一个二维坐标系变换到一个三维坐标系,属于非线性变换。通过已知4对坐标点可以求得变换矩阵。
      在这里插入图片描述

    • 下图中内容是对仿射变换的简单概述
      在这里插入图片描述

    """ 求出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))
    
    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的颜色

    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
    
    bcolor = normalColor(a, bWrap)
    cv2.imshow("bcolor", bcolor)
    cv2.waitKey()
    
    • 结果如下:
      在这里插入图片描述
  • 第九步:换脸(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()
    
    • 最终结果如下:
      在这里插入图片描述
  • 完整代码如下:

    import cv2
    import dlib
    import numpy as np
    
    """ 根据人脸68个关键点检测模型,将脸部各部位对应的点集以列表形式存储 """
    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()])  # 将关键点转换为坐标(x,y)的形式
        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
    
    
    """ 读取两张人脸图片,并获取两张人脸图片的68个关键点 """
    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()
    
    """ ===========换脸(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()
    
    

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

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

相关文章

项目管理必备:如何快速创建项目交付时间表

“不做准备,就准备失败”。项目的成功很大程度上取决于前期规划的充分性和质量。 项目交付时间表是项目管理中一个重要的工具,通过将项目分解为可管理的区块来组织工作。该方法使管理者可以创建分步的工作路径,并展示项目的宏观视图&#xff…

诺贝尔颁给AI啦,没想到高性能计算CHPC也发力了!

诺贝尔化学奖垂青 AlphaFold 3 2020 年 12 月,AlphaFold 2 发布。相较于之前实验成本高、耗时长、蛋白质结构解析困难的传统方法,AlphaFold 2 可以通过深度学习技术,仅需几小时就能准确地预测蛋白质的三维结构,极大地加速了生物…

基于Multisim的水位测量电路设计与仿真

1.利用LED指示灯显示水位(最低水位、1/4、1/2、3/4、最高水位)。 2.达到最高水位时,自动报警。

uiautomatorviewer安卓9以上正常使用及问题处理

一、安卓9以上使用uiautomatorviewer问题现象 打开Unexpected error while obtaining UI hierarchy 问题详情 Unexpected error while obtaining UI hierarchy java.lang.reflect.InvocationTargetException 二、问题处理 需要的是替换对应D:\software\android-sdk-windows…

AI自动生成PPT哪个软件好?智能生成PPT不再熬夜做课件

大概这世上,都是职场牛马对“PPT”这三个字母的头痛反应最大吧! 是的,就连各个年级段的老师也是很头痛——愁着怎样能在排版整齐的情况下,将必考知识点都呈现在PPT每一张幻灯片页面里...... 近期打听到用人工智能生成ppt课件&am…

域渗透相关内网 或 域AD环境搭建 域环境收集方式方法命令 查看 检测域控主机或者域内主机是否安装杀软方式

目录 域基础知识点知道这些就可以了 域 域的优点以及作用 域控 活动目录 活动目录主要功能 域环境搭建操作步骤: 域环境信息收集 信息收集工具 服务端客户端启动方式 CS操作部分 进行信息收集 信息收集操作 域基础知识点知道这些就可以了 域 域模型就是针对大…

MySQL——数据库

什么是数据库 数据库(DB , DataBase)概念:数据仓库,软件,安装在操作系统(window,linux,max,...)之上学习数据库最重要的就是学习SQL语句存储500万以下的数据…

HTML5_标签_各类表格的实现

目录 1. 表格标签 1.1 表格的主要作用 1.2 表格的基本语法 1.3 表头单元格标签 1.4 表格属性 案例分析 先制作表格的结构. 后书写表格属性. 代码示例: 1.5 表格结构标签 1.6 合并单元格 合并单元格方式: 目标单元格:(写合并代码) 合并单元…

蓝桥杯——搜索

搜索 DFS基础回溯 回溯法简介: 回溯法一般使用DFS(深度优先搜索)实现,DFS是一种遍历或搜索图、树或图像等数据结构的算法,当然这个图、树未必要存储下来(隐式处理就是回溯法),常见…

shell脚本语法详解

目录 shell语法基础 指定shell解析器 注释 运行 变量 定义变量 引用变量 清除变量值 从键盘获取值 输入单值 添加输入提示语 读取多值 ​编辑 定义只读变量 环境变量 设置环境变量与查看环境变量 特殊变量 三种引号的作用与区别 小括号与大括号 参数传递 位…

【HuggingFace 如何上传数据集 (2) 】国内网络-稳定上传图片、文本等各种格式的数据

【HuggingFace 下载】diffusers 中的特定模型下载,access token 使用方法总结【HuggingFace 下载中断】Git LFS 如何下载指定文件、单个文件夹?【HuggingFace 如何上传数据集】快速上传图片、文本等各种格式的数据 上文的方法因为是 https 协议&#xf…

CORS预检请求配置流程图 srpingboot和uniapp

首先要会判断预检请求 还是简单请求 简单请求 预检请求 #mermaid-svg-1R9nYRa7P9Pll4AK {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1R9nYRa7P9Pll4AK .error-icon{fill:#552222;}#mermaid-svg-1R9nYRa7P9Pll4…

JAVA Maven 的安装与配置

一、下载地址 官方网站:Maven – Download Apache Maven 我这里是3.8.6版本 二、安装步骤 maven安装之前要先安装jdk,请确保你的系统已经安装了jdk环境。 1.将下载好的 Maven 进行解压 apache-maven-3.6.8-bin.zip 2.配置本地仓库:修改 conf/settin…

HDU RSA

翻译成中文后&#xff1a; 思路&#xff1a;由题易得&#xff0c;d * e y * f ( n ) 1 ,且gcd ( e , f ( n ) ) 1,所以用扩展欧几里得求出 d &#xff0c;但要保证 d 是非负的&#xff0c;最有用快速幂求出每个字符即可。 #include<bits/stdc.h> using namespace std;…

【Web前端概述】

HTML 是用来描述网页的一种语言&#xff0c;全称是 Hyper-Text Markup Language&#xff0c;即超文本标记语言。我们浏览网页时看到的文字、按钮、图片、视频等元素&#xff0c;它们都是通过 HTML 书写并通过浏览器来呈现的。 一、HTML简史 1991年10月&#xff1a;一个非正式…

第 6 章 Kafka-Eagle 监控 和 Kafka-Kraft 模式

Kafka-Eagle 框架可以监控 Kafka 集群的整体运行情况&#xff0c;在生产环境中经常使用。 6.1 MySQL 环境准备 Kafka-Eagle 的安装依赖于 MySQL &#xff0c; MySQL 主要用来存储可视化展示的数据。如果集 群中之前安装过 MySQL 可以跨过该步。 6.2 Kafka 环境准备 …

ELK日志分析系统部署

ELK日志分析系统 ELK指的是ElasticsearchLogstashKibana这种架构的缩写。 ELK是一种日志分析平台&#xff0c;在很早之前我们经常使用Shell三剑客&#xff08;一般泛指grep、sed、awk&#xff09;来进行日志分析&#xff0c;这种方式虽然也可以应对多种场景&#xff0c;但是当…

多线程初阶(七):单例模式指令重排序

目录 1. 单例模式 1.1 饿汉模式 1.2 懒汉模式 2. 懒汉模式下的问题 2.1 线程安全问题 2.2 如何解决 --- 加锁 2.3 加锁引入的新问题 --- 性能问题 2.4 指令重排序问题 2.4.1 指令重排序 2.4.2 指令重排序引发的问题 1. 单例模式 单例模式, 是设计模式中最典型的一种模…

CTFHUB技能树之SQL——MySQL结构

开启靶场&#xff0c;打开链接&#xff1a; 先判断一下是哪种类型的SQL注入&#xff1a; 1 and 11# 正常回显 1 and 12# 回显错误&#xff0c;说明是整数型注入 判断一下字段数&#xff1a; 1 order by 2# 正常回显 1 order by 3# 回显错误&#xff0c;说明字段数是2列 知道…

未来医疗:大语言模型如何改变临床实践、研究和教育|文献精析·24-10-23

小罗碎碎念 这篇文章探讨了大型语言模型在医学领域的潜在应用和挑战&#xff0c;并讨论了它们在临床实践、医学研究和医学教育中的未来发展。 姓名单位名称&#xff08;中文&#xff09;Jan Clusmann德国德累斯顿工业大学埃尔朗根弗雷斯尼乌斯中心数字化健康研究所Jakob Nikola…