论文:BeautyGAN: Instance-level Facial Makeup Transfer with Deep Generative Adversarial Network
官网:http://liusi-group.com/projects/BeautyGAN
Github:https://github.com/Honlan/BeautyGAN
文章图片
论文提出了一种基于GAN的方式的化妆迁移的方法BeautyGAN,效果优于传统的Cycle-GAN。
主要贡献:
- 基于GAN实现了自动的换装,实验表明,BeautyGAN不仅高效,而且生成质量优于目前最好的方法。
- 通过在局部区域应用像素级别的直方图loss,取得了实例级别的风格变换。该实例级别的变换策略也可以应用于风格迁移,属性变换等其他任务。
- 贡献了3834张高清图片的换妆数据集Makeup Transfer(MT)
网络结构:
文章图片
生成器G为两个输入和两个输出的网络结构,中间的模块共享权重。生成器G中还使用了IN(instance normalization)模块。生成器的输入图片大小为256*256,输出图片大小也是256*256。
判别器D为70*70的PatchGANs。
【美颜换妆之BeautyGAN】
损失函数:
假设未化妆图片为A,A ? RH×W ×3
化妆图片为B,B ? RH×W ×3
整个换妆问题可以定义为,
文章图片
Isrc表示需要换妆的人脸图片,表示ID图,
Iref表示化好妆的参考图,
IBsrc表示将原图Isrc进行了图B的操作,即化妆操作,也就是我们真正需要的输出结果。
IAref表示将参考图Iref进行了图片A的操作,即去妆操作
BeautyGAN整体loss由4部分loss组成,对抗loss(adversarial loss),循环GAN loss( cycle consistency loss),感知loss( perceptual loss) ,换妆约束loss(makeup constrain loss) 。
文章图片
其中,α = 1, β = 10,γ = 0.005
对抗loss(adversarial loss):
文章图片
由于生成模型有2个输出组成,所以判别器也是有2个组成,DA和DB。
由于训练过程中,该loss是基于log函数的loss,很容易出现负值,因此,使用MSE loss对DA和DB分别进行优化。
文章图片
为了使得判别器的训练更加平稳,这里还引入了普归一化spectral normalization,
文章图片
σ(W ) 表示w的归一化操作。h表示每一层的输入。
循环GAN loss( cycle consistency loss):
文章图片
整个的训练过程,先通过输入的图片(Isrc,Iref)生成妆容风格互换后的图片G(Isrc,Iref)。然后将互换妆容风格的图片再输入生成器中,就会将妆容风格又互换回来G(G(Isrc,Iref)),也就是说经过2次互换,又回到了原始的输入图片。
Cycle loss的目的就是保证2次换妆后的输出和原始输入一样。
文章图片
其中,dist表示L1或者L2。
感知loss( perceptual loss):
文章图片
Flijk 表示模型的第l层,位置
换妆约束loss(makeup constrain loss):
首先使用PSPNet 这样的分割模型,对人脸区域进行分割,即Face parsing 。可以分别提取出嘴巴,眼睛,人脸这3个部位。然后分别对这3个部位进行直方图Histogram loss的计算。
文章图片
其中,λl = 1, λs =1, λf = 0.1
? 表示elemetwise的乘法操作,
item表示{lips, shadow, f ace}
为什么要进行Face parsing操作?
- 背景和头发区域的像素和换妆是没有关系的。
- 人脸换妆不仅是一个全局的风格变换,更是人脸不同区域的独立风格的变换。
First, pixels in background and hairs have no relationship with makeup. If we do not separate them apart, they为什么要使用Histogram loss,而不是MSE loss?
will disturb the correct color distribution. Second, facial makeup is beyond a global style but a collection of several independent styles in different cosmetics regions.
If we directly adopt MSE loss on pixel-level histograms of two images, the gradient will be zero, owning to the indicator function, thus makes no contribution to optimization process. Therefore, we adopt histogram matching strategy that generates a ground truth remapping image in advance.
Makeup Transfer(MT) 数据集:
文章图片
该数据集一共包含3834张图片,其中1115 张没有化妆,2719 张有化妆。图片大小为361*361。从里面随机选出100张未化妆的,250张化妆的作为测试集。并且附带分割的mask图片。
文章图片
工程化:
模型ckpt转化为pb
import tensorflow as tf
from tensorflow.python.framework import graph_utildef freeze_graph(input_checkpoint,output_graph):
'''
:param input_checkpoint:
:param output_graph: PB模型保存路径
:return:
'''
# 指定输出的节点名称,该节点名称必须是原模型中存在的节点
# 直接用最后输出的节点,可以在tensorboard中查找到,tensorboard只能在linux中使用input= ["X","Y"]
output_node_names = "generator/xs"
saver = tf.train.import_meta_graph(input_checkpoint + '.meta', clear_devices=True)
graph = tf.get_default_graph() # 获得默认的图
input_graph_def = graph.as_graph_def()# 返回一个序列化的图代表当前的图
#print(input_graph_def)
with tf.Session() as sess:
saver.restore(sess, input_checkpoint) #恢复图并得到数据
output_graph_def = graph_util.convert_variables_to_constants(# 模型持久化,将变量值固定
sess=sess,
input_graph_def=input_graph_def,# 等于:sess.graph_def
output_node_names=output_node_names.split(","))# 如果有多个输出节点,以逗号隔开
with tf.gfile.GFile(output_graph, "wb") as f: #保存模型
f.write(output_graph_def.SerializeToString()) #序列化输出
print("%d ops in the final graph." % len(output_graph_def.node)) #得到当前图有几个操作节点freeze_graph('./model/model',"model.pb")
转化完后,模型从356M降为28.4M。
有了这个基本的网络结构,就可以进行训练代码复现,本人复现后的代码,还有一些小问题,待完善后跟新,
Anyway,the devil in the details.
推理端调用pb模型:
import os,sys
import cv2
import numpy as np
import tensorflow as tfos.environ["CUDA_VISIBLE_DEVICES"] = "0"class BeautyGAN():
def __init__(self):
cur_dir = os.path.dirname(os.path.abspath(__file__))model_file = "./model.pb"
self.graph = self.load_graph(model_file)
with self.graph.as_default():
self.inputsX = self.graph.get_tensor_by_name("import/X:0")
self.inputsY = self.graph.get_tensor_by_name("import/Y:0")
self.result = self.graph.get_tensor_by_name("import/generator/xs:0")
self.session = tf.Session(graph=self.graph)
def load_graph(self, model_file):
graph = tf.Graph()
graph_def = tf.GraphDef()with open(model_file, "rb") as f:
graph_def.ParseFromString(f.read())
with graph.as_default():
tf.import_graph_def(graph_def)return graph
def interface(self,inputX,inputY):
inputX = cv2.resize(inputX[:,:,::-1], (256, 256),interpolation=cv2.INTER_CUBIC)
inputY = cv2.resize(inputY[:,:,::-1], (256, 256),interpolation=cv2.INTER_CUBIC)array_X = (np.array(inputX)/(255*1.0)-0.5)*2.0
samples_features_X =array_X.reshape([-1,256,256,3])
array_Y = (np.array(inputY)/(255*1.0)-0.5)*2.0
samples_features_Y =array_Y.reshape([-1,256,256,3])feed = {self.inputsX: samples_features_X,self.inputsY: samples_features_Y}
predict_result = self.session.run(self.result, feed_dict=feed)
predict_result =np.array(((predict_result +1)/2*255),np.uint8).squeeze()[:,:,::-1]
return predict_resultif __name__=="__main__":
bgan=BeautyGAN()
imageX=cv2.imread("./imgs/no_makeup/vSYYZ306.png")
imageY=cv2.imread("./imgs/makeup/vFG756.png")
predict_result=bgan.interface(imageX,imageY)cv2.imwrite("predict_result.jpg",predict_result)
实验结果:
文章图片
推荐阅读
- GAN|GAN(生成对抗网络)学习——pytorch实现MINIST手写数据集生成
- 机器学习|从零使用GAN(生成对抗网络)进行图像生成
- 深度学习|#萌新日志#3.使用pix2pix CycleGAN和3d CycleGAN实现T1和T2加权模态的互转