前言

想着整理一下换脸相关的技术方法,免得以后忘记了,最近脑袋越来越不好使了。应该会包含三个系列: 仅换眼口鼻;换整个面部;3D换脸

先看看2D换脸吧,网上已经有现成的教程了,这里拿过来整理一下,做个记录。

国际惯例,参考博客:

Switching Eds: Face swapping with Python, dlib, and OpenCV

opencv人脸关键点检测模型

这里面一般涉及到脸与脸之间的映射变换矩阵,这里记录一个opencv中的函数findHomography,用于找到多个二维点之间的最优变换矩阵。

流程

整个换脸包含四步:

  • 检测人脸关键点
  • 旋转、缩放、平移第二张图片,使其与第一张图片的人脸对齐
  • 调整第二张图片的色彩平衡,使其与第一张图片对应
  • 将两张图片融合

先加载一些必要的库

import cv2
import numpy as np
import matplotlib.pyplot as plt

检测人脸关键点

使用dlib库检测人脸关键点,不过由于dlib的安装貌似必须有cmake和c++编译器,个人电脑暂时没有,所以使用opencv的人脸关键点检测算法

先去这里下载人脸框检测模型haarcascade_frontalface_alt2.xml,这里下载人脸关键点检测模型LBF.model,然后预加载模型:

cas = cv2.CascadeClassifier('./model/haarcascade_frontalface_alt2.xml')
obj = cv2.face.createFacemarkLBF()
obj.loadModel('./model/lbfmodel.yaml')

检测人脸关键点:

def detect_facepoint(img):img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)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):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]),2,(0,255,0),-1)img_show = cv2.cvtColor(img_show,cv2.COLOR_BGR2RGB)return img_show

测试一波

人脸对齐

有关键点以后,需要将两个关键点的大小形状变换地尽可能接近才能进行换脸,主要涉及到旋转、平移、缩放。假设定义如下变量:旋转R、平移T、缩放S,第一个人脸关键点是qi(i=1⋯68)q_i(i=1\cdots 68)qi​(i=1⋯68),第二个人脸关键点是pi(i=1⋯68)p_i(i=1\cdots 68)pi​(i=1⋯68),那么我们需要最小化
∑i=168∥sRpiT+T−qiT∥2\sum_{i=1}^{68} \parallel sRp_i^T+T-q_i^T \parallel ^2 i=1∑68​∥sRpiT​+T−qiT​∥2
也就是关键点ppp经过旋转、缩放、平移以后,要和关键点qqq相近

以下对齐顺序是将第二个图像与第一个图像对齐

方法1:SVD分解+仿射变换(推荐)

这里原文提到了一个Ordinary Procrustes Analysis的算法,专门用于提取这个旋转矩阵的,用的SVD分解方法:

def transformation_from_points(points1, points2):points1 = points1.copy()points2 = points2.copy()points1 = points1.astype(np.float64)points2 = points2.astype(np.float64)c1 = np.mean(points1, axis=0)c2 = np.mean(points2, axis=0)points1 -= c1points2 -= c2s1 = np.std(points1)s2 = np.std(points2)points1 /= s1points2 /= s2U, S, Vt = np.linalg.svd(np.dot(points1.T , points2))R = (np.dot(U , Vt)).T return np.vstack([np.hstack(((s2 / s1) * R,np.array([c2.T - np.dot((s2 / s1) * R , c1.T)]).T )),np.array([0., 0., 1.])])

得到变换矩阵以后,就可以使用opencvwarpAffine进行仿射变换

def wrap_im(im,M,dshape):output_im = np.zeros(dshape,dtype=im.dtype)cv2.warpAffine(im,M[:2],(dshape[1],dshape[0]),dst=output_im,borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)return output_im

两个函数合起来作为一个调用方法

def align_img1(img1,img2,landmarks1,landmarks2):trans_mat = transformation_from_points(landmarks1, landmarks2)img2_align = wrap_im(img2,trans_mat,img1.shape)return img2_align

额外加一个比较好的SVD理论证明

方法2:透视变换(不推荐)

这篇文章提到的一个方法,直接用opencvfindHomography找到变换矩阵,再用warpPerspective做透视变换。函数如下:

def align_img2(img1,img2,landmarks1,landmarks2):trans_mat,mask = cv2.findHomography(landmarks2, landmarks1, cv2.RANSAC,5.0)img2_align = cv2.warpPerspective(img2.copy(),trans_mat,(img1.shape[1],img1.shape[0]))return img2_align

对比

分别调用对比看看

img2_align = align_img1(img1,img2,face_kps1,face_kps2)
plt.figure(figsize=[8,8])
plt.subplot(131)
plt.imshow(cv2.cvtColor(img1.copy(),cv2.COLOR_BGR2RGB))
plt.subplot(132)
plt.imshow(cv2.cvtColor(img2_align.copy(),cv2.COLOR_BGR2RGB))
img2_align2 = align_img2(img1,img2,face_kps1,face_kps2)
plt.subplot(133)
plt.imshow(cv2.cvtColor(img2_align2.copy(),cv2.COLOR_BGR2RGB))

这么一看,咱们还是用方法1的仿射变换吧,咳咳。透视变换功能过于强大。

获取被替换部分的掩膜

按照参考博客和本博客的目的,只替换眼、鼻、口

LEFT_EYE_POINTS = list(range(42, 48))
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_BROW_POINTS = list(range(22, 27))
RIGHT_BROW_POINTS = list(range(17, 22))
NOSE_POINTS = list(range(27, 35))
MOUTH_POINTS = list(range(48, 61))
OVERLAY_POINTS = [LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS,NOSE_POINTS + MOUTH_POINTS,
]
FEATHER_AMOUNT = 11def draw_convex_hull(im, points, color):points = cv2.convexHull(points)cv2.fillConvexPoly(im, points, color=color)def get_face_mask(im, landmarks):im = np.zeros(im.shape[:2], dtype=np.float64)#双眼的外接多边形、鼻和嘴的多边形,作为掩膜for group in OVERLAY_POINTS:draw_convex_hull(im,landmarks[group],color=1)im = np.array([im, im, im]).transpose((1, 2, 0))im = (cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0) > 0) * 1.0im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)return im

提取完mask,当然需要将mask也按照仿射变换的方法进行校正

mask = get_face_mask(img2, face_kps2)
warped_mask = align_img1(img1, mask,face_kps1,face_kps2)
combined_mask = np.max([get_face_mask(img1, face_kps1), warped_mask],axis=0)

可视化

plt.imshow(np.multiply(img2_align,combined_mask).astype('uint8')[...,::-1])

颜色校正

直接贴图,肯定会由于颖宝和老干部的肤色不搭,会有非常明显的贴图边缘痕迹。

output_im = img1 * (1.0 - combined_mask) + img2_align * combined_mask

很明显,mask的边缘部分,有非常明显的颜色差

所以需要矫正一波颜色,在后续博客中,会提供其它颜色校正方法。

COLOUR_CORRECT_BLUR_FRAC = 0.6
LEFT_EYE_POINTS = list(range(42, 48))
RIGHT_EYE_POINTS = list(range(36, 42))def correct_colours(im1, im2, landmarks1):blur_amount = COLOUR_CORRECT_BLUR_FRAC * np.linalg.norm(np.mean(landmarks1[LEFT_EYE_POINTS], axis=0) -np.mean(landmarks1[RIGHT_EYE_POINTS], axis=0))blur_amount = int(blur_amount)if blur_amount % 2 == 0:blur_amount += 1im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)# Avoid divide-by-zero errors.im2_blur += (128 * (im2_blur <= 1.0)).astype(im2_blur.dtype)return (im2.astype(np.float64) * im1_blur.astype(np.float64) /im2_blur.astype(np.float64))

矫正完毕以后:

img2_correct = correct_colours(img1,img2_align,face_kps1)
plt.imshow(cv2.cvtColor(img2_correct.copy().astype('uint8'),cv2.COLOR_BGR2RGB))
plt.axis('off')

再来瞅瞅结果:

output_im = img1 * (1.0 - combined_mask) + img2_correct * combined_mask

好了,边缘基本没有贴图痕迹了,但是整体效果。。。。。。。。

打扰了~~

后面用其它算法优化

后记

简单的记录一下只替换五官的情况下,整个换脸算法的流程。

博客代码:

链接:https://pan.baidu.com/s/1a-TNuIVslJiOTWgKse58Ag
提取码:kabv

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

换脸系列——眼鼻口替换相关推荐

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

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

  2. 换脸插件 php,换脸系列——整脸替换

    前言 前面介绍了仅替换五官的方法,这里介绍整张脸的方法. 国际惯例,参考博客: 流程 整脸替换的流程与仅替换五官的时候,稍微有点区别,步骤为: 检测人脸关键点 依据人脸关键点的凸包进行人脸三角剖分 对 ...

  3. qj71c24n通讯实例_Q系列串行口通信模块用户参考手册QJ71C24N(基础篇).pdf

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp电子工程/通信技术&nbsp>&nbsp数据通信与网络 Q系列串行口通信模块用户参考手册QJ ...

  4. Proteus8.9 VSM Studio GCC编译器仿真STM32F407ZGT6系列011_lcd1602_并口

    一,打开文件(可以随文下载放置在文档中打开).(如下图1所示) 图1 二,调整原理图大小,适合可视,另存工程文件.(如下图2,3,4所示) 图2 图3 图4 三,点击Source Code标签.(如下 ...

  5. 欧姆龙cp系列plc自由口通讯台达VFD-M变频器示例

    欧姆龙cp系列plc自由口通讯台达VFD-M变频器示例 所需硬件:欧姆龙cp系列plc及通讯扩展板,台达VFD-M变频器,威纶通6070触摸屏. 实现功能:通过TXD,RXD指令,ASCII方式,控制 ...

  6. 三菱FX系列PLC编程口通信协议总览

    该协议实际上适用于PLC编程端口以及FX-232AW模块的通信 通讯格式       命令(CMD)   命令码    目标设备  DEVICE READ  CMD    "0"  ...

  7. 上海美莱出席中国眼鼻年轻化整形高峰论坛,分享眼整形学术成果

    近期,中国眼鼻年轻化整形高峰论坛在广西省桂林市成功召开.据了解,此次论坛是西南地区大规模.全领域.高标准的医学美容专业学术交流会,会议集中于"眼整形"."鼻整形" ...

  8. 我试了下《复仇者联盟》AI换脸系列,当了英雄的我现在很慌...

    大数据文摘出品 作者:蒋宝尚 <复仇者联盟4:终局之战>上映已经有一段时间了,内地累计票房便已突破20亿.电影精彩之处离不开钢铁侠.雷神.美国队长等各位超级英雄的实力支撑. 每个英雄各具特 ...

  9. SAP ABAP实用技巧介绍系列之 使用XSLT替换xml中指定node的value

    Created by Jerry Wang, last modified on Jun 30, 2014 用于测试的xml: <catalog> <cd> <title& ...

最新文章

  1. 关于加载Fashion MNIST数据集时可能会出现的问题
  2. 清除右键菜单CMD入口
  3. matlab常用代码总结
  4. 《CCNA ICND2(200-101)认证考试指南(第4版)》——第1章定义生成树协议
  5. asterisk1.8 账号信息mysql存储(动态)
  6. appium+python自动化98-非select弹出选择框定位解决
  7. 纯php代码进行删除数据操作
  8. cvMatchTemplate() 模板匹配
  9. ACCESS数据库注入解析
  10. 【摘抄】SLAM中的位姿
  11. ps,pr ,ae,dw等软件简短解析(含安装包)
  12. plsql的注册激活
  13. 从李兴平到翔子 “草根站长”的简单化生存规则!
  14. All The Elements Pronounced in Order (American English)
  15. 亿愿数据库文章中医中药知识宝库阅读器
  16. LayUI可选择可输入下拉框
  17. 自动控制原理复习——线性系统的根轨迹法
  18. postgres install + postgis install(二)
  19. D语言与C++做映射时需要注意的事情
  20. 梦幻西游109散人最多的服务器,梦幻西游:2021年,109散人最多的服务器TOP5,2008只能排第四...

热门文章

  1. 深度学习之早停策略EarlyStopping以及保存测试集准确率最高的模型ModelCheckpoint
  2. E - 嗯? 51Nod - 1432(二分)
  3. 04.Python基础_列表_元组_字典_集合
  4. java expextion_Java(20~24)
  5. 打印图形(2)(直角三角形)(C+Java)
  6. java alder32_Java里面计算Adler32校验
  7. 深度学习(19)神经网络与全连接层二: 测试(张量)实战
  8. Spring boot系列--redis使用之1
  9. asp.net 中ashx、axd的区别
  10. python求最大连续子数组