换脸系列——眼鼻口替换
前言
想着整理一下换脸相关的技术方法,免得以后忘记了,最近脑袋越来越不好使了。应该会包含三个系列: 仅换眼口鼻;换整个面部;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.])])
得到变换矩阵以后,就可以使用opencv
的warpAffine
进行仿射变换
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:透视变换(不推荐)
这篇文章提到的一个方法,直接用opencv
的findHomography
找到变换矩阵,再用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
本文已经同步到微信公众号中,公众号与本博客将持续同步更新运动捕捉、机器学习、深度学习、计算机视觉算法,敬请关注
换脸系列——眼鼻口替换相关推荐
- 换脸系列——整脸替换
前言 前面介绍了仅替换五官的方法,这里介绍整张脸的方法. 国际惯例,参考博客: [图形算法]Delaunay三角剖分算法 维诺图(Voronoi Diagram)分析与实现 Delaunay Tria ...
- 换脸插件 php,换脸系列——整脸替换
前言 前面介绍了仅替换五官的方法,这里介绍整张脸的方法. 国际惯例,参考博客: 流程 整脸替换的流程与仅替换五官的时候,稍微有点区别,步骤为: 检测人脸关键点 依据人脸关键点的凸包进行人脸三角剖分 对 ...
- qj71c24n通讯实例_Q系列串行口通信模块用户参考手册QJ71C24N(基础篇).pdf
您所在位置:网站首页 > 海量文档  > 电子工程/通信技术 > 数据通信与网络 Q系列串行口通信模块用户参考手册QJ ...
- Proteus8.9 VSM Studio GCC编译器仿真STM32F407ZGT6系列011_lcd1602_并口
一,打开文件(可以随文下载放置在文档中打开).(如下图1所示) 图1 二,调整原理图大小,适合可视,另存工程文件.(如下图2,3,4所示) 图2 图3 图4 三,点击Source Code标签.(如下 ...
- 欧姆龙cp系列plc自由口通讯台达VFD-M变频器示例
欧姆龙cp系列plc自由口通讯台达VFD-M变频器示例 所需硬件:欧姆龙cp系列plc及通讯扩展板,台达VFD-M变频器,威纶通6070触摸屏. 实现功能:通过TXD,RXD指令,ASCII方式,控制 ...
- 三菱FX系列PLC编程口通信协议总览
该协议实际上适用于PLC编程端口以及FX-232AW模块的通信 通讯格式 命令(CMD) 命令码 目标设备 DEVICE READ CMD "0" ...
- 上海美莱出席中国眼鼻年轻化整形高峰论坛,分享眼整形学术成果
近期,中国眼鼻年轻化整形高峰论坛在广西省桂林市成功召开.据了解,此次论坛是西南地区大规模.全领域.高标准的医学美容专业学术交流会,会议集中于"眼整形"."鼻整形" ...
- 我试了下《复仇者联盟》AI换脸系列,当了英雄的我现在很慌...
大数据文摘出品 作者:蒋宝尚 <复仇者联盟4:终局之战>上映已经有一段时间了,内地累计票房便已突破20亿.电影精彩之处离不开钢铁侠.雷神.美国队长等各位超级英雄的实力支撑. 每个英雄各具特 ...
- SAP ABAP实用技巧介绍系列之 使用XSLT替换xml中指定node的value
Created by Jerry Wang, last modified on Jun 30, 2014 用于测试的xml: <catalog> <cd> <title& ...
最新文章
- 关于加载Fashion MNIST数据集时可能会出现的问题
- 清除右键菜单CMD入口
- matlab常用代码总结
- 《CCNA ICND2(200-101)认证考试指南(第4版)》——第1章定义生成树协议
- asterisk1.8 账号信息mysql存储(动态)
- appium+python自动化98-非select弹出选择框定位解决
- 纯php代码进行删除数据操作
- cvMatchTemplate() 模板匹配
- ACCESS数据库注入解析
- 【摘抄】SLAM中的位姿
- ps,pr ,ae,dw等软件简短解析(含安装包)
- plsql的注册激活
- 从李兴平到翔子 “草根站长”的简单化生存规则!
- All The Elements Pronounced in Order (American English)
- 亿愿数据库文章中医中药知识宝库阅读器
- LayUI可选择可输入下拉框
- 自动控制原理复习——线性系统的根轨迹法
- postgres install + postgis install(二)
- D语言与C++做映射时需要注意的事情
- 梦幻西游109散人最多的服务器,梦幻西游:2021年,109散人最多的服务器TOP5,2008只能排第四...
热门文章
- 深度学习之早停策略EarlyStopping以及保存测试集准确率最高的模型ModelCheckpoint
- E - 嗯? 51Nod - 1432(二分)
- 04.Python基础_列表_元组_字典_集合
- java expextion_Java(20~24)
- 打印图形(2)(直角三角形)(C+Java)
- java alder32_Java里面计算Adler32校验
- 深度学习(19)神经网络与全连接层二: 测试(张量)实战
- Spring boot系列--redis使用之1
- asp.net 中ashx、axd的区别
- python求最大连续子数组