人脸生成黑科技:使用VAE网络实现人脸生成

2020-02-11 17:00:57 浏览数 (1)

上一节我们描述了VAE网络的数学原理,特别强调了它能把输入数据隐射到一个区域内,这种特性带来一个特点是,如果将两个不同数据输入网络得到两个区间,这两个区间要是有重合的话,我们在重合的区域内取一点让解码器进行还原,那么被还原的数据就有可能兼具两个输入数据的特点融合,就好像孩子兼具爸爸与妈妈的特征那样,这点特性在人脸生成上大有用场。

这次我们使用CelebA数据集来训练VAE网络,该数据集包含了将近200000张人脸图像,这次我们使用的网络结构与上一节相差不大,只是在细节上要做一些改变。在上节我们网络识别的数字图片是单值灰度图,这次我们要处理RGB图像,同时由于人脸蕴含的特征远远超过数字图片,因此编码器输出的关键向量维度要从2提升到200以上;第三,为了让网络训练速度加快,我们需要在卷积层输出数据上进行成批的正规化处理;第四,在损失函数的相关参数上要根据经验进行手动调整。

首先我们先在上一节代码的基础上构造本节需要使用的网络实例:

代码语言:javascript复制
vae = VariationalEncoder(input_dim = [128, 128, 3],
                         encoder_conv_filters = [32, 64, 64, 64],
                         encoder_conv_kernel_size = [3,3,3,3],
                         encoder_conv_strides = [2, 2, 2, 2],
                         z_dim = 200,
                         decoder_conv_t_filters = [64,64,64,32],
                         decoder_conv_t_kernel_size = [3,3,3,3] ,
                         decoder_conv_t_strides = [2,2,2,2],
                         use_batch_norm = True,
                         use_dropout = True)

接下来我们把人脸数据加压到代码所在网络,并使用如下代码加载到内存,你可以从如下链接获得相应数据集: https://pan.baidu.com/s/13CDS_74Z7XFOt6AvRSTiZg 我们看看如何使用keras提供的datagenerator分批次将图片数据读入内存

代码语言:javascript复制
from glob import glob
from keras.preprocessing.image import ImageDataGenerator
section = 'vae'
run_id = '0001'
data_name = 'faces'
RUN_FOLDER = 'C:\generate_face\run\{}\'.format(section)
RUN_FOLDER  = '_'.join([run_id, data_name])
if not os.path.exists(RUN_FOLDER):#构造文件夹存储网络训练过程中产生的数据
    os.mkdir(RUN_FOLDER)
    os.mkdir(os.path.join(RUN_FOLDER, 'viz'))
    os.mkdir(os.path.join(RUN_FOLDER, 'images'))
    os.mkdir(os.path.join(RUN_FOLDER, 'weights'))
mode = 'build'
DATA_FOLDER = 'C:\generate_face\img_align_celeba\img_align_celeba\'
BATCH_SIZE = 32 #一次读取32张图片进行分析
INPUT_DIM = (128, 128, 3) #图片的规格
filenames = np.array(glob(os.path.join(DATA_FOLDER, '*.JPG')))
NUM_IMAGES = len(filenames)
print("pic num: ", NUM_IMAGES)
data_gen = ImageDataGenerator(rescale = 1./255)#加载图片时将每个像素点值除以255转换到[0,1]之间
data_flow = data_gen.flow_from_directory(DATA_FOLDER,
                                        target_size = INPUT_DIM[:2],
                                        batch_size = BATCH_SIZE,
                                        shuffle = True,
                                        class_mode = 'input',
                                        subset = 'training')

上面代码运行后,总共有202599张图片被读取,它们将以分批方式读入内存传给网络进行训练,接下来我们设定网络训练时需要使用的相关参数,并启动训练流程:

代码语言:javascript复制
LEARNING_RATE = 0.0005
R_LOSS_FACTOR = 10000
EPOCHS = 200
PRINT_EVERY_N_BATCHES = 100
INITIAL_EPOCH = 0
vae.compile(LEARNING_RATE, R_LOSS_FACTOR)
vae.train_with_generator(
    data_flow
    , epochs = EPOCHS
    , steps_per_epoch = NUM_IMAGES / BATCH_SIZE
    , run_folder = RUN_FOLDER
    , print_every_n_batches = PRINT_EVERY_N_BATCHES
    , initial_epoch = INITIAL_EPOCH
)

上面代码训练后,我们准备用训练好的网络识别人脸图像,首先我们先加载每张人脸图片对应的特征信息,这些信息存储在一个名为list_attr_celeba.csv的文件中:

代码语言:javascript复制
import pandas as pd
INPUT_DIM = (128,128,3)
att = pd.read_csv(os.path.join(DATA_FOLDER, 'list_attr_celeba.csv'))
att.head()

上面代码运行后输出结果如下:

我们看到每张图片对应的特性都会以1或-1的方式标记出来,1表示图片含有给定特性,-1表示没有,接下来我们把图片重新读入内容同时让每张图片对应相应特性:

代码语言:javascript复制
class ImageLabelLoader():
    def __init__(self, image_folder, target_size):
        self.image_folder = image_folder
        self.target_size = target_size

    def build(self, att, batch_size, label = None):

        data_gen = ImageDataGenerator(rescale=1./255)
        if label:
            data_flow = data_gen.flow_from_dataframe(
                att
                , self.image_folder
                , x_col='image_id'
                , y_col=label
                , target_size=self.target_size
                , class_mode='other'
                , batch_size=batch_size
                , shuffle=True
            )
        else:
            data_flow = data_gen.flow_from_dataframe(
                att
                , self.image_folder
                , x_col='image_id'
                , target_size=self.target_size
                , class_mode='input'
                , batch_size=batch_size
                , shuffle=True
            )

        return data_flow
DATA_FOLDER = '/content/drive/My Drive/all_images/images/'
imageLoader = ImageLabelLoader(DATA_FOLDER, INPUT_DIM[:2])
data_flow_generic = imageLoader.build(att, n_to_show)
print(len(data_flow_generic))

上面代码先从读取10张原图片,将其输入网络后通过编码器获得人脸对应的关键向量,然后再使用解码器根据关键向量重构图片,运行后输出结果如下:

上面是原图,下面是网络重构后输出的图片。输出虽然不是很清晰,但是网络的确能够将一个区间内任意一点解码成符合人脸特征的图像.值得我们注意的是,重构的图片与原图片有一些差异,这些差异的产生主要在于输入解码器的向量与编码器输出的并不完全一样,输入解码器的向量是从一个区间内随机采样的一点,因此得到的向量与解码器对输入图片的编码不同,但由于采样的向量与编码器对输入图片的编码结果在距离上比较接近,因此输出图片的特征与输入图片依然有很大的相似之处。

我们接下来看看如何用编解码器生成新人脸:

代码语言:javascript复制
n_to_show = 30
'''
随机采样一点作为关键向量,因为解码器已经知道如何将位于单位正太分布区间内的一点转换为人脸,
因此我们随机在区间内获取一点后,解码器就能生成相应人脸
'''
znew = np.random.normal(size = (n_to_show, vae.z_dim))
new_face = vae.decoder.predict(np.array(znew))
fig = plt.figure(figsize = (18, 5))
fig.subplots_adjust(hspace = 0.4, wspace = 0.4)
for i in range(n_to_show):
  ax = fig.add_subplot(3, 10, i 1)
  ax.imshow(new_face[i, :, :, :])
  ax.axis('off')
plt.show()

上面代码运行后输出结果如下:

上面的人脸图片在我们的图片库中不存在,是网络动态生成的结果。这些人脸实际上与图片库中的不同人脸又有相似之处,他们的生成实际上是网络将图片库中人脸的不同特征进行组合的结果。上面生成人脸中,某个人脸的头发颜色可能来自图片库某张图片,发型可能又来自另一张图片,眼睛可能又来自第三张图片,由于编码器能将人类分解成200个特征点,也就是关键向量中的每个分量,当我们从这些分量中随机采样时,就相当于对人脸不同特征进行抽取,最后再把这些抽取出来的特征组合成一张人脸,下一节我们会看到如何实现更神奇的人脸变换。

更多精彩讲解和演示请点击’阅读原文‘

0 人点赞