前言

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

国际惯例,参考博客:

流程

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

检测人脸关键点

依据人脸关键点的凸包进行人脸三角剖分

对两人人脸对应的三角网格进行变形对齐

使用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*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')

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

颜色校正

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

流程就是重新提取一次面部掩膜,利用此掩膜调用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

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

本文同步分享在 博客“风翼冰舟”(CSDN)。

如有侵权,请联系 support@oschina.cn 删除。

本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

换脸插件 php,换脸系列——整脸替换相关推荐

  1. 换脸系列——整脸替换

    前言 前面介绍了仅替换五官的方法,这里介绍整张脸的方法. 国际惯例,参考博客: [图形算法]Delaunay三角剖分算法 维诺图(Voronoi Diagram)分析与实现 Delaunay Tria ...

  2. 如何进行AI换脸,AI换脸从 “0“ 到 “1” 详细教程 ——从配置环境开始

    后续文章读起来可能会影响观看可以前往鄙人博客查看:http://www.anyuer.club/?id=199 前言: 本人吃计算机这口饭的,说实话AI换脸很火的时候自己却没碰,挺吃亏的,最近时间比较 ...

  3. 【Android 插件化】Hook 插件化框架 ( 使用 Hook 方式替换插件 Activity 的 mResources 成员变量 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. java换脸_随意换脸 · ink-image/api Wiki · GitHub

    1.图片上传接口 参数说明: Key:墨图科技分配给客户的唯一标识符 Secret:墨图科技分配给客户的唯一标识符 imageBody:经过Base64.URL编码后的图片内容,目前只支持jpg格式的 ...

  5. jasperreports_JasperReports JSF插件用例系列

    jasperreports 这是文章系列的切入点,在该系列文章中,我将尝试介绍JasperReport JSF插件的一些用例,该工具的创建是为了轻松地将为JasperReports设计的业务报告集成到 ...

  6. JasperReports JSF插件用例系列

    这是文章系列的切入点,在该系列文章中,我将尝试介绍JasperReport JSF Plugin的一些用例, JasperReport JSF Plugin是一种工具,旨在轻松地将为JasperRep ...

  7. Python实现头像换脸(AI换脸)

    Python实现头像换脸(AI换脸) AI换脸 源程序代码如下(注释已经尽可能详细): 程序运行结果: 需要的两个照片:对应代码里的1.png和2.png 运行代码之后生成的3.png(合成效果图还不 ...

  8. 火狐浏览器安装java插件下载_插件下载安装系列Eclipse/IDEA/谷歌/火狐安装插件

    装对的插件,如虎添翼 硬肝的人生,慎重肾重 eclipse安装插件 商店安装: help - about eclipse - installation detail - 搜索安装即可 jar包安装: ...

  9. 【Unity好插件之PlayMaker系列一上半部分】如何只用一个插件和一个脚本完成制作一个简易的游戏

    学习目标: 对于PlayMaker,笔者在很早期学习中就有意识到这个插件被大伙一整乱吹,当时我觉得做个游戏肯定需要海量的脚本这PlayMaker一看就不可靠,但最近得知我贼喜欢的游戏<空洞骑士& ...

最新文章

  1. biti_rainy的面试题
  2. mysql分区、分表学习
  3. 关于ApplicationContextAware使用深入理解
  4. C语言之文件读写探究(一):fopen、fclose(文件的打开和关闭)
  5. CentOS/RHEL Linux安装EPEL第三方软件源
  6. Ubuntu下配置JDK
  7. 对极域64位禁止终止进程、键盘锁定的分析
  8. Win10 封装报错处理
  9. 程序员 写作_如何经常写作可以使您成为更好的程序员
  10. yum安装zabbix包失败问题
  11. Android WebView 调用相机、相册,压缩图片后上传
  12. 企业官方微博的视觉设计与营销策略分析
  13. 谢震业,离“苏神”还有多远?
  14. 洛谷 4238 【模板】多项式求逆
  15. Java枚举—枚举初识
  16. USB device for mac
  17. 高盛:DeFi 的互操作性可能会增加系统性风险
  18. 基于stm32的自定义HID设备开发与上位机通讯实现
  19. Trying to start MapKit location updates without prompting for location authorization. Must call -[CL
  20. 抽象类:小样儿(接口),我一眼看出你就不是人(抽象类)

热门文章

  1. Spring装配bean的三种方法:自动化装配,java代码装配,XML装配及它们的混合使用
  2. 温湿度对养殖场有多重要?看看这个案例你就懂了
  3. Hadoop和Spark学习日记4
  4. 编程日记-主要编程语言简介
  5. hive —— 数据类型
  6. 16核处理器意义何在?
  7. 全球最具收藏艺术家-曲建杰专题报道
  8. KVM虚拟化- KVM虚拟化介绍
  9. javascript 判断是上午还是下午 还是晚上
  10. WIN11分屏设置问题记录