天天看点

tensorflow实现基于深度学习的图像补全

tensorflow实现基于深度学习的图像补全

目录

■ 简介

■ 第一步:将图像理解为一个概率分布的样本

     你是怎样补全缺失信息的呢?

     但是怎样着手统计呢?这些都是图像啊。

     那么我们怎样补全图像?

 ■ 第二步:快速生成假图像

     在未知概率分布情况下,学习生成新样本

     [ml-heavy] 生成对抗网络(generative adversarial net, gan) 的架构

     使用g(z)生成伪图像

     [ml-heavy] 训练dcgan

     现有的gan和dcgan实现

     [ml-heavy] 在tensorflow上构建dcgans

     在图片集上跑dcgan

■ 第三步:找到用于图像补全最好的伪图像

     使用 dcgan 进行图像补全

     [ml-heavy] 到 pgpg 的投影的损失函数

     [ml-heavy] 使用tensorflow来进行dcgan图像补全

     补全图像

 ■ 结论

简介

图像补全分为三个步骤。

首先我们将图像理解为一个概率分布的样本。

基于这种理解,学习如何生成伪图片。

然后我们找到最适合填充回去的伪图片。

使用photoshop来对图像缺失部分补全,并使用photoshop自动删除不要的部分。

这些图像是我从 lfw 数据集中取得的一个随机样本。

第一步:将图像理解为一个概率分布的样本

    1.你是怎样补全缺失信息的呢?

在上面的例子中,想象你正在构造一个可以填充缺失部分的系统。你会怎么做呢?你觉得人类大脑是怎么做的呢?你使用了什么样的信息呢? 

在博文中,我们会关注两种信息: 

语境信息:你可以通过周围的像素来推测缺失像素的信息。 

感知信息:你会用“正常”的部分来填充,比如你在现实生活中或其它图片上看到的样子。 

两者都很重要。没有语境信息,你怎么知道填充哪一个进去?没有感知信息,通过同样的上下文可以生成无数种可能。有些机器学习系统看起来“正常”的图片,人类看起来可能不太正常。 

如果有一种确切的、直观的算法,可以捕获前文图像补全步骤介绍中提到的两种属性,那就再好不过了。对于特定的情况,构造这样的算法是可行的。但是没有一般的方法。目前最好的解决方案是通过统计和机器学习来得到一个近似的技术。

    2.但是怎样着手统计呢?这些都是图像啊。

从这个分布中采样,就可以得到一些数据。需要搞清楚的是pdf和样本之间的联系。

从正态分布中的采样 

tensorflow实现基于深度学习的图像补全

2维图像的pdf和采样。 pdf 用等高线图表示,样本点画在上面。

这是1维分布,因为输入只能沿着一个维度。在两个维度上也可以这么做。 

在图像和统计学之间,最关键的联系就是,我们可以将图像看作是从一个高维概率分布中得到的采样。概率分布对应的是图像的像素。想象你在用相机拍照。得到的图像是由有限个数的像素组成。当你通过相机拍照的时候,你就在从这个复杂的概率分布中进行采样。这个概率分布就决定了我们判断一张图片是正常的,还是不正常的。对于图片而言,与正态分布不同的是,我们无法得知真实的概率分布,我们只能去收集样本。 

   3.那么我们怎样补全图像?

首先考虑多变量正态分布,以求得到一些启发。给定 x=1 , 那么 y 最可能的值是什么?我们可以固定x的值,然后找到使pdf最大的 y。 

tensorflow实现基于深度学习的图像补全

在多维正态分布中,给定x,得到最大可能的y

这个概念可以很自然地推广到图像概率分布。我们已知一些值,希望补全缺失值。这可以简单理解成一个最大化问题。我们搜索所有可能的缺失值,用于补全的图像就是可能性最大的值。 

第二步:快速生成假图像

   1.在未知概率分布情况下,学习生成新样本

tensorflow实现基于深度学习的图像补全

yann lecun 对生成对抗网络的介绍

tensorflow实现基于深度学习的图像补全

将生成对抗网络类比为街机游戏。两个网络相互对抗,共同进步。就像两个人类在游戏中对抗一样。

   2.[ml-heavy] 生成对抗网络(generativeadversarial net, gan) 的架构

z =np.random.uniform(-1, 1, 5)

array([0.77356483,  0.95258473,-0.18345086,  0.69224724, -0.34718733])

现在有了一个用于采样的简单分布,我们定义一个函数 g(z) 来从我们的原始概率分布中采样。

def g(z):

   ...

   return imagesample

z = np.random.uniform(-1,1, 5)

imagesample =g(z)

tensorflow实现基于深度学习的图像补全

卷积运算图示,蓝色是输入,绿色是输出。

接下来,假设你有一个3x3的输入。我们的目标是进行上采样(upsample),这样,得到一个更大的输出。你可以将微步长卷积理解为将输入图像放大,然后在像素间插入0。然后在这个放大后的图像上进行卷积操作,得到一个较大的输出。此处,输出为5x5。 

tensorflow实现基于深度学习的图像补全

微步长卷积运算图示,蓝色是输入,绿色是输出。

现在我们有了微步长卷积结构,可以得到g(z)的表达,以一个向量z∼pzz∼pz 作为输入,输出一张 64x64x3 的rgb图像。

tensorflow实现基于深度学习的图像补全

    3.使用g(z)生成伪图像

tensorflow实现基于深度学习的图像补全

另外,你也可以在输入空间z进行代数运算。下面是一个生成人脸的网络。 

tensorflow实现基于深度学习的图像补全

基于dcgan的人脸代数运算 dcgan论文。

    4.[ml-heavy] 训练dcgan

首先我们要定义一些符号。数据的概率分布(未知的)记作pdatapdata。那么g(z),(其中z∼pzz∼pz )可以理解为从一个概率分布中的采样。让我们把这个概率分布记作pgpg。

符号pzpdatapg含义z的概率分布,简单、已知图像的概率分布(未知),是图像数据样本的来源生成器g用来采样的概率分布,我们希望pg==pdata符号含义pzz的概率分布,简单、已知pdata图像的概率分布(未知),是图像数据样本的来源pg生成器g用来采样的概率分布,我们希望pg==pdata

判别器网络d(x)输入图像x,返回图像x是从pdatapdata的分布中采样的概率。理论上,当输入图像是从pdatapdata中采样得到时,判别器输出一个接近1的值,当输入图像是伪图像,比如pgpg采样得到的图像时,判别器输出一个接近0的值。在dcgans中,d(x)是一个传统的卷积神经网络。

tensorflow实现基于深度学习的图像补全

训练判别器的目标是:

·      1、对于真实数据分布x∼pdatax∼pdata的每一张图片,最大化d(x)。

·      2、对于不是真实数据分布x≁pdatax≁pdata的每一张图片,最小化d(x)。

生成器g(z)的训练目标是生成可以迷惑d的样本。输出是一张图像,可以作为判别器的输入。因此,生成器希望最大化d(g(z)),也就是最小化(1-d(g(z))),因为d是一个概率,取值在0和1之间。

mingmaxdex∼pdatalog(d(x)+ez∼pz[log(1−d(g(z)))]mingmaxdex∼pdatalog(d(x)+ez∼pz[log(1−d(g(z)))]

通过这个表达式关于d和g的参数的梯度,可以训练它们。我们知道如何快速计算这个表达式的每一个部分。数学期望可以通过大小为m的小批数据来估计,内侧的最大化可以通过k步梯度来估计。已经证明,k=1是比较适合训练的值。

    5.现有的gan和dcgan实现

在 github 上,你可以看到很多极棒的 gan 和 dcgan 实现。 

    6.[ml-heavy] 在tensorflow上构建dcgans

defgenerator(self, z):

    self.z_, self.h0_w, self.h0_b = linear(z,self.gf_dim*8*4*4, 'g_h0_lin', with_w=true)

    self.h0 = tf.reshape(self.z_, [-1, 4, 4,self.gf_dim * 8])

    h0 = tf.nn.relu(self.g_bn0(self.h0))

    self.h1, self.h1_w, self.h1_b =conv2d_transpose(h0,

        [self.batch_size, 8, 8, self.gf_dim*4],name='g_h1', with_w=true)

    h1 =tf.nn.relu(self.g_bn1(self.h1))

    h2, self.h2_w, self.h2_b =conv2d_transpose(h1,

        [self.batch_size, 16, 16,self.gf_dim*2], name='g_h2', with_w=true)

    h2 = tf.nn.relu(self.g_bn2(h2))

    h3, self.h3_w, self.h3_b =conv2d_transpose(h2,

        [self.batch_size, 32, 32,self.gf_dim*1], name='g_h3', with_w=true)

    h3 = tf.nn.relu(self.g_bn3(h3))

    h4, self.h4_w, self.h4_b =conv2d_transpose(h3,

        [self.batch_size, 64, 64, 3],name='g_h4', with_w=true)

    return tf.nn.tanh(h4)

defdiscriminator(self, image, reuse=false):

    if reuse:

       tf.get_variable_scope().reuse_variables()

    h0 = lrelu(conv2d(image, self.df_dim,name='d_h0_conv'))

    h1 = lrelu(self.d_bn1(conv2d(h0,self.df_dim*2, name='d_h1_conv')))

    h2 = lrelu(self.d_bn2(conv2d(h1,self.df_dim*4, name='d_h2_conv')))

    h3 = lrelu(self.d_bn3(conv2d(h2,self.df_dim*8, name='d_h3_conv')))

    h4 = linear(tf.reshape(h3, [-1, 8192]), 1,'d_h3_lin')

    return tf.nn.sigmoid(h4), h4

当我们初始化这个类的时候,将要用到这两个函数来构建模型。我们需要两个判别器,它们共享(复用)参数。一个用于来自数据分布的小批图像,另一个用于生成器生成的小批图像。

self.g =self.generator(self.z)

self.d,self.d_logits = self.discriminator(self.images)

self.d_,self.d_logits_ = self.discriminator(self.g, reuse=true)

self.d_loss_real= tf.reduce_mean(

    tf.nn.sigmoid_cross_entropy_with_logits(self.d_logits,

                                           tf.ones_like(self.d)))

self.d_loss_fake= tf.reduce_mean(

   tf.nn.sigmoid_cross_entropy_with_logits(self.d_logits_,

                                           tf.zeros_like(self.d_)))

self.g_loss =tf.reduce_mean(

                                           tf.ones_like(self.d_)))

self.d_loss =self.d_loss_real + self.d_loss_fake

将每个模型的变量汇总到一起,这样,它们可以分别训练。

t_vars = tf.trainable_variables()

self.d_vars =[var for var in t_vars if 'd_' in var.name]

self.g_vars =[var for var in t_vars if 'g_' in var.name]

d_optim =tf.train.adamoptimizer(config.learning_rate, beta1=config.beta1) \

                  .minimize(self.d_loss,var_list=self.d_vars)

g_optim =tf.train.adamoptimizer(config.learning_rate, beta1=config.beta1) \

                  .minimize(self.g_loss,var_list=self.g_vars)

下面我们遍历数据。每一次迭代,我们采样一个小批数据,然后使用优化器来更新网络。有趣的是,如果g只更新一次,鉴别器的损失不会变成0。另外,我认为最后调用d_loss_fake 和 d_loss_real 进行了一些不必要的计算,因为这些值在 d_optim 和 g_optim 中已经计算过了。作为tensorflow 的一个联系,你可以试着优化这一部分,并发送pr到原始的repo。

for epoch inxrange(config.epoch):

    ...

    for idx in xrange(0, batch_idxs):

        batch_images = ...

        batch_z = np.random.uniform(-1, 1,[config.batch_size, self.z_dim]) \

                    .astype(np.float32)

        # update d network

        _, summary_str =self.sess.run([d_optim, self.d_sum],

            feed_dict={ self.images:batch_images, self.z: batch_z })

        # update g network

        _, summary_str =self.sess.run([g_optim, self.g_sum],

            feed_dict={ self.z: batch_z })

        # run g_optim twice to make sure thatd_loss does not go to zero (different from paper)

        errd_fake =self.d_loss_fake.eval({self.z: batch_z})

        errd_real =self.d_loss_real.eval({self.images: batch_images})

        errg = self.g_loss.eval({self.z:batch_z})

    7.在图片集上跑dcgan

git clonehttps://github.com/cmusatyalab/openface.git

git clonehttps://github.com/bamos/dcgan-completion.tensorflow.git

下面下载一个人脸图像数据集。数据集中有没有标注不重要,我们会删掉它。不完全列表如下:ms-celeb-1m,celeba, casia-webface, facescrub, lfw, 和 megaface。将图片放在目录dcgan-completion.tensorflow/data/your-dataset/raw 下,表明它是数据集的原始数据。

现在我们用 openface 的 alignment 工具将图像预处理为 64x64 的数据。

./openface/util/align-dlib.pydata/dcgan-completion.tensorflow/data/your-dataset/raw aligninnereyesandbottomlipdata/dcgan-completion.tensorflow/data/your-dataset/aligned --size 64

最后我们将处理好图像的目录展平,这样目录下只有图像,没有子文件夹。

cddcgan-completion.tensorflow/data/your-dataset/aligned

find . -name'*.png' -exec mv {} . \;

find . -type d-empty -delete

cd ../../..

./train-dcgan.py--dataset ./data/your-dataset/aligned --epoch 20

你可以在 sample 文件夹中查看从生成器中随机抽样出来的样本发图像是什么样子。我在 casia-webface 数据集和 facescrub 数据集上训练,因为我手头就有这两个数据集。 14轮训练之后,我的样本是这样的。 

tensorflow实现基于深度学习的图像补全

在 casia-webface 和 facescrub 上训练14轮后的 dcgan 的样本

tensorboard--logdir ./logs 

tensorflow实现基于深度学习的图像补全

tensorboard 损失可视化图像。在训练过程中实时更新。

tensorflow实现基于深度学习的图像补全

dcgan 网络的tensorboard可视化

第三步:找到用于图像补全最好的伪图像

    1.使用 dcgan 进行图像补全

对于某个图片y进行图像补全,一个有道理但是不可行的方案是,对于缺失的像素,最大化d(y)。结果既不是数据分布(pdatapdata),也不是生成分布(pgpg)。我们期望的是,将y投影到生成分布上。

tensorflow实现基于深度学习的图像补全

    2.[ml-heavy] 到 pgpg 的投影的损失函数

tensorflow实现基于深度学习的图像补全

二值掩码图例

接下来,假设我们已经找到了一个 z^z^, 可以生成一个对缺失值进行重构的合理的g(z^)g(z^)。补全的像素 (1−m)⊙g(z^)(1−m)⊙g(z^) 可以加到原始像素上,得到重构的图像: 

xreconstructed=m⊙y+(1−m)⊙g(z^)xreconstructed=m⊙y+(1−m)⊙g(z^)

语境损失:为了得到和输入图像相同的上下文,需要确保y已知像素对应位置的g(z)g(z)尽可能相似。所以,当 g(z) 的输出和 y 已知位置图像不相似的时候,需要对 g(z) 进行惩罚。为此,我们用 g(z) 减去 y 中对应位置的像素,然后得到它们不相似的程度: 

lcontextual(z)=||m⊙g(z)−m⊙y||lcontextual(z)=||m⊙g(z)−m⊙y||

理想情况下,已知部分的 y 和 g(z) 的像素是相等的。也就是对于已知位置的像素i, ||m⊙g(z)i−m⊙yi||=0||m⊙g(z)i−m⊙yi||=0 , lcontextual(z)=0lcontextual(z)=0 。

感知损失:为了重建一个看起来真实的图像,需要确保判别器判定图像看起来是真实的。为此,我们进行和训练 dcgan 中相同的步骤。 

lperceptual(z)=log(1−d(g(z)))lperceptual(z)=log(1−d(g(z)))

最后,将语境损失和感知损失组合起来,就可以找到 z^z^ 了; 

l(z)=lcontextual(z)+λlperceptual(z)z^=argminzl(z)l(z)=lcontextual(z)+λlperceptual(z)z^=arg⁡minzl(z)

其中 λλ 是超参数,用来控制相比于感知损失,语境损失重要的程度。(我用的是默认的λ=0.1λ=0.1,并没有对这个值进行深入研究。)然后如前所述,使用 g(z) 来重建y中缺失的部分。 

    3.[ml-heavy] 使用tensorflow来进行dcgan图像补全

self.mask =tf.placeholder(tf.float32, [none] + self.image_shape, name='mask')

self.contextual_loss= tf.reduce_sum(

tf.contrib.layers.flatten(

    tf.abs(tf.mul(self.mask, self.g) -tf.mul(self.mask, self.images))), 1)

self.perceptual_loss= self.g_loss

self.complete_loss= self.contextual_loss + self.lam*self.perceptual_loss

self.grad_complete_loss= tf.gradients(self.complete_loss, self.z)

接下来,我们定义掩码。我只是在图像的中央区域加了一个,你可以加一些别的,比如随机掩码,然后发一个pull请求。

ifconfig.masktype == 'center':

    scale = 0.25

    assert(scale <= 0.5)

    mask = np.ones(self.image_shape)

    l = int(self.image_size*scale)

    u = int(self.image_size*(1.0-scale))

    mask[l:u, l:u, :] = 0.0

for idx inxrange(0, batch_idxs):

    batch_images = ...

    batch_mask = np.resize(mask,[self.batch_size] + self.image_shape)

    zhats = np.random.uniform(-1, 1,size=(self.batch_size, self.z_dim))

    v = 0

    for i in xrange(config.niter):

        fd = {

            self.z: zhats,

            self.mask: batch_mask,

            self.images: batch_images,

        }

        run = [self.complete_loss,self.grad_complete_loss, self.g]

        loss, g, g_imgs = self.sess.run(run,feed_dict=fd)

        v_prev = np.copy(v)

        v = config.momentum*v - config.lr*g[0]

        zhats += -config.momentum * v_prev +(1+config.momentum)*v

        zhats = np.clip(zhats, -1, 1)

    4.补全图像

选择一些用于图像补全的图片,将它们放到dcgan-completion.tensorflow/your-test-data/raw 。然后像之前dcgan-completion.tensorflow/your-test-data/aligned 那样排列整齐。这里我从lfw中随机抽出一些图像。我的dcgan没有使用lfw的图像来训练。

你可以这样补全图像:

./complete.py./data/your-test-data/aligned/* --outdir outputimages

这段代码会生成图像,并周期性地将图像输出在 —outdir 文件夹中。你可以使用imagemagick来生成一个gif:

cd outputimages

convert -delay10 -loop 0 completed/*.png completion.gif

tensorflow实现基于深度学习的图像补全

最后的图像补全。图像的中心是自动生成的。源代码从此处下载。这是我随机从 lfw 中挑出的样本。

结论

感谢阅读,现在我们成功了!在文章中,我们涉及了图像补全的一种方法:

1、将图像理解为概率的分布。 

2、生成伪图像。 

3、找到用于补全最好的伪图像。

原文发布时间为:2017-5-11

本文来自云栖社区合作伙伴“大数据文摘”,了解相关信息可以关注“bigdatadigest”微信公众号