变脸大师:基于OpenCV与Dlib的人脸换脸技术实现

devtools/2024/9/23 17:31:49/

目录

简介 

重新简介

思路解析

1. 加载人脸检测器和特征点预测模型

2. 读取两张人脸图片

3. 获取人脸的特征点

4. 使用Delaunay三角剖分

5. 仿射变换三角形

6. 三角形变形并复制

7. 脸部轮廓掩模

8. 无缝克隆换脸

9. 缩放图像

10. 显示换脸结果

整体代码

效果展示

准备换脸的图

准备接收换脸的图

结果


a4697c94656a4a51a5c786ef7d6e04d1.jpeg

 

 

简介 

        能让你秒变吴彦祖(或者你的女神,随你挑)的神奇换脸程序!呀哈哈哈哈(博主本人已疯),熬夜秃头(夸张一下)换来的。

        想象一下,早上醒来,对着镜子一照,哎呀妈呀,这不是我一直梦寐以求的那张脸吗?别急着去整容医院,先试试我的换脸神器吧!只需简单几步,你就能在朋友圈里上演一场“大变活人”的好戏,保证让你的朋友们惊掉下巴,直呼内行!

        当然啦,开发这玩意儿可不是闹着玩的。我经历了无数次的代码调试、算法优化,还有那些让人头疼的bug大战。但每当看到换脸效果越来越自然,我就像看到了自己的孩子慢慢长大一样,心里那叫一个美滋滋啊!

        好啦,废话不多说,接下来就让我带你走进这个换脸世界的奇妙之旅吧!记得系好安全带,因为接下来的内容可能会让你的脑洞大开,笑到肚子疼哦!

 

呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃

写得出来上面的话我真的是个正常人吗

 

重新简介

        在现代的图像处理技术中,人脸识别和图像变形已经不再是遥不可及的技术。通过一些简单的算法和工具,我们可以实现很多有趣的效果,比如今天要介绍的“人脸置换”。在这篇文章中,我将向大家展示如何使用OpenCV和Dlib库实现一个有趣的换脸程序,代码不仅能识别人脸,还能通过仿射变换,将一张脸的特征平滑地融合到另一张脸上。that`s ez


f07b903419ed41648d573ff4e2127578.jpeg

思路解析

        实现了两个图像之间的“换脸”功能,使用了Dlib库的人脸检测器、特征点预测器,以及OpenCV的图像处理函数。具体的实现思路如下:

1. 加载人脸检测器和特征点预测模型

 
python">detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('./model/shape_predictor_68_face_landmarks.dat')

 

        首先,代码加载了Dlib的正面人脸检测器detector,以及68个特征点预测模型predictor,后者会用来提取人脸的关键特征点。

2. 读取两张人脸图片

 
python">img1 = cv2.imread("./imgs/006.jpg")
img2 = cv2.imread("./imgs/008.jpg")

 

        接着,使用OpenCV的imread函数读取两张图像。img1是要移植脸部特征的图片,img2是脸部特征的目标图片。

3. 获取人脸的特征点

 
python">def get_landmarks(image):gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)faces = detector(gray)if len(faces) == 0:return Nonelandmarks = predictor(gray, faces[0])return np.array([[p.x, p.y] for p in landmarks.parts()])

 

        这个函数将图像转换为灰度图后,通过detector检测人脸区域。如果检测到人脸,则使用predictor提取人脸的68个特征点,返回一个包含这些特征点坐标的NumPy数组。每个特征点对应面部的特定位置,如眼睛、鼻子、嘴巴等。

4. 使用Delaunay三角剖分

 
python">def get_triangles(landmarks):rect = cv2.boundingRect(np.array([landmarks]))subdiv = cv2.Subdiv2D(rect)subdiv.insert(landmarks.tolist())triangles = subdiv.getTriangleList()triangles = np.array(triangles, dtype=np.int32)...

 

        该函数通过Delaunay三角剖分算法,将人脸特征点分割为多个三角形。Delaunay三角剖分可以确保生成的三角形不会产生过小的角度,这样更利于仿射变换。

5. 仿射变换三角形

 
python">def apply_affine_transform(src, src_tri, dst_tri, size):warp_mat = cv2.getAffineTransform(np.float32(src_tri), np.float32(dst_tri))dst = cv2.warpAffine(src, warp_mat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101)return dst

 

        这个函数实现了将一个三角形从源图像变换到目标图像。通过仿射变换,将src_tri中的三角形变换为dst_tri中的相应三角形,返回变换后的图像。

6. 三角形变形并复制

 
python">def warp_triangle(img1, img2, t1, t2):r1 = cv2.boundingRect(np.array([t1]))r2 = cv2.boundingRect(np.array([t2]))...

 

  warp_triangle函数使用仿射变换将每个三角形从img1中的位置变形到img2_new_face中的对应位置。这是将源人脸区域贴到目标人脸的核心步骤。

7. 脸部轮廓掩模

 
python">hull2 = cv2.convexHull(np.array(landmarks2))
mask = np.zeros_like(img2[:, :, 0])
cv2.fillConvexPoly(mask, np.int32(hull2), 255)

 

        在仿射变换之后,通过convexHull构建目标人脸区域的凸包,即覆盖整个脸部的轮廓,并生成一个掩模mask用于后续的无缝克隆。

8. 无缝克隆换脸

 
python">seamless_img = cv2.seamlessClone(np.uint8(img2_new_face), img2, mask, center, cv2.NORMAL_CLONE)

 

  seamlessClone函数将换脸后的区域与目标图像进行无缝融合。这个函数会避免图像边界产生不自然的效果,从而使换脸看起来更加逼真。

9. 缩放图像

 
python">scale_factor = 0.9
dim = (int(seamless_img.shape[1] * scale_factor), int(seamless_img.shape[0] * scale_factor))
resized_img = cv2.resize(seamless_img, dim, interpolation=cv2.INTER_AREA)

 

        为了适应显示器大小(你们自己调调,分辨率高的图片就调小一点,分辨率低的就拉满吧),这里设置了一个缩放因子scale_factor(0.9),将最终生成的图像缩小到原来的90%。

10. 显示换脸结果

 
python">cv2.imshow("Face Swap", resized_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

        最后,使用imshow显示处理后的换脸结果,并等待用户按下任意键后关闭窗口。


ae9fd562949b4c1296bfe546ebc1ba32.jpeg

整体代码

python">import cv2
import numpy as np
import dlib# 加载人脸检测器和特征点预测模型
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('./model/shape_predictor_68_face_landmarks.dat')# 读取两张人脸图片
img1 = cv2.imread("./imgs/006.jpg")
img2 = cv2.imread("./imgs/002.jpg")# 获取人脸的特征点
def get_landmarks(image):gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)faces = detector(gray)if len(faces) == 0:return Nonelandmarks = predictor(gray, faces[0])return np.array([[p.x, p.y] for p in landmarks.parts()])# 使用Delaunay三角剖分
def get_triangles(landmarks):rect = cv2.boundingRect(np.array([landmarks]))subdiv = cv2.Subdiv2D(rect)subdiv.insert(landmarks.tolist())triangles = subdiv.getTriangleList()triangles = np.array(triangles, dtype=np.int32)indexes = []for t in triangles:pts = []for i in range(3):for j, p in enumerate(landmarks):if (t[i * 2], t[i * 2 + 1]) == (p[0], p[1]):pts.append(j)if len(pts) == 3:indexes.append(pts)return indexes# 仿射变换三角形
def apply_affine_transform(src, src_tri, dst_tri, size):warp_mat = cv2.getAffineTransform(np.float32(src_tri), np.float32(dst_tri))dst = cv2.warpAffine(src, warp_mat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101)return dst# 变形并复制
def warp_triangle(img1, img2, t1, t2):r1 = cv2.boundingRect(np.array([t1]))r2 = cv2.boundingRect(np.array([t2]))t1_rect = []t2_rect = []t2_rect_int = []for i in range(3):t1_rect.append(((t1[i][0] - r1[0]), (t1[i][1] - r1[1])))t2_rect.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))t2_rect_int.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))img1_rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]size = (r2[2], r2[3])img2_rect = apply_affine_transform(img1_rect, t1_rect, t2_rect, size)mask = np.zeros((r2[3], r2[2], 3), dtype=np.float32)cv2.fillConvexPoly(mask, np.int32(t2_rect_int), (1.0, 1.0, 1.0), 16, 0)img2_rect = img2_rect * maskimg2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] * ((1.0, 1.0, 1.0) - mask)img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] + img2_rect# 主程序
landmarks1 = get_landmarks(img1)
landmarks2 = get_landmarks(img2)if landmarks1 is None or landmarks2 is None:print("未能检测到人脸")exit()triangles = get_triangles(landmarks1)# 复制人脸区域
img2_new_face = np.zeros_like(img2)for tri in triangles:t1 = [landmarks1[tri[0]], landmarks1[tri[1]], landmarks1[tri[2]]]t2 = [landmarks2[tri[0]], landmarks2[tri[1]], landmarks2[tri[2]]]warp_triangle(img1, img2_new_face, t1, t2)# 构建脸部轮廓掩模
hull2 = cv2.convexHull(np.array(landmarks2))
mask = np.zeros_like(img2[:, :, 0])
cv2.fillConvexPoly(mask, np.int32(hull2), 255)# 将换脸后的区域融合
r = cv2.boundingRect(np.int32(hull2))  # 使用 np.int32 类型
center = ((r[0] + int(r[2] / 2), r[1] + int(r[3] / 2)))seamless_img = cv2.seamlessClone(np.uint8(img2_new_face), img2, mask, center, cv2.NORMAL_CLONE)# 缩放因子
scale_factor = 0.3# 计算新尺寸
width = int(seamless_img.shape[1] * scale_factor)
height = int(seamless_img.shape[0] * scale_factor)
dim = (width, height)# 缩放图像
resized_img = cv2.resize(seamless_img, dim, interpolation=cv2.INTER_AREA)# 显示换脸效果
cv2.imshow("Face Swap", resized_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 


效果展示

准备换脸的图

2fa0e1f86bd5466e82cedad5dfc21574.png

准备接收换脸的图

3b1d8f42a33249b2ada923300e39bd6a.png

 

结果

daae93c68bc94e1eac32815920e2dde4.png

 

我正如彭于晏一样的帅气(*^▽^*)

a6635f1defde40d1afc0cd7113ff9fd8.jpeg

 

 

 

 

 


http://www.ppmy.cn/devtools/116101.html

相关文章

王守义都说的十三香,销量直至今年依旧能打——苹果13系列的SoC芯片A15和射频收发芯片高通SDR868

浅谈人工智能技术,对社会经济变革的思考 苹果iPhone 16系列已经开放购买了,小伙伴们的钱包准备好了吗?平台君打开银行卡看了一眼,觉得iPhone 13还是很香的,也还能再战几年。 想当年iPhone 13系列发布的时候,网络流传一句话-王守义都说十三香啊。具体“香”在哪里?数据说…

MySQL:事务隔离级别

SQL 标准定义了四个隔离级别: READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。READ-COMMITTED(读取已提交) :允许读取并发事务已经提交的数据&#xf…

el-table使用el-switch选择器没效果

出现问题的代码: 0表示启用&#xff0c;1表示禁用&#xff0c;发现页面根本没有效果&#xff0c;百思不得其解&#xff0c;查阅资料&#xff0c;恍然大悟。 <el-table :data"userList" stripe border style"width: 100%" height"500"><…

今日leetCode 反转字符串

344. 反转字符串 提示 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例 1&#xff1a; 输入&#xff1a;s …

Ubuntu 中无法直接使用 `conda` 命令,设置conda的环境变量

您好&#xff0c;您在 Ubuntu 中无法直接使用 conda 命令&#xff0c;是因为 conda 的可执行文件没有添加到您的环境变量 PATH 中。您可以通过以下方法解决&#xff1a; 方法一&#xff1a;使用 conda init 初始化 运行初始化命令&#xff1a; /home/sunyuhua/miniconda3/bin/…

QT 数据加密

一.使用环境 应该是通用的,此测试版本为如图 二.使用代码 1. 运行代码 QString data = "123abcAbc.-+";qDebug() << "加密:" << QAESEncryption::encodedText(data, "填入自己秘钥");qDebug() << "解密:" <…

数据库DML语句详解与实践

目录 一、什么是DML&#xff08;Data Manipulation Language&#xff09;&#xff1f; 二、DML常用语法 1. 插入数据——INSERT 2. 更新数据——UPDATE 3. 删除数据——DELETE 4. 查询数据——SELECT 5. 条件与限制子句 三、事务控制与DML 四、DML语句的最佳实践 五、…

Spring项目如何通过MinIO实现文件分片上传、断点续传、秒传

一、前端 前端将文件分成固定大小的若干个&#xff0c;在Vue前端&#xff0c;可以使用File API和Blob对象将文件分片 // 分片上传函数 async function uploadFile(file) {const chunkSize 5 * 1024 * 1024; // 5MB每片const totalChunks Math.ceil(file.size / chunkSize);…