人脸生成黑科技:实现人脸转变特效,让人脸自动戴墨镜

2020-02-18 23:04:21 浏览数 (1)

上一节我们通过VAE网络完成了人脸生成效果。VAE网络一个特性是会把人脸编码成一个含有200个分量的向量,反过来说在特定分布范围内的含有200个分量的向量就对应一张人脸。由于向量之间可以进行运算,这就意味着我们把两张不同人脸A,B分布转换成两个不同向量z_A,z_B,然后我们使用向量运算例如z_AB = z_A *(1 - alpha) z_B *alpha,就能将两个向量以一定比例合成一个新向量,该新向量就会对应一个人脸,而且这个人脸就会同时具有人脸A和B的特点,如果我们增大参数alpha,那么生成向量对应的人脸特征就会更像人脸B,如果我们减少alpha的值,生成向量对应的人脸就更像人脸A.

接下来我们看看如何实现人脸的转变特效,首先我们先出数据图片中选出具有特定特征的人脸图片,例如”戴墨镜“,然后使用编码器得出”戴墨镜“人脸图片的特征向量,然后我们再选取不带墨镜的人脸图片,计算其特征向量,然后使用向量运算将两个向量结合起来,这样我们就能把后面不带墨镜的人脸转换成带墨镜的模样。

我们看看相应代码实现:

代码语言:javascript复制
def  morph_faces(start_image_file, end_image_file):
    alpha = np.arange(0, 1, 0.1)
    atttribute_specific = att[att['image_id'].isin([start_image_file, end_image_file])]
    att_specific = atttribute_specific.reset_index()
    data_flow_label = imageLoader.build(att_specific, 2) #加载满足给定条件的图片

    example_batch = next(data_flow_label)
    example_images = example_batch[0]
    example_labels = example_batch[1]
    z_vectors = vae.encoder.predict(example_images)#获得人脸图像的关键向量
    fig = plt.figure(figsize = (18, 8))
    counter = 1
    img = example_images[0].squeeze()
    sub = fig.add_subplot(1, len(alpha)   2, counter)
    sub.axis('off')
    sub.imshow(img)
    counter  = 1

    for factor in alpha: #通过变化参数让一个向量滑向另一个向量
        changed_z_vector = z_vectors[0] * (1 - factor)   z_vectors[1] * factor
        changed_image = vae.decoder.predict(np.array([changed_z_vector]))[0] #根据新向量绘制人脸图像
        img = changed_image.squeeze()
        sub = fig.add_subplot(1, len(alpha)   2, counter)
        sub.axis('off')
        sub.imshow(img)
        counter  = 1
    img = example_images[1]
    sub = fig.add_subplot(1, len(alpha)   2, counter)
    sub.axis('off')
    sub.imshow(img)
    plt.show()

上面函数先将输入的两张图片转换成各自对应的向量,然后使用向量运算,通过变化中间参数的形式让一个向量不断的”滑向“另一个向量,由此形成的效果是,其中一张人脸图片参数”渐变“效果,随着向量不断滑向另一个向量,所生成的人脸图片越来越具备目标向量的特性,我们调用上面函数看看实现效果:

代码语言:javascript复制
start_image_file = '000238.jpg'
end_image_file = '000193.jpg' #戴墨镜人脸
morph_faces(start_image_file, end_image_file)

上面代码运行后所得结果如下:

处于最左和最右边的图像时我们输入的两张人脸图片,中间人脸是将一边人脸图片对应的向量滑向另一边时所产生的人脸,我们注意到中间人脸图片是左右两张人脸图片特征的混合。回到deepfake或zao这样的变脸应用,他们的原理就是先将计算原来视频中人脸变化所对应的不同向量,然后计算用户的人脸向量,然后将用户人脸向量”滑向“视频中人脸当前表情对应向量从而实现用户人脸展现出视频中人脸的同样表情,当前它们的实现比我们这里介绍的要复杂得多。

接下来我们看如何让一个没有带墨镜的人脸“自然而然”的带上墨镜,首先要做的是计算所有戴墨镜的人脸图片对应的向量,将这些向量加总求平均值得向量A,这样我们就能用向量A表达出”带墨镜“这一特征,然后计算所有没有带墨镜的人脸图片所对应的向量,将这些向量加总求平均值得向量B,将该向量减去”戴墨镜“所对应的特征向量,这样就得出滑动方向,然后将不带墨镜的人脸图片对应向量慢慢根据给定方向滑动,这样我们就可以天衣无缝的给原来没带墨镜的人脸带上墨镜,实现如下:

代码语言:javascript复制
def  get_vector_from_label(label, batch_size):
    data_flow_label = imageLoader.build(att, batch_size, label = label) #读取所有图片

    origin = np.zeros(shape = vae.z_dim, dtype = 'float32') #原点向量
    current_sum_pos = np.zeros(shape = vae.z_dim, dtype = 'float32') #将所有含有给定特征的图片向量加总
    current_n_pos = 0 #统计当前具备给定特征的图片数量
    current_mean_pos = np.zeros(shape = vae.z_dim, dtype = 'float32') #求加给定特征向量加总后的平均值向量

    current_sum_neg = np.zeros(shape = vae.z_dim, dtype = 'float32') #不具备给定特征的向量加总
    current_n_neg = 0 #不具备给定特征的向量个数
    current_mean_neg =  np.zeros(shape = vae.z_dim, dtype = 'float32') #不具备给定特征的向量加总后求平均所得向量

    current_vector = np.zeros(shape = vae.z_dim, dtype = 'float32')
    current_dist = 0 #给定图片特征向量与给定特征向量之间的距离
    print('label: '   label) #label表示给定特征
    print('image : POS move : NEG move : distance : delta distance')
    while current_n_pos < 10000:
        batch = next(data_flow_label)
        im = batch[0]
        attribute = batch[1]

        z = vae.encoder.predict(np.array(im)) #计算图片对应的特征向量

        z_pos = z[attribute == 1] #抽取出具有给定特征图片所对应的向量
        z_neg = z[attribute == -1] #抽取出不具备给定特征的图片所对应的向量

        if len(z_pos) > 0:
            current_sum_pos = current_sum_pos   np.sum(z_pos, axis = 0) #将所有含有给定特征的向量加总
            current_n_pos  = len(z_pos)
            new_mean_pos = current_sum_pos / current_n_pos #计算平均向量
            #随着图片加载越多,具有给定特征的图片也就越多,于是得到的平均向量在位置上就会发生改变
            movement_pos = np.linalg.norm(new_mean_pos - current_mean_pos) #计算特征向量加总求平均后所得向量的变化量
        if len(z_neg) > 0:
            current_sum_neg = current_sum_neg   np.sum(z_neg, axis = 0) #将不具备给定特征的人脸向量加总
            current_n_neg  = len(z_neg)
            new_mean_neg = current_sum_neg / current_n_neg #计算平均向量
            #随着读入图片的增多,不具备给定特征的图片也会增多,于是平均向量也会发生改变
            movement_neg = np.linalg.norm(new_mean_neg - current_mean_neg) #计算平均向量的改变量

        current_vector = new_mean_pos - new_mean_neg #获得连接给定特征的平均向量和不具备给定特征的平均向量之间的连接向量
        new_dist = np.linalg.norm(current_vector) #获得两个向量之间的距离
        dist_change = new_dist - current_dist

        print(str(current_n_pos)   '      :  '   str(np.round(movement_pos, 3))
                '      :  '   str(np.round(movement_neg, 3))
                '      :  '   str(np.round(new_dist, 3))
                '      :  '   str(np.round(dist_change, 3))
              )
        current_mean_pos = np.copy(new_mean_pos)
        current_mean_neg = np.copy(new_mean_neg)
        current_dist = np.copy(new_dist)

        if np.sum([movement_pos, movement_neg]) < 0.08: #当向量该变量足够小时我们结束对平均向量的计算
            current_vector = current_vector / current_dist
            print("Found the "   label   ' vector')
            break
    return  current_vector

def  add_vector_to_images(feature_vec):  #将给定图片的向量滑向给定特征平均向量,于是让图片具备给定特征
    n_to_show = 5
    factors = [-4, -3, -2, -1, 0, 1, 2, 3, 4]

    example_batch = next(data_flow_generic)
    example_images = example_batch[0]
    example_labels = example_batch[1]

    z_points = vae.encoder.predict(example_images) #获得图片对应特征向量
    fig = plt.figure(figsize = (18, 10))
    counter = 1

    for i in range(n_to_show):
        img = example_images[i].squeeze()
        sub = fig.add_subplot(n_to_show, len(factors)   1, counter)
        sub.axis('off')
        sub.imshow(img) #显示没有变化前图片

        counter  = 1

        for factor in factors:  #让图片对应向量滑向给定特征平均向量
            changed_z_point = z_points[i]   feature_vec * factor
            changed_image = vae.decoder.predict(np.array([changed_z_point]))[0] #根据滑动后向量创建人脸图片

            img = changed_image.squeeze()
            sub = fig.add_subplot(n_to_show, len(factors)   1, counter)
            sub.axis('off')
            sub.imshow(img)
            counter  = 1

    plt.show()

接着我们随机采用若干张没有戴墨镜的人脸图片,然后先调用上面代码计算戴墨镜这一特征对应的向量,然后将人类图片对应向量沿着戴墨镜所对应特征向量的方向滑动,这样就能使得不带墨镜的人脸变成戴墨镜,代码如下:

代码语言:javascript复制
BATCH_SIZE = 500
eyeglasses_vec = get_vector_from_label("Eyeglasses", BATCH_SIZE) #先获得"带墨镜"对应的特征向量
add_vector_to_images(eyeglasses_vec) #让没有带墨镜的人脸图片添加上带墨镜效果

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

从中我们看到,最左边对应没有戴墨镜的人脸图片,最右边的人脸则是戴墨镜的效果,到这里我们就介绍完使用VAE网络实现人脸生成的技术方法

0 人点赞