自学内容网 自学内容网

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

目录

简介 

重新简介

思路解析

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

2. 读取两张人脸图片

3. 获取人脸的特征点

4. 使用Delaunay三角剖分

5. 仿射变换三角形

6. 三角形变形并复制

7. 脸部轮廓掩模

8. 无缝克隆换脸

9. 缩放图像

10. 显示换脸结果

整体代码

效果展示

准备换脸的图

准备接收换脸的图

结果


a4697c94656a4a51a5c786ef7d6e04d1.jpeg

 

 

简介 

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

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

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

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

 

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

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

 

重新简介

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


f07b903419ed41648d573ff4e2127578.jpeg

思路解析

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

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

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

 

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

2. 读取两张人脸图片

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

 

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

3. 获取人脸的特征点

 
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三角剖分

 
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. 仿射变换三角形

 
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. 三角形变形并复制

 
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. 脸部轮廓掩模

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

 

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

8. 无缝克隆换脸

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

 

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

9. 缩放图像

 
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. 显示换脸结果

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

 

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


ae9fd562949b4c1296bfe546ebc1ba32.jpeg

整体代码

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

 


效果展示

准备换脸的图

2fa0e1f86bd5466e82cedad5dfc21574.png

准备接收换脸的图

3b1d8f42a33249b2ada923300e39bd6a.png

 

结果

daae93c68bc94e1eac32815920e2dde4.png

 

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

a6635f1defde40d1afc0cd7113ff9fd8.jpeg

 

 

 

 

 


原文地址:https://blog.csdn.net/DDDDWJDDDD/article/details/142369782

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!