美颜换妆之BeautyGAN

论文: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。

 

主要贡献:

  1. 基于GAN实现了自动的换装,实验表明,BeautyGAN不仅高效,而且生成质量优于目前最好的方法。
  2. 通过在局部区域应用像素级别的直方图loss,取得了实例级别的风格变换。该实例级别的变换策略也可以应用于风格迁移,属性变换等其他任务。
  3. 贡献了3834张高清图片的换妆数据集Makeup Transfer(MT)

 

网络结构:

生成器G为两个输入和两个输出的网络结构,中间的模块共享权重。生成器G中还使用了IN(instance normalization)模块。生成器的输入图片大小为256*256,输出图片大小也是256*256。

判别器D为70*70的PatchGANs。

 

损失函数:

假设未化妆图片为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层,位置<j,k>处的第i个滤波器。

 

换妆约束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操作?

  1. 背景和头发区域的像素和换妆是没有关系的。
  2. 人脸换妆不仅是一个全局的风格变换,更是人脸不同区域的独立风格的变换。

First, pixels in background and hairs have no relationship with makeup. If we do not separate them apart, they
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.

为什么要使用Histogram loss,而不是MSE loss?

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_util


def 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 tf

os.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_result

if __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)
    

 

 

实验结果:

 

 

 

 

 

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值