打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
换脸系列——整脸替换
userphoto

2023.08.24 美国

关注

前言

前面介绍了仅替换五官的方法,这里介绍整张脸的方法。

国际惯例,参考博客:

[图形算法]Delaunay三角剖分算法

维诺图(Voronoi Diagram)分析与实现

Delaunay Triangulation and Voronoi Diagram using OpenCV ( C++ / Python )

Face Swap using OpenCV ( C++ / Python )

learnopencv中的换脸源码

流程

整脸替换的流程与仅替换五官的时候,稍微有点区别,步骤为:

  • 检测人脸关键点
  • 依据人脸关键点的凸包进行人脸三角剖分
  • 对两人人脸对应的三角网格进行变形对齐
  • 使用seamlessclone柏松融合算法进行贴图

先加载必要的库

import cv2import numpy as npimport matplotlib.pyplot as plt

检测人脸关键点

跟上一篇仅人脸五官替换的文章一样,代码直接贴过来了

cas = cv2.CascadeClassifier('./model/haarcascade_frontalface_alt2.xml')obj = cv2.face.createFacemarkLBF()obj.loadModel('./model/lbfmodel.yaml')# opencv检测关键点def detect_facepoint(img):    img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)    print(img_gray.shape)    print(cas.detectMultiScale(img_gray,2,3,0,(30,30)))    faces = cas.detectMultiScale(img_gray,2,3,0,(30,30))    landmarks = obj.fit(img_gray,faces)    assert landmarks[0],'no face detected'    if(len(landmarks[1])>1):        print('multi face detected,use the first')    return faces[0],np.squeeze(landmarks[1][0])#绘制人脸关键点def draw_kps(img,face_box,kps,kpssize=3):    img_show = img.copy()    cv2.rectangle(img_show,(face_box[0],face_box[1]),(face_box[0]+face_box[2],face_box[1]+face_box[3]),(0,255,0),3)    for i in range(kps.shape[0]):        cv2.circle(img_show,(kps[i,0],kps[i,1]),kpssize,(0,0,255),-1)    img_show = cv2.cvtColor(img_show,cv2.COLOR_BGR2RGB)    return img_show

三角剖分

根据人脸关键点,提取人脸三角网格,流程是先提取人脸区域的凸包,参考这里,接下来使用getTriangleList函数提取人脸网格:

def get_triangle(img,facekpts):    convex_kps = cv2.convexHull(facekpts,returnPoints=True)    kps = np.squeeze(convex_kps)    rect = (0,0,img.shape[1],img.shape[0])    subdiv = cv2.Subdiv2D(rect)    for i in range(kps.shape[0]):        subdiv.insert((kps[i,0],kps[i,1]))    triangleList = subdiv.getTriangleList()    return triangleList

写一个画图函数,可视化三角网格

def draw_triangles(img,triangles):    for t in triangles:        pt1 = (t[0],t[1])        pt2 = (t[2],t[3])        pt3 = (t[4],t[5])        cv2.line(img,pt1,pt2,(0,255,0),2,cv2.LINE_AA)        cv2.line(img,pt1,pt3,(0,255,0),2,cv2.LINE_AA)        cv2.line(img,pt2,pt3,(0,255,0),2,cv2.LINE_AA)    img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)    return img

可视化看看:

# 提取人脸关键点img1 = cv2.imread('./images/hjh.jpg')img2 = cv2.imread('./images/zly.jpg')face_box1,face_kps1 = detect_facepoint(img1)face_kps1=face_kps1.astype(int)face_box2,face_kps2 = detect_facepoint(img2)face_kps2=face_kps2.astype(int)#获取三角网格img_t1 = get_triangle(img1,face_kps1)img_t2 = get_triangle(img2,face_kps2)#可视化plt.figure(figsize=(8,8))plt.subplot(121)plt.imshow(draw_triangles(img1.copy(),img_t1))plt.axis('off')plt.subplot(122)plt.imshow(draw_triangles(img2.copy(),img_t2))plt.axis('off')

人脸网格可视化

网格变形

目的是将第二个人脸分别用网格变形到第一个人脸对应的网格区域。

所以第二个人脸的网格没用,可以按照第一个人脸的网格分割第二个人脸。这两个人脸唯一对应的地方就是他们关键点的索引顺序相同,所以找到第一个网格每个顶点对应是哪个人脸关键点,就能用索引重新分割第二个人脸。

找第一个人脸的每个网格对应的人脸关键点

#找到三角网格对应的关键点索引def get_nearest(img_t,face_kps):    triangle_idx=[]    for t in img_t:        idx1=np.argmin(np.sum(abs(face_kps-np.array([[t[0],t[1]]])),axis=1))        idx2=np.argmin(np.sum(abs(face_kps-np.array([[t[2],t[3]]])),axis=1))        idx3=np.argmin(np.sum(abs(face_kps-np.array([[t[4],t[5]]])),axis=1))        triangle_idx.append([idx1,idx2,idx3])    return triangle_idx

接下来提取第二个图像的每一块进行变形,举个例子,比如第二块网格。流程是提取三角网格的外接矩形,把它切出来,并且把举行里面对应的三个关键点的坐标重新计算一下:

# 提取第一张图的所有三角网格对应的人脸关键点索引wrap_idx = get_nearest(img_t1,face_kps1)i=2# 块索引# 三角形的三个坐标t1 = face_kps1[wrap_idx[i]]t2 = face_kps2[wrap_idx[i]]# 提取三角网格的外接矩形patch_rect1 = cv2.boundingRect(t1)patch_rect2 = cv2.boundingRect(t2)# 重置关键点坐标new_t1 = t1 - np.array([[ patch_rect1[0],patch_rect1[1] ]])new_t2 = t2 - np.array([[ patch_rect2[0],patch_rect2[1] ]])# 把第二张图像对应的图像块切分开img_patch2 = img2[patch_rect2[1]:patch_rect2[1]+patch_rect2[3],                  patch_rect2[0]:patch_rect2[0]+patch_rect2[2]]

可视化看看当前的图像切块和关键点是不是对应,因为opencv里面经常出现坐标轴弄反的问题

#验证当前关键点是否正确plt.figure(figsize=(8,8))plt.subplot(131)plt.imshow(cv2.cvtColor(img_patch2.copy(),cv2.COLOR_BGR2RGB))plt.axis('off')plt.subplot(132)plt.imshow(draw_kps(img_patch2,(0,0,patch_rect2[2],patch_rect2[3]),new_t2))plt.axis('off')plt.subplot(133)plt.imshow(draw_kps(img2.copy(),face_box2,t2,4))plt.axis('off')

核对关键点与网格切片

接下来将第二个图像的三角网格变形,使其能够贴到第一张图对应的三角区域,变形函数很简单难,就是利用opencv的计算变形矩阵函数getAffineTransform和应用变形函数warpAffine,将两个区域变形对齐:

def applyAffineTransform(src, srcTri, dstTri, size) :    # 给定两个三角形,找到第一个到第二个的仿射变换矩阵    warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) )    # 将第一个做仿射变换    dst = cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )    return dst

调用上面的函数,利用两个三角形的仿射变换矩阵,将图二的块变形

patch_affine2=applyAffineTransform(img_patch2,new_t2,new_t1,(patch_rect1[2],patch_rect1[3]))

可视化看看呗

plt.imshow(cv2.cvtColor(patch_affine2.copy(),cv2.COLOR_BGR2RGB))plt.axis('off')

其中一块的网格

我们只需要将三角网格部分贴过去,而非贴上面的这个矩形区域,所以利用掩膜去贴三角区域

mask = np.zeros((patch_rect1[3],patch_rect1[2],3),dtype=np.uint8)mask = cv2.fillConvexPoly(mask,new_t1,(1,1,1),16,0)mask_img = patch_affine2*maskimg1[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]] = \    img1[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]]*(1-mask)img1[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]] = \    img1[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]]+mask_img

可视化看看
plt.imshow(cv2.cvtColor(img1.copy(),cv2.COLOR_BGR2RGB))plt.axis('off')

在这里插入图片描述

仔细看,下嘴唇下面有一道印,那个地方就是第2块三角网格的贴图结果。

这一块的整体函数是:

def warp_triangle(dst_img,src_img,img_tri1,kps1,kps2):    # 提取第一张图的所有三角网格对应的人脸关键点索引    wrap_idx = get_nearest(img_tri1,kps1)    for i in range(len(wrap_idx)): #将第二个图的每个网格变形贴到第一张图的对应位置        t1 = kps1[wrap_idx[i]]        t2 = kps2[wrap_idx[i]]        patch_rect1 = cv2.boundingRect(t1)        patch_rect2 = cv2.boundingRect(t2)        new_t1 = t1 - np.array([[ patch_rect1[0],patch_rect1[1] ]])        new_t2 = t2 - np.array([[ patch_rect2[0],patch_rect2[1] ]])        img_patch2 = src_img[patch_rect2[1]:patch_rect2[1]+patch_rect2[3],          patch_rect2[0]:patch_rect2[0]+patch_rect2[2]]# 提取第二张图的网格图像        patch_affine2=applyAffineTransform(img_patch2,new_t2,new_t1,(patch_rect1[2],patch_rect1[3])) #变形        #将第二张图网格变形后贴到第一张图对应地方        mask = np.zeros((patch_rect1[3],patch_rect1[2],3),dtype=np.uint8)        mask = cv2.fillConvexPoly(mask,new_t1,(1,1,1),16,0)        mask_img = patch_affine2*mask        dst_img[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]] = \            dst_img[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]]*(1-mask)        dst_img[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]] = \            dst_img[patch_rect1[1]:patch_rect1[1]+patch_rect1[3],patch_rect1[0]:patch_rect1[0]+patch_rect1[2]]+mask_img    return dst_img

所有的网格都变形以后的结果图:

# 对人脸网格进行变形wrap_img = warp_triangle(img1.copy(),img2.copy(),img_t1,face_kps1,face_kps2)

可视化结果:

plt.imshow(cv2.cvtColor(wrap_img,cv2.COLOR_BGR2RGB))plt.axis('off')

未加颜色矫正的替换效果

很明显遇到了颜色不一致问题,导致贴图痕迹明显。

颜色校正

上一章节我们引用的是一种高斯校正的方法,这里我们直接用opencvseamlessClone()方法,使用泊松融合的方法矫正贴图痕迹过于明显的问题。

流程就是重新提取一次面部掩膜,利用此掩膜调用opencv函数贴图

# 对人脸进行重新贴图convex1 = cv2.convexHull(face_kps1,returnPoints=True)mask = np.zeros_like(img1)mask = cv2.fillConvexPoly(mask,convex1,(255,255,255))r=cv2.boundingRect(convex1)center = ((r[0]+int(r[2]/2)),r[1]+int(r[3]/2))result_img = cv2.seamlessClone(wrap_img,img1,mask,center,cv2.NORMAL_CLONE)

可视化结果

plt.figure(figsize=(18,18))plt.subplot(131)plt.imshow(mask.astype(np.uint8))plt.axis('off')plt.subplot(132)plt.imshow(cv2.cvtColor(wrap_img,cv2.COLOR_BGR2RGB))plt.axis('off')plt.subplot(133)plt.imshow(cv2.cvtColor(result_img,cv2.COLOR_BGR2RGB))plt.axis('off')

这个脸的方向看起来很怪,所以我们尽量让换脸的两个人的图面部朝向保持一致。用女神俞飞鸿和赵丽颖的图像做替换,效果如下:

好吧,反正我是看不出来左下角图片是赵丽颖。不要慌,算法多多,后面再想其他算法。

后记

本文是上一片只替换五官换脸方法的更进一步的替换方法,替换整张面部。后面会继续更新其他换脸算法,有兴趣可持续关注公众号。

文章所有代码都会在公众号简介的github或者csdn账号中找到。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
基于OpenCV的条形码区域分割
改变灰度图像直方图的均值和标准差
一次不太成功的项目实战:HOG特征+SVM实现交通标志的检测
opencv练手项目:文档OCR识别
LearnOpenCV学习
基于图像处理的户型图识别
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服