换脸系列——整脸替换

前言

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

国际惯例,参考博客:

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

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

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

Face Swap using OpenCV ( C++ / Python )

learnopencv中的换脸源码

流程

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

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

先加载必要的库

import cv2
import numpy as np
import 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*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]]*(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*maskdst_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_imgreturn 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')

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

在这里插入图片描述

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

后记

本文是上一片只替换五官换脸方法的更进一步的替换方法,替换整张面部。

博客代码:

链接: https://pan.baidu.com/s/11syxp6yM96GVGi09FSGUwQ

提取码: e8ae

本文已经同步到微信公众号中,公众号与本博客将持续同步更新运动捕捉、机器学习、深度学习、计算机视觉算法,敬请关注
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3D人脸重建——PRNet网络输出的理解

前言 之前有款换脸软件不是叫ZAO么,分析了一下,它的实现原理绝对是3D人脸重建,而非deepfake方法,找了一篇3D重建的论文和源码看看。这里对源码中的部分函数做了自己的理解和改写。 国际惯例,参考博客: 什…

tensorflow官方posenet模型解析

前言 tensorflow官方有个姿态估计项目,这个输入和openpose还有点不一样,这里写个单人情况下的模型输出解析方案。 国际惯例,参考博客: 博客: 使用 TensorFlow.js 在浏览器端上实现实时人体姿势检测 tensorflow中posnet的IOS代…

tensorflow2安装时候的一个dll找不到的错误

电脑环境: vs2015python3.7.6,使用anaconda安装的CUDA 10.1cuDnn 7.6.5tensorflow2.1.0 错误内容 File "C:\Users\zb116\anaconda3\lib\imp.py", line 242, in load_modulereturn load_dynamic(name, filename, file)File "C:\Users\z…

PCA、SVD、ZCA白化理论与实现

简介 在UFLDL中介绍了主成分分析这一块的知识,而且当时学机器学习的时候,老师是将PCA和SVD联系起来将的,同时UFLDL也讲到了使用PCA做数据白化whitening处理,这个词经常在论文里面看到。 国际惯例,参考博客&#xff1…

OpenCV使用Tensorflow2-Keras模型

前言 最近工作上需要在C上快速集成Tensorflow/Keras训练好的模型,做算法验证。首先想到的就是opencv里面的dnn模块了,但是它需要的格式文件比较郁闷,是pb格式的模型,但是keras通常保存的是h5文件,查阅了很多资料&…

3D人脸表情驱动——基于eos库

前言 之前出过三篇换脸的博文,遇到一个问题是表情那一块不好处理,可行方法是直接基于2D人脸关键点做网格变形,强行将表情矫正到目标人脸,还有就是使用PRNet的思想,使用目标人脸的顶点模型配合源人脸的纹理&#xff0c…

3D姿态估计——ThreeDPose项目简单易用的模型解析

前言 之前写过tensorflow官方的posenet模型解析,用起来比较简单,但是缺点是只有2D关键点,本着易用性的原则,当然要再来个简单易用的3D姿态估计。偶然看见了ThreeDPose的项目,感觉很强大的,所以把模型扒下来…

简易的素描图片转换流程与实现

前言 之前经常在网上看到用PS实现真实图片到素描图片的转换,但是流程都大同小异,身为一只程序猿,必须来个一键转化额。 国际惯例,参考博客: Photoshop基础教程:混合模式原理篇 颜色减淡的原理讲解以及应…

一个简单好用的磨皮祛斑算法理论和python实现

前言 最近看了一个磨皮算法祛斑感觉效果不错,效果图看文末就行,个人觉得效果非常不错滴。 国际惯例,参考博客: 磨皮算法的源码:YUCIHighPassSkinSmoothing How To Smooth And Soften Skin With Photoshop 图像算法…

OpenVINO——配置与道路分割案例

前言 最近看到了一个深度学习库OpenVINO,专门用于Intel硬件上部署深度学习模型,其内置了非常非常多使用的预训练模型,比如道路分割、人脸提取、3D姿态估计等等。但是配置和调用有点小恶心,这里以道路分割为例,展示如何…

图像颜色迁移《color transfer between images》

前言 前段时间,在深度学习领域不是有个比较火的方向叫风格迁移的嘛,对于我这种不喜欢深度学习那种不稳定结果的人来说,还是想看看传统图像处理领域有什么类似的技术,发现了一个颜色迁移的算法,很久前的论文了。 国际…

ColorSpace颜色空间简介

前言 如果看过之前的介绍的图像颜色迁移《color transfer between images》和颜色协调模型Color Harmoniztion就会发现,大部分图像处理算法虽然输入输出是RGB像素值,但是中间进行算法处理时很少直接更改RGB值,而是转换到其它空间&#xff0c…

Ogre共享骨骼与两种骨骼驱动方法

前言 最近业务中用到Ogre做基于3D关键点虚拟角色骨骼驱动,但是遇到两个问题: 身体、头、眼睛、衣服等mesh的骨骼是分开的,但是骨骼结构都是一样的,需要设置共享骨骼驱动的时候可以直接修改骨骼旋转量,或者将旋转量存…

仿射变换和透视变换

前言 在前面做换脸的博客中提到了使用仿射变换和透视变换将两张不同的人脸基于关键点进行对齐,保证一张人脸贴到另一张人脸时,大小完全一致;所以有必要理解一下这两个概念的区别,由于以实用性为目的,所以所有的图像算…

obj格式解析

前言 最近处理一些网格渲染的时候,需要解析Obj文件,从Free3D上随便找了个免费的人体obj模型解析测试一波 国际惯例,参考博客: 本文所使用的从Free3D下载的模型 .obj文件格式与.mtl文件格式 详解3D中的obj文件格式 3D中OBJ文…

Flask服务部署与简单内网穿透

前言 最近学习部署的时候,想到深度学习里面通常用的部署方法是flask做服务端,然后使用nginx做负载均衡,貌似也能做内网穿透。不过我不太懂负载均衡,只想利用本地电脑搭建一个简单的服务器,实现外部调用API服务的功能。…

OpenCV学习——轮廓检测

前言 轮廓检测是传统视觉中非常常用的功能,这里简单记录一下opencv中的轮廓检测算法使用方法,至于理论,后续有机会再去细品。 国际惯例: OpenCV官方的轮廓检测教程python版 OpenCV中的二值化方法教程 OpenCV轮廓层级官方文档…

RBF神经网络理论与实现

前言 最近发现有挺多人喜欢径向基函数(Radial Basis Function,RBF)神经网络,其实它就是将RBF作为神经网络层间的一种连接方式而已。这里做一个简单的描述和找了个代码解读。 之前也写过一篇,不过排版不好看,可以戳这里跳转 国际惯例&#x…

基于python和unity交互的卡通角色肢体和表情驱动(深度学习)

前言 最近看到了好多卡通角色的肢体驱动的东东,感觉是时候发挥一下读研时候学的东西了,而且虽然现在不炼丹了,但是还是得保持吃丹的技能。这个项目找了很多很多代码进行测试,最终集成了一个3D姿态估计和人脸关键点提取的代码。 …

OpenCV学习——形态学

前言 继续学习图像里面的形态学知识——结构元、腐蚀、膨胀、开运算、闭运算、击中/不击中变换。以及部分基本形态学算法,包括边界提取、空洞填充、连通分量的提取、凸壳、细化、粗化、骨架、裁剪、形态学重建。 其实就是对冈萨雷斯的《数字图像处理》中第9章节《…