《OpenCV》—— dlib(换脸操作)

server/2025/3/7 2:54:16/

文章目录

    • dlib换脸介绍
    • 仿射变换
      • 在 dlib 换脸中的应用
    • 换脸操作

dlib换脸介绍

  • dlib 换脸是基于 dlib 库实现的一种人脸替换技术,以下是关于它的详细介绍:
    • 原理
      • 人脸检测:dlib 库中包含先进的人脸检测器,如基于 HOG(方向梯度直方图)特征和线性分类器的方法,能够在图像或视频帧中快速定位人脸的位置,确定人脸的边界框。
      • 关键点检测:利用 dlib 预训练的形状预测模型,如shape_predictor_68_face_landmarks.dat,可以精准定位出人脸的 68 个关键点,这些点分布在眼睛、眉毛、鼻子、嘴巴、下巴等部位,精确描述了人脸的形状和表情。
      • 图像变换与融合:通过计算源人脸和目标人脸关键点之间的变换关系,如仿射变换、Delaunay 三角剖分等,将源人脸的形状和姿态调整到与目标人脸匹配。然后,采用图像融合技术,将调整后的源人脸与目标图像进行融合,使换脸效果更加自然。
    • 实现步骤
      • 安装相关库:主要安装 dlib 库,还可能需要 opencv、numpy 等库配合。在 Python 中可以使用pip install dlib等命令安装。
      • 人脸检测与关键点提取:使用 dlib 的人脸检测器和形状预测器,加载图像或视频帧,检测其中的人脸并提取关键点。
      • 计算变换关系:根据源人脸和目标人脸的关键点,计算出两者之间的变换矩阵,确定如何将源人脸变换到目标人脸的位置和姿态。
      • 人脸替换:将源人脸按照计算出的变换关系进行变形和调整,然后将其融合到目标图像的对应位置上。
      • 后处理:对换脸后的图像进行一些后处理操作,如调整颜色、亮度、对比度等,使换脸效果更加自然逼真。
    • 优缺点
      • 优点:具有较高的人脸检测和关键点定位精度,能够处理不同姿态、表情和光照条件下的人脸;提供了丰富的函数和工具,方便开发者进行二次开发和集成;在处理速度上相对较快,能够满足一些实时性要求不高的应用场景。
      • 缺点:对图像和视频的质量要求较高,如果输入的图像分辨率低、模糊或存在遮挡,可能会影响换脸效果;对于复杂的场景和特殊的光照条件,换脸效果可能不够自然;在实时性要求较高的场景中,如实时视频通话换脸,可能需要进一步优化才能满足性能要求。

仿射变换

仿射变换(Affine Transformation)是一种在二维或三维空间中广泛应用的线性变换,它能够保持图像的 “平直性” 和 “平行性”,在计算机视觉、图形学等领域有诸多应用,比如在 dlib 换脸中就用于调整源人脸的形状和姿态以匹配目标人脸。
在这里插入图片描述

在这里插入图片描述

在 dlib 换脸中的应用

在这里插入图片描述

图片:在这里插入图片描述

仿射变换代码:

import cv2
import numpy as np# 读取图像,这里读取的图像文件名为 'c_luo.png'
# cv2.imread 函数用于从指定路径读取图像,返回一个表示图像的 NumPy 数组
img = cv2.imread('c_luo.png')# 获取图像的高度和宽度
# img.shape 返回一个包含图像维度信息的元组,元组的前两个元素分别是图像的高度和宽度
height, width = img.shape[:2]# 定义源图像的三个关键点
# 这里使用 np.float32 创建一个 3x2 的浮点型 NumPy 数组
# 三个点分别是图像的左上角 (0, 0)、左下角 (0, height - 1) 和右上角 (width - 1, 0)
mat_src = np.float32([[0, 0], [0, height - 1], [width - 1, 0]])# 定义目标图像对应的三个关键点
# 同样是一个 3x2 的浮点型 NumPy 数组
# 这里的三个点是经过变换后源图像三个关键点对应的目标位置
mat_dst = np.float32([[0, 0], [100, height - 100], [width - 100, 100]])# 计算仿射变换矩阵
# cv2.getAffineTransform 函数根据源图像和目标图像的三个对应关键点,计算仿射变换矩阵
# 该矩阵用于描述从源图像到目标图像的仿射变换关系
M = cv2.getAffineTransform(mat_src, mat_dst)# 应用仿射变换
# cv2.warpAffine 函数将仿射变换应用到输入图像 img 上
# M 是前面计算得到的仿射变换矩阵
# (width, height) 是输出图像的大小
dst = cv2.warpAffine(img, M, (width, height))# 将原始图像和经过仿射变换后的图像水平拼接在一起
# np.hstack 函数用于将多个数组水平堆叠,方便同时显示原始图像和变换后的图像
imgs = np.hstack([img, dst])# 创建一个可调整大小的窗口
# cv2.namedWindow 函数用于创建一个指定名称的窗口,cv2.WINDOW_NORMAL 表示窗口可以调整大小
cv2.namedWindow('imgs', cv2.WINDOW_NORMAL)# 在创建的窗口中显示拼接后的图像
cv2.imshow('imgs', imgs)# 等待用户按下任意按键
# cv2.waitKey(0) 表示无限期等待用户按键,按键后程序继续执行
cv2.waitKey(0)

这段代码的主要功能是对一张图像进行仿射变换,并将原始图像和变换后的图像拼接在一起显示。通过指定源图像和目标图像的三个对应关键点,计算出仿射变换矩阵,然后将该变换应用到图像上。

结果:
在这里插入图片描述

换脸操作

对下方图片进行换脸操作:
在这里插入图片描述
在这里插入图片描述
实现代码:

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):"""生成人脸掩码:param im: 输入图像:param keyPoints: 人脸关键点:return: 人脸掩码图像"""# 创建一个与输入图像高度和宽度相同的零矩阵im = np.zeros(im.shape[:2], dtype=np.float64)for p in POINTS:# 计算关键点的凸包points = cv2.convexHull(keyPoints[p])# 填充凸包区域为 1cv2.fillConvexPoly(im, points, color=1)# 将单通道掩码扩展为三通道im = np.array([im, im, im]).transpose((1, 2, 0))# 对掩码进行高斯模糊处理im = cv2.GaussianBlur(im, (25, 25), 0)return imdef getM(points1, points2):"""计算仿射变换矩阵:param points1: 源图像的关键点:param points2: 目标图像的关键点:return: 仿射变换矩阵"""points1 = points1.astype(np.float64)points2 = points2.astype(np.float64)# 计算关键点的均值c1 = np.mean(points1, axis=0)c2 = np.mean(points2, axis=0)# 减去均值进行中心化points1 -= c1points2 -= c2# 计算关键点的标准差s1 = np.std(points1)s2 = np.std(points2)# 归一化关键点points1 /= s1points2 /= s2# 进行奇异值分解U, S, Vt = np.linalg.svd(points1.T * points2)# 计算旋转矩阵R = (U * Vt).Treturn np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T))def getKeyPoints(im):"""检测图像中的人脸关键点:param im: 输入图像:return: 人脸关键点矩阵"""# 使用 dlib 的人脸检测器检测人脸rects = detector(im, 1)# 使用 dlib 的形状预测器预测关键点shape = predictor(im, rects[0])# 将关键点转换为矩阵形式s = np.matrix([[p.x, p.y] for p in shape.parts()])return sdef normalColor(a, b):"""颜色校正:param a: 源图像:param b: 目标图像:return: 颜色校正后的目标图像"""# 定义高斯核大小ksize = (71, 71)# 对源图像和目标图像进行高斯模糊aGauss = cv2.GaussianBlur(a, ksize, 0)bGauss = cv2.GaussianBlur(b, ksize, 0)# 避免除以零bGauss = np.where(bGauss == 0, 1e-8, bGauss)# 计算权重weight = aGauss / bGaussreturn b * weight# 读取源图像和目标图像
a = cv2.imread('c_luo.png')
b = cv2.imread('mu_ba_pei.png')# 初始化 dlib 的人脸检测器
detector = dlib.get_frontal_face_detector()
# 初始化 dlib 的形状预测器,需要提前下载 shape_predictor_68_face_landmarks.dat 文件
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')# 检测源图像和目标图像的人脸关键点
aKeyPoints = getKeyPoints(a)
bKeyPoints = getKeyPoints(b)# 保存目标图像的原始副本
bOriginal = b.copy()# 生成源图像的人脸掩码
aMask = getFaceMask(a, aKeyPoints)
# 显示源图像的人脸掩码
cv2.imshow('aMask', aMask)
cv2.waitKey()# 生成目标图像的人脸掩码(使用源图像的关键点)
bMask = getFaceMask(b, aKeyPoints)
# 显示目标图像的人脸掩码
cv2.imshow('bMask', bMask)
cv2.waitKey()# 计算仿射变换矩阵
M = getM(aKeyPoints[POINTStuple], bKeyPoints[POINTStuple])# 获取源图像的尺寸(宽度和高度)
dsize = a.shape[:2][::-1]# 对目标图像的掩码进行仿射变换
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()# 对目标图像进行仿射变换
bWrap = cv2.warpAffine(b, M, dsize,borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)
# 显示变换后的目标图像
cv2.imshow('bWrap', bWrap)
cv2.waitKey()# 对变换后的目标图像进行颜色校正
bcolor = normalColor(a, bWrap)
# 显示颜色校正后的目标图像
cv2.imshow('bcolor', bcolor)
cv2.waitKey()# 融合源图像和颜色校正后的目标图像
out = a * (1.0 - mask) + bcolor * mask
# 显示源图像
cv2.imshow('a', a)
# 显示目标图像的原始副本
cv2.imshow('b', bOriginal)
# 显示换脸后的结果图像
cv2.imshow('out', out / 255)
cv2.waitKey()
# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

结果:

在这里插入图片描述
可通过结果看出换脸成功。


http://www.ppmy.cn/server/173037.html

相关文章

Amadine for Mac v1.6.7 矢量图形设计软件 支持M、Intel芯片

Amadine 是Mac毒找到的一款矢量图形设计软件,非常适合平面设计专业人士以及具有创造性思维的业余爱好者。Amadine精确开发并注重用户需求,提供各种工具和功能,将最疯狂的插图创意带入生活。完美平衡的UI保证了快速简便的工作流程。 应用介绍…

golang反射

https://www.bilibili.com/video/BV1gf4y1r79E?p23 reflect包 反射的作用:在运行时检查数据的类型和值核心函数 reflect.TypeOf:获取一个值的类型reflect.ValueOf:获取它的值reflect.New:创建一个指向该类型的新指针。也就是说&…

PHP 包含(Include)机制详解

PHP 包含(Include)机制详解 在PHP编程中,include和require是两个非常基础的函数,用于在脚本中包含其他文件。它们在模块化编程中发挥着至关重要的作用,使得代码更易于维护和扩展。本文将详细介绍PHP的包含机制,包括其工作原理、使用方法以及最佳实践。 一、PHP 包含机制…

Unity Shader Graph 2D - 一个简单的电路流效果

前言 游戏中电路上电流的流过是一种特别酷的效果,本文将通过一种简单的方式在Unity Shader Graph中来实现在给出的电路图上完成电路流的效果。 电路纹理 首先创建一个Texutrue2D的MainTex变量,将其拖拽到Shader Graph视图中,然后将其连接到Sample Texture 2D的Texture输入节…

vscode远程连接ubuntu/Linux(虚拟机同样适用)

前言 在现代开发环境中,远程工作和跨平台开发变得越来越普遍。Visual Studio Code(VSCode)作为一个流行的代码编辑器,提供了强大的远程开发功能,使得开发者能够高效地连接和管理远程 Linux 服务器上的项目。通过 VSCod…

网络安全中分区分域

🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 安全区域边界 1. 边界防护 a)应保证跨越边界的访问和数据流通过边界设备提供的受控接口进行通信; 1)应核查网络拓扑图与实际的网络链路是否一致&am…

Linux三种网络方式

前言 发现运维啥都得会,这周就遇到了网络问题自己无法解决,因此痛定思痛学一下。 参考文献 你管这破玩意叫网络? 桥接模式、NAT模式、仅主机模式,原来是这样工作的 交换机 构成局域网,实现所有设备之间的通信。 …

​DeepSeek:如何通过自然语言生成HTML文件与原型图?

在当今快节奏的开发与设计环境中,快速生成HTML文件或原型图是每个开发者与设计师的迫切需求。虽然DeepSeek无法直接生成图片,但它却能够通过自然语言生成流程图、原型图以及交互式页面,甚至可以直接输出HTML代码。本文将详细介绍如何与DeepSe…