基于深度学习的人脸替换和人脸重演
基于深度学习的人脸替换(DeepFake)
- 人脸数据获取
人脸检测使用的是dlib库[dlib.get_frontal_face_datector()]返回值是rectangle,就是一个矩形。可以获取检测框的左上角和右下角坐标[(x1, y1) (x2, y2)]。
可以通过函数的left,right,top,bottom方法分别获取对应的x1, x2, y1, y2值
- 数据增强
分别使用了random_transform,random_warp 两个函数来做数据增强。因为是无监督任务所以其反向转播的误差由图像自己的提供,而要使得数据增强后(代码中warped_image)有对应的样本(代码中的target_image)就要进行图片转换。
warped_image = cv2.remap(image, interp_mapx, interp_mapy, cv2.INTER_LINEAR)#重映射
src_points = numpy.stack([mapx.ravel(), mapy.ravel()], axis=-1)
dst_points = numpy.mgrid[0:65:16, 0:65:16].T.reshape(-1, 2)
mat = umeyama(src_points, dst_points, True)[0:2]
target_image = cv2.warpAffine(image, mat, (64, 64))
使用opencv.rmap构造出warped_image
使用umeyama和opencv.warpAffine构造出target_image。
【调用umeyama后获取变换所需的矩阵,最后将原图和所求得矩阵放进warpAffine即可获的增强后对应的target_image。其中warpAffine功能是根据变换矩阵对源矩阵变换。】
- 模型搭建
人脸替换网络使用自动编码器,其中包含一个公用的编码器,两个人脸各自的解码器
class Autoencoder(nn.Module):
def __init__(self):
super(Autoencoder, self).__init__()
self.encoder = Encoder()# 一个编码器
self.decoder_A = Decoder()# 解码器A
self.decoder_B = Decoder()# 解码器B
def forward(self, x, select="A"):
if select == "A":
x = self.encoder(x)
out = self.decoder_A(x)
else:
x = self.encoder(x)
out = self.decoder_B(x)
return out
- 模型训练
图片来自网络,侵删
模型训练过程中,将A脸输入到编码器中,使用A的解码器还原;将B脸输入到编码器中,使用B的解码器还原,分别计算损失函数和优化。
warped_A = model(warped_A, 'A')
warped_B = model(warped_B, 'B')
loss_A = criterion(warped_A, target_A)
loss_B = criterion(warped_B, target_B)
- 模型测试
图片来自网络,侵删
模型测试过程中,若想将A脸换到B脸上,要将A的人脸使用编码器编码后,使用B的解码器还原。
converted_face = model(batch_warped_img,'B') # 选择解码器B
优化:使用泊松融合
进入神经网络的不是整张图片,而是仅仅只有脸部的小区域。因此测试时,首先截取人脸送进网络,再根据网络的输出覆盖原图部分。
泊松融合的大致思想提供一个一个mask矩阵,背景区域为0,ROI区域(region of insteresing 这里就是指脸部)的区域为255,算法通过该矩阵得知哪一步分是融合的部分,然后通过计算梯度,用梯度场作为指示,修改ROI的像素值,使得边界与原图更为贴切。
def merge(postion, face, body):
mask = 255 * np.ones(face.shape, face.dtype)
center = (postion['left']+(postion['right']-postion['left'])//2,
postion['top']+(postion['bot']-postion['top'])//2)
normal_clone = cv2.seamlessClone(face, body, mask, center, cv2.NORMAL_CLONE)
return normal_clone
Opencv已经实现了泊松融合的接口,包括三种融合方式,经测试:
NORMAL_CLONE:融合效果比较好
MIXED_CLONE:没有达到换脸的效果
MONOCHROME_TRANSFER:效果较差
基于深度学习的人脸重演(ReenactGAN)
Wayne Wu, Yunxuan Zhang, Cheng Li, Chen Qian, and Chen Change Loy. Reenactgan:Learning to reenact faces via boundary transfer. In Proceedings of the European Conference on Computer Vision (ECCV),2018:603–619.
主要模型分为三个部分
- 编码器
编码器的主体结构为两个级联的沙漏网络Hourglass,其前馈网络为一个7×7的卷积,3个残差模块,两次下采样。
def forward(self, x):
out = []
x = self.conv1(x) # 7×7的卷积
x = self.bn1(x) # 归一化
x = self.relu(x) # 激活函数
x = self.layer1(x) # 残差网络
x = self.maxpool(x) # 池化层
x = self.layer2(x) # 残差网络
x = self.layer3(x) # 残差网络
for i in range(self.num_stacks):
y = self.hg[i](x)#1 沙漏模块
y = self.res[i](y)#2 全卷积网络
y = self.fc[i](y)#3.1 上分支
score = self.score[i](y)#3.2 下分支:heatmap
out.append(score)
if i < self.num_stacks-1:#后面还有沙漏网络,3上下分支合并
fc_ = self.fc_[i](y)
score_ = self.score_[i](score)
x = x + fc_ + score_
return out # 返回一系列中间边界热图
编码器效果:
-
边界空间变形器
2.1 边界空间变形器的生成器主要包含三部分
第一部分是1组(镜像填充+conv+bn+relu)和两个下采样(conv+bn+relu),第二部分是9个残差块,第三部分是2组(deconv+bn+relu)和一个后馈网络。
https://github.com/jcjohnson/fast-neural-style/
model = [nn.ReflectionPad2d(3), # 镜像填充,对输入数据扩边
nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0,
bias=use_bias),
norm_layer(ngf),
nn.ReLU(True)]
n_downsampling = 2
for i in range(n_downsampling): # 2个下采样层
mult = 2**i
model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3,
stride=2, padding=1, bias=use_bias),
norm_layer(ngf * mult * 2),
nn.ReLU(True)]
mult = 2**n_downsampling
for i in range(n_blocks): # 9个残差模块
model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)]
for i in range(n_downsampling):
mult = 2**(n_downsampling - i)
model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2),
kernel_size=3, stride=2,
padding=1, output_padding=1,
bias=use_bias),
norm_layer(int(ngf * mult / 2)),
nn.ReLU(True)]
model += [nn.ReflectionPad2d(3)]
model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)]
model += [nn.Sigmoid()]
2.2 判别器部分使用patchGAN,参考pix2pix
sequence = [
nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw),
nn.LeakyReLU(0.2, True)
]
nf_mult = 1
nf_mult_prev = 1
for n in range(1, n_layers):
nf_mult_prev = nf_mult
nf_mult = min(2**n, 8)
sequence += [
nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult,
kernel_size=kw, stride=2, padding=padw, bias=use_bias),
norm_layer(ndf * nf_mult),
nn.LeakyReLU(0.2, True)
]
nf_mult_prev = nf_mult
nf_mult = min(2**n_layers, 8)
sequence += [
nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult,
kernel_size=kw, stride=1, padding=padw, bias=use_bias),
norm_layer(ndf * nf_mult),
nn.LeakyReLU(0.2, True)
]
sequence += [nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)]
变形器在训练过程中,经数据集中的所有人的人脸均输入,规定一个人A为五官提供脸,其他所有人Bi 为姿势指导脸,在所有人之间建立循环一致性损失。
使用GAN,分别训练5个生成模型和5个判别模型,测试时使用G_BA
变形器效果:
- 解码器
解码器生成器部分使用Unet结构,判别器使用PatchGAN,同样参考pix2pix。
Isola P, Zhu J Y, Zhou T, et al. Image-to-image translation with conditional adversarial networks[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2017: 1125-1134.
生成器有两个,分别是net_B(输入真实图片生成边界图)和net_G(输入边界图生成目标图片)
self.fake_Boundary = self.netBoundaryFeature(self.real_F1)
self.boundary_map = self.fake_Boundary[-1]
self.fake_F2 = self.netG(self.boundary_map)
作为生成器的Unet可以看做是加了跳跃连接的自编码器,在训练过程中输入图片为五官提供人脸A,使用判别器进行判别从而优化模型。训练过程模型图如下,测试时使用net_G
解码器效果:
训练过程踩的坑o(╥﹏╥)o
- 人脸识别模型的使用
在人脸替换实验中,导入手机拍摄视频之后换了多个版本的Dlib人脸识别库均不能识别出人脸,最终发现是opencv会自动将手机拍摄图片旋转,所以在图片读入后还需再次旋转,否则会识别不到人脸
success, frame = cap.read()
frame = np.rot90(frame,-1)
- 使用GPU
在人脸重演训练过程中,需要安装CUDA,但是笔记本不满足设置,本想安装虚拟机,结果发现没有GPU仍然不能安装和训练,经过各种方法的尝试,在此记录强大的google服务器calboratory!!!
calboratory类似于Python编辑的笔记本格式,在首部添加 !便可以使用Linux命令。内置CPU,GPU,TPU和15G的内存,新用户似乎可以免费使用GPU几十个小时,后续9.9美元一个月,比较稳定,但是电脑睡眠屏幕变黑后会停止训练,并且不会自动保存数据(每次分配的服务器都不一样)
calboratory可以与google云端硬盘连接,直接读取硬盘数据和运行代码,训练的模型也可以直接保存到云盘上,使用colaboratory与google drive要先配置:
import os
os.chdir("drive/My Drive/face/ReenactGAN")
将工作地址转移到自己的工作空间中。