前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Neural Rerendering in the Wild》论文解析

《Neural Rerendering in the Wild》论文解析

原创
作者头像
craftsliu
发布2019-09-09 19:39:10
1K0
发布2019-09-09 19:39:10
举报
文章被收录于专栏:craftsliucraftsliu

这篇关于神经网络重渲染的文章,来自CVPR2019 oral.探索在不同的外观(如季节和时间)下记录,建模和重新渲染场景。基于记录旅游地标的互联网照片,论文对照片进行3D重构,并将场景近似为点云。对于每张照片,将场景点云渲染为深度帧缓冲deep framebuffer,并训练神经网络以学习这些初始渲染到真实照片的映射。通过这种方法,我们可以在屏幕前就能获取罗马一天的观光之旅,或者基于这种方法,构建真实的游戏场景体验。该渲染网络还将潜在外观向量和指示诸如行人的瞬态对象的位置语义掩码作为输入,同时对该模型在多种多样的光照条件的数据集上进行评估。作者还提供了视频,展示对图像视点,外观和语义标签的逼真处理。

效果展示:https://www.youtube.com/watch?v=E1crWQn_kmY

论文将整个场景捕获的问题定义为为给定场景的所有图像创建生成模型。我们希望这样的模型:

  • 编码场景的3D结构,从任意视点进行渲染
  • 捕捉场景的所有可能外观,例如,所有光照和天气条件,并允许在任何场景下渲染场景
  • 了解场景中瞬态物体的位置和外观(如行人和汽车)并允许重现或忽略它们

尽管这些目标雄心勃勃,但通过实验表明,如果有足够的场景图像,例如为热门旅游地标获得的图像,就可以创建这样的生成模型。论文提出的全场景捕捉方法有两个主要组成部分:

(1)创建输入图像的因式表示,分离视点,外观条件和行人等瞬态对象。

(2)从这个因子表示中渲染逼真的图像。

不同于最近的方法,提取隐含的解析的观点和内容表示,论文采用最先进的重建方法,以密集但嘈杂的点云的形式创建一个明确的中间3D表示,并使用这个3D表示作为“脚手架”来预测图像。显式3D表示允许将渲染问题转换为多模式图像转换。输入是延迟着色帧缓冲,其中每个渲染像素存储反照率,深度和其他属性,输出是不同外观下的逼真视图。我们通过生成配对数据集来训练模型,使用每个输入图像的恢复的视点参数从同一视图渲染场景的深缓冲区,即,以像素方向对齐。我们的模型有效地学习采取近似的初始场景渲染并重新渲染逼真的图像。这类似于最近的神经再造框架,但使用不受控制的互联网图像而不是仔细捕获的镜头。

论文描述了一个神经重新渲染框架,通过对受控捕获设置中的工作进行调整,对非结构化照片集的更具挑战性的设置,论文扩展了这个模型,以便在不同的外观下实现外观捕获和多模式生成渲染。通过对输入图片的语义标签进行建模,我们进一步扩展模型,使得模型能更好地处理训练数据中的瞬态对象。

1. 神经网络重渲染框架 场景重建的传统方法首先使用大规模的运动结构生成稀疏重建,然后执行MultiView Stereo(MVS)或变分优化重建密集场景模型。然而,大多数这样的技术假定单个外观,或者简单地恢复场景的平均外观。我们基于这些技术,使用从MVS恢复的密集点云作为神经重新渲染的代理几何。基于互联网收集场景的照片集,训练集样本准备如下: real image: %04d_reference.png render color: %04d_color.png render depth: %04d_depth.png

我们首先使用COLMAP进行代理3D重建,它应用Structure-from-Motion(SfM)和MultiView Stereo(MVS)来创建密集的彩色点云。我们的神经重新渲染框架可以生成高度逼真的图像,只给出基于点的渲染作为输入。

给定代理3D重建,我们通过从每个输入图像的视点渲染3D点云来生成渲染图像和真实图像的对准数据集,其中vi由通过SfM恢复的相机内在函数和外部函数组成。我们为每个图像生成一个延迟着色深度缓冲区,可能包含每像素反照率,法线,深度和任何其他衍生信息。在我们的例子中,我们只使用反照率和深度,并通过使用半径为1像素的z缓冲区的点喷溅来渲染点云。

深度图像合成pix2pix的开创性工作,通过深度神经网络,使用成对的训练数据将图像从一个域(例如语义标签)转换到另一个域。图像翻译技术可用于在重新渲染场景,以实现面部表情合成,或添加视点依赖效果。但是,Image-to-Image不适用于我们的用例,因为它假定输入和输出之间的一对一映射。根据天气,光照条件,色彩平衡,后期处理滤镜等,从特定视点观察到的场景可能看起来非常不同。此外,一对一的映射无法解释场景中的瞬态物体,例如行人或汽车,其位置和个人外观无法单独从静态场景几何中预测。有趣的是,如果在数据集上对这个简单的任务训练足够大的神经网络,网络就会学会(1)通过记忆将视点与外观联系起来,以及(2)幻化瞬态对象的位置,如图

2. 外观建模 同样的场景在不同的时间,不同的天气条件下可以具有显着不同的外观,并且可以随着时间改变。我们使用不需要直接监督的方式,从输入图像分布中学习到的外观隐式表示。为了捕获输入视点和输出图像在不同外观下的一对多关系,我们将重新渲染任务转换为多模态图像转换.在这样的公式中,目标是学习潜在外观矢量za,其捕获输出域Ii中不能从输入域Bi推断的变化。我们计算潜在外观向量为za= Ea(Ii,Bi)其中Ea是外观编码器,其将输出图像Ii和深度缓冲器Bi作为输入。我们认为,通过外观编码器Ea观察输入Bi,可以通过将Ii中的光照与Bi中的场景几何相关联来学习更复杂的外观模型。最后,重新渲染网络R生成以视点Bi和潜在外观矢量za为条件的场景渲染。 下图显示整个过程的概述。

为了训练外观编码器Ea和渲染网络R,我们首先采用了多模态合成中的最新方法,即找到在我们的场景中最有效的组合。然而,这种组合仍然存在缺点,因为它无法很好地模拟不常见的外观。例如,它无法可靠地捕获我们数据集中场景的夜间外观。我们假设外观编码器(与渲染网络联合训练)没有足够的表达力用于捕获数据中的大变化。 为了提高模型表现力,我们的方法是通过预训练来稳定R和Ea的联合训练。外观网络Ea独立于代理任务。然后,我们采用分阶段的训练方法,其中渲染网络R首先使用固定外观进行训练嵌入,最后我们联合微调两个网络。这种分阶段的训练方法使得我们可以更简单的模型捕捉更复杂的外观。 下面从使用了目前最先进的多模态合成技术的Baseline方法以及Staged appearance training展开阐述。

Baseline 我们的基线使用BicycleGAN 有两个主要的改造。

[ BicycleGAN ]

首先,我们的外观编码器还将缓冲器Bi作为输入,如上所述。其次,我们添加交叉循环一致性损失,鼓励跨越的外观转移。

Staged apprearance training 我们分阶段训练方法的关键是外观预训练阶段,我们在代理任务中独立预先训练外观编码器Ea。然后,我们在固定Ea的权重,同时训练渲染网络R,允许R找到输出图像与代理任务产生的嵌入之间的相关性。最后,我们共同微调Ea和R. 这种分阶段的方法简化并稳定了R的训练,使得能够以更少的正则化项来训练更简单的网络。特别是,我们消除了cycle cross-cycle consistency loss,潜在向量重建损失和KL损失,只留下直接重建损失和GAN损失。

def create_computation_graph(x_in, x_gt, x_app=None, arch_type='pggan',
                             use_appearance=True):
   # ---------------------------------------------------------------------------
  # Build models/networks
  # ---------------------------------------------------------------------------
  rerenderer = networks.RenderingModel(arch_type, use_appearance)
  app_enc = rerenderer.get_appearance_encoder()
  discriminator = networks.MultiScaleDiscriminator(
      'd_model', opts.appearance_nc, num_scales=3, nf=64, n_layers=3,
      get_fmaps=False)
  # ---------------------------------------------------------------------------
  # Forward pass
  # ---------------------------------------------------------------------------
  if opts.use_appearance:
    z_app, _, _ = app_enc(x_app)
  else:
    z_app = None
  y = rerenderer(x_in, z_app)
  # ---------------------------------------------------------------------------
  # Losses
  # ---------------------------------------------------------------------------
  w_loss_gan = opts.w_loss_gan
  w_loss_recon = opts.w_loss_vgg if opts.use_vgg_loss else opts.w_loss_l1
# compute discriminator logits
  disc_real_featmaps = discriminator(x_gt, x_in)
  disc_fake_featmaps = discriminator(y, x_in)
  # discriminator loss
  loss_d_real = losses.multiscale_discriminator_loss(disc_real_featmaps, True)
  loss_d_fake = losses.multiscale_discriminator_loss(disc_fake_featmaps, False)
  loss_d = loss_d_real + loss_d_fake
  # generator loss
  loss_g_gan = losses.multiscale_discriminator_loss(disc_fake_featmaps, True)
  if opts.use_vgg_loss:
    vgg_layers = ['conv%d_2' % i for i in range(1, 6)]  # conv1 through conv5
    vgg_layer_weights = [1./32, 1./16, 1./8, 1./4, 1.]
    vgg_loss = losses.PerceptualLoss(y, x_gt, [256, 256, 3], vgg_layers,
                                     vgg_layer_weights)  # NOTE: shouldn't hardcode image size!
    loss_g_recon = vgg_loss()
  else:
    loss_g_recon = losses.L1_loss(y, x_gt)
  loss_g = w_loss_gan * loss_g_gan + w_loss_recon * loss_g_recon

训练方法的Alative Study

外观预训练 为了预先训练外观编码器Ea,我们选择一个代理任务,使用输入图像之间的适当距离度量来优化输入图像嵌入外观潜在空间。这种训练使得如果两个图像在距离度量下接近,那么它们的外观嵌入也应该在外观隐含特征空间中接近。

理想情况下,我们选择的距离度量应忽略Ii和Bi的内容或视点,因为我们的目标是编码独立于视点的潜在空间。在实验上我们发现神经风格转移工作中使用的style loss,有这样的特性,它在很大程度上忽略了内容,并专注于更抽象的属性。

def gram_matrix(layer):
  """Computes the gram_matrix for a batch of single vgg layer
  Input:
    layer: a batch of vgg activations for a single conv layer
  Returns:
    gram: [batch_sz x num_channels x num_channels]: a batch of gram matrices
  """
  batch_size, height, width, num_channels = layer.get_shape().as_list()
  features = tf.reshape(layer, [batch_size, height * width, num_channels])
  num_elements = tf.constant(num_channels * height * width, tf.float32)
  gram = tf.matmul(features, features, adjoint_a=True) / num_elements
  return gram
def compute_gram_matrices(
    images, vgg_layers=['conv1_2', 'conv2_2', 'conv3_2', 'conv4_2', 'conv5_2']):
  """Computes the gram matrix representation of a batch of images"""
  vgg_net = vgg16.Vgg16(opts.vgg16_path)
  vgg_acts = vgg_net.get_vgg_activations(images, vgg_layers)
  grams = [gram_matrix(layer) for layer in vgg_acts]
  return grams
def compute_pairwise_style_loss_v2(image_paths_list):
  grams_all = [None] * len(image_paths_list)
  crop_height, crop_width = opts.train_resolution, opts.train_resolution
  img_var = tf.placeholder(tf.float32, shape=[1, crop_height, crop_width, 3])
  vgg_layers = ['conv%d_2' % i for i in range(1, 6)]  # conv1 through conv5
  grams_ops = compute_gram_matrices(img_var, vgg_layers)
  with tf.Session() as sess:
    for ii, img_path in enumerate(image_paths_list):
      print('Computing gram matrices for image #%d' % (ii + 1))
      img = np.array(Image.open(img_path), dtype=np.float32)
      img = img * 2. / 255. - 1  # normalize image
      img = utils.get_central_crop(img, crop_height, crop_width)
      img = np.expand_dims(img, axis=0)
      grams_all[ii] = sess.run(grams_ops, feed_dict={img_var: img})
  print('Number of images = %d' % len(grams_all))
  print('Gram matrices per image:')
  for i in range(len(grams_all[0])):
    print('gram_matrix[%d].shape = %s' % (i, grams_all[0][i].shape))
  n_imgs = len(grams_all)
  dist_matrix = np.zeros((n_imgs, n_imgs))
  for i in range(n_imgs):
    print('Computing distances for image #%d' % i)
    for j in range(i + 1, n_imgs):
      loss_style = 0
      # Compute loss using all gram matrices from all layers
      for gram_i, gram_j in zip(grams_all[i], grams_all[j]):
        loss_style += np.mean((gram_i - gram_j) ** 2, axis=(1, 2))
      dist_matrix[i][j] = dist_matrix[j][i] = loss_style
  return dist_matrix

为了训练嵌入,我们使用triplet loss

其中对于每个图像,我们找到由style loss给出的k个最近和最远邻居图像的集合,我们从中取出正样本和负样本

def compute_dist_matrix(imageset_dir, dist_file_path,recompute_dist=False):
  if not recompute_dist and osp.exists(dist_file_path):
   print('*** Loading distance matrix from %s' % dist_file_path)
   with open(dist_file_path, 'rb') as f:
     dist_matrix = pickle.load(f)['dist_matrix']
     print('loaded a dist_matrix of shape: %s' % str(dist_matrix.shape))
     return dist_matrix
  else:
    images_paths = sorted(glob.glob(osp.join(imageset_dir, '*_reference.png')))
    dist_matrix = style_loss.compute_pairwise_style_loss_v2(images_paths)
    dist_dict = {'dist_matrix': dist_matrix}
    print('Saving distance matrix to %s' % dist_file_path)
    with open(dist_file_path, 'wb') as f:
      pickle.dump(dist_dict, f)
    return dist_matrix

3. 语义条件 为了解释场景中的瞬态对象,我们在图像Ii的语义标签Si上调节重新渲染网络,其描绘了诸如行人等瞬态对象的位置。具体来说,我们将语义标记Si连接到深度缓冲区Bi,无论以前使用深度缓冲区。这阻止网络编码由外观向量中的瞬态对象的位置引起的变化,或者将这些瞬态对象与特定视点相关联,如图Figure 2. 语义标记的另一个好处是它允许重新渲染网络推断未在3D重建中捕获的场景中的静态对象,例如圣马可广场的灯柱。这可以防止网络随意引入这些对象,而是让它们出现在语义标签中检测到的位置,这是一项非常简单的任务。此外,通过将分段标签添加到深度缓冲区,我们允许外观编码器在计算外观潜在向量时推理诸如天空或地面之类的语义类别。 我们使用在ADE20K数据上训练出的DeepLab在输入图像Ii上计算“地面实况”语义分割。ADE20K包含150个类,我们将其映射到3通道彩色图像。

[ ADE20K数据集 ]

由于地标具备独特的建筑物,地标本身的语义标签质量很差,,但在瞬态物体上是合理的。使用语义标记调整,重新渲染网络将场景的语义标记作为输入。为了重新渲染虚拟摄像机路径,我们需要为虚拟摄像机路径中的每个帧合成语义标签。为简单起见,我们在对齐数据集的样本(Bi,Si)上训练与渲染网络具有相同结构的网络(减去注入的外观向量),并修改地面实况图像Si的语义标签并掩盖掉标记为瞬态的像素损失,由ADE20K中的瞬态对象类别列表定义。

参考资料:

1.Neural Rerendering in the Wild

2.Toward Multimodal Image-to-Image Translation

3.http://colmap.github.io/

4.https://github.com/MoustafaMeshry/neural_rerendering_in_the_wild

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
图像处理
图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档