目录
[1. 加载人脸检测器和特征点预测模型](#1. 加载人脸检测器和特征点预测模型)
[2. 读取两张人脸图片](#2. 读取两张人脸图片)
[3. 获取人脸的特征点](#3. 获取人脸的特征点)
[4. 使用Delaunay三角剖分](#4. 使用Delaunay三角剖分)
[5. 仿射变换三角形](#5. 仿射变换三角形)
[6. 三角形变形并复制](#6. 三角形变形并复制)
[7. 脸部轮廓掩模](#7. 脸部轮廓掩模)
[8. 无缝克隆换脸](#8. 无缝克隆换脸)
[9. 缩放图像](#9. 缩放图像)
[10. 显示换脸结果](#10. 显示换脸结果)
简介
能让你秒变吴彦祖(或者你的女神,随你挑)的神奇换脸程序!呀哈哈哈哈(博主本人已疯),熬夜秃头(夸张一下)换来的。
想象一下,早上醒来,对着镜子一照,哎呀妈呀,这不是我一直梦寐以求的那张脸吗?别急着去整容医院,先试试我的换脸神器吧!只需简单几步,你就能在朋友圈里上演一场"大变活人"的好戏,保证让你的朋友们惊掉下巴,直呼内行!
当然啦,开发这玩意儿可不是闹着玩的。我经历了无数次的代码调试、算法优化,还有那些让人头疼的bug大战。但每当看到换脸效果越来越自然,我就像看到了自己的孩子慢慢长大一样,心里那叫一个美滋滋啊!
好啦,废话不多说,接下来就让我带你走进这个换脸世界的奇妙之旅吧!记得系好安全带,因为接下来的内容可能会让你的脑洞大开,笑到肚子疼哦!
呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃
写得出来上面的话我真的是个正常人吗
重新简介
在现代的图像处理技术中,人脸识别和图像变形已经不再是遥不可及的技术。通过一些简单的算法和工具,我们可以实现很多有趣的效果,比如今天要介绍的"人脸置换"。在这篇文章中,我将向大家展示如何使用OpenCV和Dlib库实现一个有趣的换脸程序,代码不仅能识别人脸,还能通过仿射变换,将一张脸的特征平滑地融合到另一张脸上。that`s ez
思路解析
实现了两个图像之间的"换脸"功能,使用了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 None
landmarks = 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
显示处理后的换脸结果,并等待用户按下任意键后关闭窗口。
整体代码
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 None
landmarks = 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 * 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]] * (
(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()
效果展示
准备换脸的图
准备接收换脸的图
结果
我正如彭于晏一样的帅气(*^▽^*)