狗都能看懂的NeRF原理讲解
简介
NeRF是一篇2020年3月论文,也是如今3D领域的一个表达方式。
论文地址:https://arxiv.org/pdf/2003.08934
代码:https://github.com/bmild/nerf
笔者也是从其他教学视频中学习总结而来,具体推荐:https://www.bilibili.com/video/BV1o34y1P7Md?spm_id_from=333.788.videopod.sections&vd_source=b1ec667b727974f2a474487c7e66b834
渲染和反渲染
首先我们要理解为什么NeRF被称为神经隐式的三维重建。我们要从计算机图形领域的渲染开始介绍,它的渲染方式是基于一个三维模型以及材质和光照的信息,通过一个特定的视角,将物体渲染成一个比较精美的画面,比如下图的Rendering的过程。
而在计算机图像处理中,有一个反渲染的过程,也就是我们常常会说的三维重建,它是希望通过渲染出来的图像,重新得到三维模型。一般三维模型我们会用网格、点云或者体素的方式去表现它。
NeRF
NeRF其实也是三维重建的一个技术,但它跟我们以往的三维重建方式有所不同,过往的三维重建技术是直接通过图片重建出一个网格、点云或者体素的模型。而NeRF是通过一种神经隐式的方式去建这个三维模型。什么叫神经隐式呢?从这张一张图的就能很清晰的看出,隐式体现在什么地方。
它将三维模型的信息存在了神经网络中,输入是相机的位姿,输出是输出图片的RGB和不透明度alpha。通过这样的方式,我们只需要输入相机的相关信息,那么它就会经过这个网络自动渲染出可能从来没有见过的特定视角的一张图片。我们只需要通过NeRF神经网络体积雾的渲染方式(体积雾:可以暂时先简单地理解为带不透明度的点云)通过对已知视角的图片训练,然后在推理时,输入其他视角的参数,从而预测出未出现视角的图片。此时三维模型信息就储存在NeRF的神经网络信息之中。
这里其实还有一个问题,一个NeRF神经网络模型只能存储一个三维物体或者一个三维场景的信息。不过NeRF已经是比较早的论文了,现在这个问题应该已经解决了(一个NeRF模型能用在多个场景或多个物体)。这种原始的NeRF只能用在一个物体上,也就是说,每次如果我需要用NeRF,都要对一个物体训练一个对应的NeRF神经网络模型。
谷歌官方有放出一段视频讲解它:https://www.youtube.com/watch?v=JuH79E8rdKc&ab_channel=MatthewTancik
NeRF的场景表示
NeRF神经网络的输入就是相机位姿,它的输出其实是一组采样点的颜色和不透明度值。即对应上面的5D坐标,注意这里的(x,y,z)并不是相机的位置,为什么它不是相机的位置?
相机位置在 NeRF 训练时是已知的(来自相机位姿),但它不是 5D 输入的一部分;渲染一条光线时,NeRF 会从相机位置沿光线方向,在不同的深度上取很多采样点,也就是上图上(a)中的每个黑点;这些采样点的位置就是(x,y,z),是世界坐标系下的空间位置;
我们可以这样理解:
相机位置 → 光线的起点光线方向(由相机姿态 + 像素坐标决定)沿着这条光线在不同深度采样 → 得到多个 3D 点的(x,y,z)每个采样点再加上它指向相机的方向(这是个球坐标)(θ, φ)→ 得到 5D 坐标
所以总结下来:
(x, y, z) → 采样点的空间位置(物体表面、物体内部、或者空气里)(θ, φ) → 从这个采样点指向相机的方向(同一条光线上的采样点,这两个值是一样的。他们分别控制水平方向角和垂直方向角)相机位置是已知的,但不直接放进 5D 输入,而是通过它推导出 (θ, φ)
有了这一组5D信息之后,我们就可以在位置拍摄物体,我们将它输入到神经网络中,最后把成像内容给输出出来。所以它的输出是一组颜色和不透明度值。通过这个方式,我们就能将三维信息模型,储存在一个神经网络之中(这个网络大概只有40MB)。
在训练过程中,它是以一个像素点对应的一个射线跟相机位置作为一个训练资料,他不是以整张图作为一个训练资料。而是将其中的像素点都抽离出来,如下图所示:
所以一个batch里面就会有很多个来自不同地方的像素点合在一起作为训练资料。输出的结果是RGB和不透明度值,但是这样的输出不是直接得到一个像素点的颜色值和不透明度值。它是先输出一段采样点上的RGBA的值(即一条光线上的所有采样点),在射线方向上,对这些采样点进行特定的积分,才得到像素点确切的颜色值。可以看作是下图(b)的这个过程:
这个过程是体积雾渲染的一个方式,所以我们通过NeRF渲染的出来的物体,会看起来像雾一样的感觉。通过对很多采样点的计算,才慢慢将物体拟合出来,但他本质上还是一个体积雾的一个形状。
位置编码
这里不说明它是如何结合的,从论文中能知道,位置编码主要是将图像中的高频信息体现出来。NeRF采用的位置编码能够大幅度提高图片生成质量,对比请看上图。
模型结构
NeRF的模型结构很简单,用全连接层的方式暴力堆叠,中间接回原始的输入,形成残差网络,最后的一个256层输出密度/不透明度(这里比较神奇的一点是,作者把这个密度值先输出出来,是他认为物体的密度值和观测角度是无关的,也就是说从不同的角度去观测物体时,物体的不透明度是不受所观测角度的影响),再结合相机的方向和位置编码,再输出颜色值。从论文的附录中可以看到,一个射线上会采样64个点,也即前文所提及的黑点。
注意:这里我用不透明度来表示原文的密度,可以这么理解。其实体大小就是不透明度,越大越表示吸收光纤。表示当前位置吸收多少光,也就是多显示颜色的多少,因为人眼看到的就是反光的而不是透光的。(个人理解,如有不对请指出)
Volume Render
这里的64个采样点是需要如何处理呢?其实它是一个Volume Render的部分,这64个采样点就其实就是在体积雾里面的64个点,但这个64个点,有些地方可能是透明的,有些地方它是不透明的,光线已经碰到了物体,那么就会显示不透明,即下图中的波峰的位置。
但是这里可能有个问题,这里的物体是会有前面和后面的。那么我们在计算的时候,先遇到某个面不透明度很高。像上图ray2一样,它的纵坐标代表不透明度,前面很高,后面也很高,第一个波峰代表碰到物体的边缘了,第二个波峰代表物体的背面。但在实际的物体成像方式上,我们是不应该把后面的内容成像到图片上。因为这个物体是不透明的。所以文中是用了特殊的计算方式,这里不展开讲解数学,主要是只提取第一个波峰所对应的颜色和不透明度值,来产生对应的画面中的像素。通过这样的方式我们就能够把整个画面渲染出来了。最后就是我们只需要通过一组图片(和对应的相机位姿)去训练一个网络,这个网络就能储存对应的模型信息了。我们再通过输入一个不一样视角的相机参数,就能够把我们想要的一个物体的角度给呈现出来。
分层采样
我们刚刚说的这64个采样点,默认是均匀采样的。这样做其实效果不会特别好。如下图所示,均匀采样的时候,物体外部(空气),物体内部(实心)都没什么意义。那这些采样点计算出来也是浪费算力。反而是在边缘这种采样点,我们应该在物体边缘添加多一些采样点。
而作者是采用了一种特殊的逆变换,即通过一开始计算出来的密度波峰,重新在波峰位置进行128个点的采样,也就是在密度值高的地方多采样一些点,就像下方的图一样: