GIRAFFE: CVPR 2021 最佳论文介绍和代码解释

GIRAFFE是一个基于学习的、完全可微的渲染引擎,用于将场景合成为多个“特征域”的总和。

CVPR 2021年结束了,深度学习继续在计算机视觉领域占据主导地位,包括SLAM、姿态估计、深度估计、新的数据集、GANs,以及去年的神经辐射场[1]或NeRFs的许多改进。

到目前为止,你可能已经听说过一篇论文“GIRAFFE: representation Scenes as composition Generative Neural Feature Fields”。[2]“作为今年最佳论文奖的大奖,这篇论文结合了gan、nerf和可差异化渲染来生成新颖的图像。然而更重要的是,它提供了一个模块化框架,以完全可微和可学习的方式从对象构建和组成3D场景,让我们向神经三维设计的世界更近一步。在这篇文章中,我将进一步研究GIRAFFE源代码,并生成一些快速的可视化示例。

简单回顾一下nerf,它们是一种描述和渲染3D场景的方法,在3D体积中任何给定的点上它的密度和辐射。它与光场的概念密切相关,光场是表达光如何流经给定空间的函数。对于空间中给定的(x,y,z)视点,图像将方向(θ, φ)的射线投射到一个场景中。对于这条线上的每个点,我们收集其密度和视相关的发射辐射,并以类似于传统光线追踪的方式将这些光线合成为单个像素值。这些NeRF场景是从各种姿势拍摄的图像收集学习,你会使用在结构从运动应用程序。

GIRAFFE

本质上,GIRAFFE 是一种基于学习的、完全可微的渲染引擎,它允许您将场景组合成多个“特征场”的总和,这是 NeRF 中辐射场的概括。这些特征字段是 3D 体积,其中每个体素包含一个特征向量。特征域是通过合成 GAN 生成的学习表示来构建的,这些表示接受潜在代码作为 3D 场景的输入。由于特征字段应用于 3D 体积,因此您可以应用相似性变换,例如旋转、平移和缩放。您甚至可以将整个场景合成为各个特征字段的总和。该方法对 NeRF 进行了以下改进:

可以用独立的变换来表示多个对象(和一个背景)(原始 NeRF 只能支持一个“场景”,无法解开单个对象)。

可以对单个对象应用姿势和相似性变换,例如旋转、平移和缩放。

生成特征字段的 GAN 可以像组件一样独立学习和重用。

具有经过端到端训练的可微渲染引擎。

颜色值不仅支持 RGB,还可能扩展到其他材料属性。

使用像转换器这样的位置编码来编码位置,这也“引入了归纳偏差来学习规范方向的 3D 形状表示,否则将是任意的。”

GIRAFFE 项目包括源代码,您可以使用这些源代码来重现他们的人物,甚至创作您自己的场景。我简要介绍了他们的源代码,并展示了我们如何使用 GIRAFFE 来组成一些简单的神经 3D 场景。

GIRAFFE 源代码的结构考虑了配置。 configs/default.yaml 文件指定应用程序的默认配置。 其他配置文件,如 configs/256res/cars_256/pretrained.yaml 使用 inherit_from 键从该配置文件继承,并通过指定其他键值对覆盖默认值。 这使我们能够使用 python render.py <CONFIG.yaml> 渲染图像,并通过自记录配置文件而不是组合输入参数使用 python train.py <CONFIG.yaml> 进行训练。

要自己尝试一些渲染,请首先运行 README.md 文件中的快速入门说明。 这将下载一个预训练模型并将一系列输出可视化(如下所示)写入文件夹 out。

配置文件只是采用默认值并在 Cars 数据集上插入一个预训练模型。它生成了许多操作底层渲染的方式的可视化,例如外观插值、形状插值、背景插值、旋转和平移。这些可视化在 configs/default.yaml 中的 render_program 键下指定,其值是指定这些可视化的字符串列表。这些指定了 GIRAFFE 渲染器在调用 render.py 时将调用的“渲染程序”。在 im2scene.giraffe.rendering.Renderer 的 render_full_visualization 方法中,您将看到一系列 if 语句,用于查找更多渲染程序的名称,例如“object_translation_circle”、“render_camera_elevation”和“render_add_cars”。

我们可以进行自定义的测试,创建一个名为 cars_256_pretrained_more.yaml 的新配置文件,并添加以下内容:

这只是我们之前使用的配置文件,默认配置文件的 render_program 键被我们想要的新渲染程序覆盖。现在执行 python render.py configs/256res/cars_256_pretrained_more.yaml 以生成更多可视化。你应该得到这样的结果:

inherit_from: configs/256res/cars_256.yaml
training:
  out_dir:  out/cars256_pretrained
test:
  model_file: https://s3.eu-central-1.amazonaws.com/avg-projects/giraffe/models/checkpoint_cars256-d9ea5e11.pt
rendering:
  render_dir: rendering
  render_program: ['render_camera_elevation', 'render_add_cars']

这只是我们之前使用的配置文件,默认配置文件的 render_program 键被我们想要的新渲染程序覆盖。 现在执行 python render.py configs/256res/cars_256_pretrained_more.yaml 以生成更多可视化。 你应该得到这样的结果:

带有 Cars 数据集的相机高程。 注意相机视角在背景和汽车轮廓上的变化,就好像相机从上方和下方围绕汽车旋转

使用其他数据集添加新的车

这些渲染程序实际上是如何放置、平移和旋转这些汽车的? 为了回答这个问题,我们需要仔细看看 Renderer 类。 对于上面的“object_rotation”示例,调用了 Renderer.render_object_rotation 方法。

class Renderer(object):
    # ...
    def render_object_rotation(self, img_out_path, batch_size=15, n_steps=32):
        gen = self.generator
        bbox_generator = gen.bounding_box_generator

        n_boxes = bbox_generator.n_boxes

        # Set rotation range
        is_full_rotation = (bbox_generator.rotation_range[0] == 0
                            and bbox_generator.rotation_range[1] == 1)
        n_steps = int(n_steps * 2) if is_full_rotation else n_steps
        r_scale = [0., 1.] if is_full_rotation else [0.1, 0.9]

        # Get Random codes and bg rotation
        latent_codes = gen.get_latent_codes(batch_size, tmp=self.sample_tmp)
        bg_rotation = gen.get_random_bg_rotation(batch_size)

        # Set Camera
        camera_matrices = gen.get_camera(batch_size=batch_size)
        s_val = [[0, 0, 0] for i in range(n_boxes)]
        t_val = [[0.5, 0.5, 0.5] for i in range(n_boxes)]
        r_val = [0. for i in range(n_boxes)]
        s, t, _ = gen.get_transformations(s_val, t_val, r_val, batch_size)

        out = []
        for step in range(n_steps):
            # Get rotation for this step
            r = [step * 1.0 / (n_steps - 1) for i in range(n_boxes)]
            r = [r_scale[0] + ri * (r_scale[1] - r_scale[0]) for ri in r]
            r = gen.get_rotation(r, batch_size)

            # define full transformation and evaluate model
            transformations = [s, t, r]
            with torch.no_grad():
                out_i = gen(batch_size, latent_codes, camera_matrices,
                            transformations, bg_rotation, mode='val')
            out.append(out_i.cpu())
        out = torch.stack(out)
        out_folder = join(img_out_path, 'rotation_object')
        makedirs(out_folder, exist_ok=True)
        self.save_video_and_images(
            out, out_folder, name='rotation_object',
            is_full_rotation=is_full_rotation,
            add_reverse=(not is_full_rotation))
    # ...

此函数为给定批次的成员生成一系列旋转矩阵。 然后它迭代地将这个范围的成员(以及一些缩放和平移的默认值)传递给生成器网络的 forward 方法,该方法由 default.yaml 中的生成器键指定。 如果您现在查看 im2scene.giraffe.models.init.py,您将看到此键映射到 im2scene.giraffe.models.generator.Generator。

from im2scene.giraffe.models import generator
# ...
generator_dict = {
    'simple': generator.Generator,
}

现在,查看 Generator.forward 。 它接受各种可选的输入参数,例如transformations、bg_rotation 和camera_matrices,然后将它们传递给它的volume_render_image 方法。 这就是合成魔法发生的地方。 场景中所有对象的潜在代码,包括我们的背景,都被分为它们的形状和外观组件。

z_shape_obj, z_app_obj, z_shape_bg, z_app_bg = latent_codes

在这个例子中,这些潜在代码是使用 torch.randn 函数随机生成的:

class Generator(nn.Module):
    # ...
    def get_latent_codes(self, batch_size=32, tmp=1.):
        z_dim, z_dim_bg = self.z_dim, self.z_dim_bg
        n_boxes = self.get_n_boxes()
        def sample_z(x): return self.sample_z(x, tmp=tmp)
        z_shape_obj = sample_z((batch_size, n_boxes, z_dim))
        z_app_obj = sample_z((batch_size, n_boxes, z_dim))
        z_shape_bg = sample_z((batch_size, z_dim_bg))
        z_app_bg = sample_z((batch_size, z_dim_bg))
        return z_shape_obj, z_app_obj, z_shape_bg, z_app_bg

    def sample_z(self, size, to_device=True, tmp=1.):
        z = torch.randn(*size) * tmp
        if to_device:
            z = z.to(self.device)
        return z
    # ...

这是解码器前向传递将 3D 点和相机观察方向映射到每个对象的 σ 和 RGB(特征)值的地方。 不同的生成器应用于背景(为了可读性省略了细节)。

n_iter = n_boxes if not_render_background else n_boxes + 1
# ...
for i in range(n_iter):
    if i < n_boxes:  # Object
        p_i, r_i = self.get_evaluation_points(pixels_world,
            camera_world, di, transformations, i)
        z_shape_i, z_app_i = z_shape_obj[:, i], z_app_obj[:, i]
        feat_i, sigma_i = self.decoder(p_i, r_i, z_shape_i, z_app_i)
        # ...
    else:  # Background
        p_bg, r_bg = self.get_evaluation_points_bg(pixels_world,
            camera_world, di, bg_rotation)
        feat_i, sigma_i = self.background_generator(
            p_bg, r_bg, z_shape_bg, z_app_bg)
        # ...
    feat.append(feat_i)
    sigma.append(sigma_i)
# ...

然后使用复合函数通过 σ max 或平均值合成这些地图。

sigma_sum, feat_weighted = self.composite_function(sigma, feat)

最后,通过沿射线向量通过 σ 体积对特征图进行加权来创建最终图像。 最终结果是您在上面看到的动画之一的单个窗口中的单个帧(有关如何构造 di 和 ray_vector 的详细信息,请参阅 generator.py)。

weights = self.calc_volume_weights(di, ray_vector, sigma_sum)
feat_map = torch.sum(weights.unsqueeze(-1) * feat_weighted, dim=-2)

现在总结一下,让我们尝试创建自己的渲染程序。 这将简单地结合深度平移和旋转来创建汽车从左到右旋转和滑动的效果。 为此,我们对 renderer.py 中的 Renderer 类进行了一些简单的添加。

class Renderer(object):
    # ...
    def render_full_visualization(self, img_out_path,
            render_program=['object_rotation']):
        for rp in render_program:
            # ...
            # APPEND THIS TO THE END OF render_full_visualization
            if rp == 'object_wipeout':
                self.set_random_seed()
                self.render_object_wipeout(img_out_path)
    # ...
    # APPEND THIS TO THE END OF rendering.py
    def render_object_wipeout(self, img_out_path, batch_size=15,
            n_steps=32):
        gen = self.generator

        # Get values
        latent_codes = gen.get_latent_codes(batch_size, tmp=self.sample_tmp)
        bg_rotation = gen.get_random_bg_rotation(batch_size)
        camera_matrices = gen.get_camera(batch_size=batch_size)
        n_boxes = gen.bounding_box_generator.n_boxes
        s = [[0., 0., 0.]
             for i in range(n_boxes)]
        n_steps = int(n_steps * 2)
        r_scale = [0., 1.]

        if n_boxes == 1:
            t = []
            x_val = 0.5
        elif n_boxes == 2:
            t = [[0.5, 0.5, 0.]]
            x_val = 1.0

        out = []
        for step in range(n_steps):
            # translation
            i = step * 1.0 / (n_steps - 1)
            ti = t + [[0.1, i, 0.]]
            # rotation
            r = [step * 1.0 / (n_steps - 1) for i in range(n_boxes)]
            r = [r_scale[0] + ri * (r_scale[1] - r_scale[0]) for ri in r]

            transformations = gen.get_transformations(s, ti, r, batch_size)
            with torch.no_grad():
                out_i = gen(batch_size, latent_codes, camera_matrices,
                            transformations, bg_rotation, mode='val')
            out.append(out_i.cpu())
        out = torch.stack(out)

        out_folder = join(img_out_path, 'object_wipeout')
        makedirs(out_folder, exist_ok=True)
        self.save_video_and_images(
            out, out_folder, name='object_wipeout',
            add_reverse=True)

将这些添加内容复制粘贴到 render.py 中,然后创建以下配置文件作为 configs/256res/cars_256_pretrained_wipeout.yaml:

inherit_from: configs/256res/cars_256.yaml
training:
  out_dir:  out/cars256_pretrained
test:
  model_file: https://s3.eu-central-1.amazonaws.com/avg-projects/giraffe/models/checkpoint_cars256-d9ea5e11.pt
rendering:
  render_dir: rendering
  render_program: ['object_wipeout']

现在,如果您执行 python render.py configs/256res/cars_256_pretrained_wipeout.yaml,就能看到结果

GIRAFFE 是对 NeRF 和 GAN 研究最近大量研究的一个令人兴奋的补充。 辐射场表示描述了一个强大且可扩展的框架,我们可以用它以可区分和可学习的方式构建 3D 场景。 我希望您发现深入了解代码很有用。 如果是这样,我鼓励您自己查看作者的来源和论文。

参考

[1] Ben Mildenhall, Pratul P. Srinivasan, Matthew Tancik, Jonathan T. Barron, Ravi Ramamoorthi, Ren Ng — NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis (2020), ECCV 2020

热门文章

暂无图片
编程学习 ·

那些年让我们目瞪口呆的bug

程序员一生与bug奋战&#xff0c;可谓是杀敌无数&#xff0c;见怪不怪了&#xff01;在某知识社交平台中&#xff0c;一个“有哪些让程序员目瞪口呆的bug”的话题引来了6700多万的阅读&#xff0c;可见程序员们对一个话题的敏感度有多高。 1、麻省理工“只能发500英里的邮件” …
暂无图片
编程学习 ·

redis的下载与安装

下载redis wget http://download.redis.io/releases/redis-5.0.0.tar.gz解压redis tar -zxvf redis-5.0.0.tar.gz编译 make安装 make install快链方便进入redis ln -s redis-5.0.0 redis
暂无图片
编程学习 ·

《大话数据结构》第三章学习笔记--线性表(一)

线性表的定义 线性表&#xff1a;零个或多个数据元素的有限序列。 线性表元素的个数n定义为线性表的长度。n为0时&#xff0c;为空表。 在比较复杂的线性表中&#xff0c;一个数据元素可以由若干个数据项组成。 线性表的存储结构 顺序存储结构 可以用C语言中的一维数组来…
暂无图片
编程学习 ·

对象的扩展

文章目录对象的扩展属性的简洁表示法属性名表达式方法的name属性属性的可枚举性和遍历可枚举性属性的遍历super关键字对象的扩展运算符解构赋值扩展运算符AggregateError错误对象对象的扩展 属性的简洁表示法 const foo bar; const baz {foo}; baz // {foo: "bar"…
暂无图片
编程学习 ·

让程序员最头疼的5种编程语言

世界上的编程语言&#xff0c;按照其应用领域&#xff0c;可以粗略地分成三类。 有的语言是多面手&#xff0c;在很多不同的领域都能派上用场。大家学过的编程语言很多都属于这一类&#xff0c;比如说 C&#xff0c;Java&#xff0c; Python。 有的语言专注于某一特定的领域&…
暂无图片
编程学习 ·

写论文注意事项

参考链接 给研究生修改了一篇论文后&#xff0c;该985博导几近崩溃…… 重点分析 摘要与结论几乎重合 这一条是我见过研究生论文中最常出现的事情&#xff0c;很多情况下&#xff0c;他们论文中摘要部分与结论部分重复率超过70%。对于摘要而言&#xff0c;首先要用一小句话引…
暂无图片
编程学习 ·

安卓 串口开发

上图&#xff1a; 上码&#xff1a; 在APP grable添加 // 串口 需要配合在项目build.gradle中的repositories添加 maven {url "https://jitpack.io" }implementation com.github.licheedev.Android-SerialPort-API:serialport:1.0.1implementation com.jakewhart…
暂无图片
编程学习 ·

2021-2027年中国铪市场调研与发展趋势分析报告

2021-2027年中国铪市场调研与发展趋势分析报告 本报告研究中国市场铪的生产、消费及进出口情况&#xff0c;重点关注在中国市场扮演重要角色的全球及本土铪生产商&#xff0c;呈现这些厂商在中国市场的铪销量、收入、价格、毛利率、市场份额等关键指标。此外&#xff0c;针对…
暂无图片
编程学习 ·

Aggressive cows题目翻译

描述&#xff1a; Farmer John has built a new long barn, with N (2 < N < 100,000) stalls.&#xff08;John农民已经新建了一个长畜棚带有N&#xff08;2<N<100000&#xff09;个牛棚&#xff09; The stalls are located along a straight line at positions…
暂无图片
编程学习 ·

剖析组建PMO的6个大坑︱PMO深度实践

随着事业环境因素的不断纷繁演进&#xff0c;项目时代正在悄悄来临。设立项目经理转岗、要求PMP等项目管理证书已是基操&#xff0c;越来越多的组织开始组建PMO团队&#xff0c;大有曾经公司纷纷建造中台的气质&#xff08;当然两者的本质并不相同&#xff0c;只是说明这个趋势…
暂无图片
编程学习 ·

Flowable入门系列文章118 - 进程实例 07

1、获取流程实例的变量 GET运行时/进程实例/ {processInstanceId} /变量/ {变量名} 表1.获取流程实例的变量 - URL参数 参数需要值描述processInstanceId是串将流程实例的id添加到变量中。变量名是串要获取的变量的名称。 表2.获取流程实例的变量 - 响应代码 响应码描述200指…
暂无图片
编程学习 ·

微信每天自动给女[男]朋友发早安和土味情话

微信通知&#xff0c;每天给女朋友发早安、情话、诗句、天气信息等~ 前言 之前逛GitHub的时候发现了一个自动签到的小工具&#xff0c;b站、掘金等都可以&#xff0c;我看了下源码发现也是很简洁&#xff0c;也尝试用了一下&#xff0c;配置也都很简单&#xff0c;主要是他有一…
暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法&#xff0c;在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较&#xff0c;从而确定目标数是在中间数的左边还是右边&#xff0c;将查…
暂无图片
编程学习 ·

项目经理,你有什么优势吗?

大侠被一个问题问住了&#xff1a;你和别人比&#xff0c;你的优势是什么呢? 大侠听到这个问题后&#xff0c;脱口而出道&#xff1a;“项目管理能力和经验啊。” 听者抬头看了一下大侠&#xff0c;显然听者对大侠的这个回答不是很满意&#xff0c;但也没有继续追问。 大侠回家…
暂无图片
编程学习 ·

nginx的负载均衡和故障转移

#注&#xff1a;proxy_temp_path和proxy_cache_path指定的路径必须在同一分区 proxy_temp_path /data0/proxy_temp_dir; #设置Web缓存区名称为cache_one&#xff0c;内存缓存空间大小为200MB&#xff0c;1天没有被访问的内容自动清除&#xff0c;硬盘缓存空间大小为30GB。 pro…
暂无图片
编程学习 ·

业务逻辑漏洞

身份认证安全 绕过身份认证的几种方法 暴力破解 测试方法∶在没有验证码限制或者一次验证码可以多次使用的地方&#xff0c;可以分为以下几种情况︰ (1)爆破用户名。当输入的用户名不存在时&#xff0c;会显示请输入正确用户名&#xff0c;或者用户名不存在 (2)已知用户名。…