NeRF 其一:NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis
- 1. 什么是神经辐射场
- 2. 论文简述
- 3. 体渲染
- 3.1 视线
- 3.2 体渲染-连续
- 3.3 体渲染-离散
- 4. 神经网络与位置编码
- 4.1 神经网络
- 4.2 视线角度
- 4.3 位置编码
- 为什么需要位置编码?
- 高频与低频
- NeRF 中如何进行位置编码
- 5. 损失函数与训练策略
- 5.1 网络训练
- 5.2 损失函数
Reference:
- 深蓝学院:NeRF基础与常见算法解析
系列文章:
- NeRF 其一:NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis
- NeRF 其二:Mip-NeRF
首先来看看 NeRF 开山之作的标题----NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis:
- 使用神经辐射场来表达场景
- 以完成视角合成的目标
1. 什么是神经辐射场
因此 NeRF 的目标也就相当清楚了:首先拍摄多视角的图像,然后要拿到这些图像对应的内外参数(可以使用 COLMAP),这时使用神经网络去学习三维场景内的辐射值(学习不同方向的光线),然后就可以在不同视角下做可视化了。
这里要明白 光线(ray)
的含义,它是由 相机/眼睛 发出,已知会穿过实际 3D 物体的一条虚拟射线,如下图所示:
图中的 Image Plane
就是我们最后想得到的图,我们要用 NeRF 找到 Image Plane 上面每个像素的 RGB。
那么怎么找呢?就是利用图中的箭头,也就是我们说的 Ray,首先使用 NeRF 找到箭头与 Volume Data
也就是 3D 物体的若干交点,然后对这些交点做积分就行了。
这里并不是眼睛能发出光,而是相反只能接收光。但因为光路是可逆的,在理解问题时,可以假设眼睛发光,如下图所示。所以,一条光线对应最终图片中的一个像素,而确定光线的关键,就是要搞清楚相机的位姿,即光线的原点,因为所有的光线都是从相机 “发出” 的。
2. 论文简述
上图表示了 ReRF 内全部的 Pipeline。假设感兴趣的三维物体场景被包含在一个虚拟的 3D 立方体内。三维空间的一个点经过一个 [R|t],就可以转到摄像机坐标系,再通过相机内参就可以转到像素平面上,这样就可以将三维空间中的颜色赋给这个值,于是整张图像就都可以得到了。但是 NeRF 中考虑的不是一个点,而是考虑这条光线上所有的点,最后生成这个像素值,这个过程就被称为体渲染技术
。
论文内有以下几个重点概念:
- 三维场景:
输入(Position+Direction):一组相机位姿 { x , y , z , θ , ϕ } n \{x, y, z, \theta, \phi\}_n {x,y,z,θ,ϕ}n;
输出(Color+Density):体密度 σ \sigma σ + 观察角度 ( θ , ϕ ) (\theta, \phi) (θ,ϕ) 看到的颜色值 C C C。
上图内虚拟方格的每一个点都有一个 ( x , y , z ) (x,y,z) (x,y,z) 空间坐标值+一个体密度值 σ \sigma σ(体密度和这个点的材质有关,比如在图中拖拉机上和空气中的体密度就是不一样的)+颜色值 C C C,但是 C C C 在不同角度看过去颜色是不一样的-----这考虑到了镜面反射。 - 辐射场:
辐射场
为定义在空间中的函数,记录了空间中每个点的体密度 σ \sigma σ 以及该点每个方向上的颜色值 C C C(也就是辐射场内辐射值
的概念)
那么问题来了,什么样的函数能够记录这么多的信息?如果在每个角度都使用查找表,那这样做空间的消耗就太大了,也并不适合做推理。所以就用到了下一个概念-------全连接神经网络
,来记录辐射场信息。 - 神经网络:
全连接神经网络 F θ F_{\theta} Fθ - 体渲染(Volume Rendering):
视线上所有的点投射到图像上形成像素颜色的过程。
图像内包含两个流程:训练流程+推理流程。
有了这些概念,我们先描述推理流程:给我一个相机位置,就可以一个像素点一个像素点的生成一张图片。从一个像素点打一条光线出去,会与虚拟立方体空间相交获得一系列的点,通过这些点和训练好的辐射场信息,可以得到每个点的颜色和体密度。再通过体渲染将这些点合成起来,即可得到当前像素点的值。
训练流程的区别在于,获得了体渲染的颜色后需要和真实值进行比较,这就形成了一个 Loss(见图(d)),使用这个 Loss 来指导神经网络的更新,越接近越好。
3. 体渲染
3.1 视线
上一节提到了,视线上某一点由 5 5 5 维变量表示:
- P P P 三维空间中的一个点(三维,世界坐标);
- θ \theta θ 为视线的俯仰角(pitch);
- ϕ \phi ϕ 为视线的偏航角(yaw)。
这里滚转角对于射线没有意义。激光雷达也是使用的这种模型,在《自动驾驶与机器人中的SLAM技术》一书中,把这种模型称为 RAE(Range Azimuth Elevation)
模型。
视线
r
r
r 上的点可以表示为:
r
(
t
)
=
o
+
t
d
\boldsymbol{r}(t) = \boldsymbol{o}+t\boldsymbol{d}
r(t)=o+td
- o \boldsymbol{o} o 是摄像机的光心在世界坐标系的坐标;
- d \boldsymbol{d} d 为直线方向,是世界坐标系下的一个单位向量(笛卡尔坐标系);
- t t t 是一个实数,表示从 o \boldsymbol{o} o 点沿视线到 r ( t ) \mathbf{r}(t) r(t) 点的距离。
注意在论文中使用的是 θ \theta θ 和 ϕ \phi ϕ 二维信息,但在代码内使用的还是三维向量。给定一个视线 r \boldsymbol{r} r,即 o \boldsymbol{o} o 和 d \boldsymbol{d} d 已知,给定一个 t t t,就可以得到一个点的世界坐标。
3.2 体渲染-连续
体渲染
:视线上
r
\boldsymbol{r}
r 所有的点投射到图像上形成像素颜色
C
C
C 的过程,先来体渲染在连续情况下的函数:
C
(
r
)
=
∫
t
n
t
f
T
(
t
)
σ
(
r
(
t
)
)
c
(
r
(
t
)
,
d
)
d
t
, where
T
(
t
)
=
exp
(
−
∫
t
n
t
σ
(
r
(
s
)
)
d
s
)
C(\boldsymbol{r}{)=\int_{t_{n}}^{t_{f}} T(t) \sigma(\boldsymbol{r}(t)) \boldsymbol{c}(\boldsymbol{r}(t), \boldsymbol{d}) d t } \text {, where } T(t)=\exp \left(-\int_{t_{n}}^{t} \sigma(\boldsymbol{r}(s)) d s\right)
C(r)=∫tntfT(t)σ(r(t))c(r(t),d)dt, where T(t)=exp(−∫tntσ(r(s))ds)其中,
- r ( t ) \boldsymbol{r}(t) r(t) 为三维空间中的一个点, d \boldsymbol{d} d 为视线方向,这在 3.1 节提到了
- 三维场景的近端和远端边界分别为
t
n
t_n
tn 以及
t
f
t_f
tf
如果没有边界,那么意味着从 [ 0 , ∞ ) [0,\infty) [0,∞) 都要考虑,先不说可不可实现,计算量肯定是爆炸的。这里的 t n t_n tn 和 t f t_f tf 是需要指定的先验信息。 -
c
(
r
(
t
)
,
d
)
\boldsymbol{c}(\boldsymbol{r}(t), \boldsymbol{d})
c(r(t),d) 为三维点
r
(
t
)
\boldsymbol{r}(t)
r(t) 从
d
\boldsymbol{d}
d 这个方向看到的颜色值
因为点的颜色值和方向相关所以需要多传一个视线方向 d \boldsymbol{d} d。 -
σ
(
r
(
t
)
)
\sigma(\boldsymbol{r}(t))
σ(r(t)) 为体密度函数,反映了
r
(
t
)
\boldsymbol{r}(t)
r(t) 位置的物理材质吸收光线的能力
比如物体表面、物体内部、空气中,这个值都是不一样的:- σ = 0 \sigma=0 σ=0:全透明场景,没有吸收光线,比如空气;
- σ = ∞ \sigma = \infty σ=∞:一到这里光线就会被吸走,后面的点都看不见了;
-
σ
=
正数
\sigma = \text{正数}
σ=正数:这个材质会吸收一部分光线,另一部分光线会透过去,比如一会作为例子的海洋。
-
T
(
t
)
T(t)
T(t) 是射线上从
t
n
t_n
tn 到
t
t
t 的累积透射率
T T T 的范围应该是从 1 1 1 开始到 0 0 0,表示还剩下多少光强。当前点的 σ \sigma σ 越大、 T T T 越大,当前点对 C ( r ) C(\boldsymbol{r}) C(r) 的贡献也就越大。
从上面公式可以知道,
σ
\sigma
σ 和
c
\boldsymbol{c}
c 都是把当前三维点信息丢进神经网络内算出来的,而
T
T
T 是通过积分得到的。看下面一个例子:
- 开始因为在空气中,此时 σ = 0 \sigma=0 σ=0,因为 T T T 为 σ = 0 \sigma=0 σ=0 的积分,这时 T T T 恒为 1 1 1,即这一部分完全投射;
- 开始进入水下,这时候 σ \sigma σ 是有值的了;
- 在这一部分中, T T T 的值逐渐减小,这一阶段开始,射线上的点将会对像素颜色 C C C 产生贡献;
- 在这时候 T T T 为零,往下的点不参与计算;
- 因为这时即往下 T T T 都是 0 0 0 了,即使后续的 σ \sigma σ 是有值的,但它也不会对当前像素的颜色 C C C 产生任何贡献了。
3.3 体渲染-离散
在 3.2 小节的公式中写的是积分,但是在实际计算肯定不会是连续的,所以需要离散化。
第一个想到的肯定是均匀采样
,这里的均匀采样存在一个问题:两个点的跨度太大,中间的值没采到,这样肯定是不准确的。如下面示例,两个点中间的信息就没有采集到:
这时就想到了随机均匀采样的方法:
将
t
n
t_n
tn 到
t
f
t_f
tf 拆分为
N
N
N 个均匀分布区间,从每个区间中随机均匀抽取一个样本
t
i
t_i
ti:
t
i
∼
U
[
t
n
+
i
−
1
N
(
t
f
−
t
n
)
,
t
n
+
i
N
(
t
f
−
t
n
)
]
i从1到N
t_{i} \sim \mathcal{U}\left[t_{n}+\frac{i-1}{N}\left(t_{f}-t_{n}\right), t_{n}+\frac{i}{N}\left(t_{f}-t_{n}\right)\right] \text{i从1到N}
ti∼U[tn+Ni−1(tf−tn),tn+Ni(tf−tn)]i从1到N即分成两个步骤:
- 均匀的划分成 N N N 份;
- 每个区间内随机抽取一个样本,两样本间的最大间距也就是两倍的间隔,不会出现过大的情况。
在这种采样下,如何进行体渲染呢?
C
^
(
r
)
=
∑
i
=
1
N
T
i
(
1
−
exp
(
−
σ
i
δ
i
)
)
c
i
where
T
i
=
exp
(
−
∑
j
=
1
i
−
1
σ
j
δ
j
)
\hat{C}{(\mathbf{r})=\sum_{i=1}^{N} T_{i}\left(1-\exp \left(-\sigma_{i} \delta_{i}\right)\right) \mathbf{c}_{i} } \text{ where } T_{i}=\exp \left(-\sum_{j=1}^{i-1} \sigma_{j} \delta_{j}\right)
C^(r)=i=1∑NTi(1−exp(−σiδi))ci where Ti=exp(−j=1∑i−1σjδj)其中,
δ
i
=
t
i
+
1
−
t
i
\delta_{i}=t_{i+1}-t_{i}
δi=ti+1−ti 表示相邻采样点之间的距离。
4. 神经网络与位置编码
4.1 神经网络
- 用途:从第二节中已经了解到,神经网络用来记录辐射场信息
- 输入:坐标位置 x \boldsymbol{x} x 的位置编码 γ ( x ) \gamma(\boldsymbol{x}) γ(x) 以及视角向量 d \boldsymbol{d} d 的位置编码 γ ( d ) \gamma(\boldsymbol{d}) γ(d)
- 输出:在坐标位置 x \boldsymbol{x} x 的体密度 σ \sigma σ 以及视线 d \boldsymbol{d} d 方向观察时 x \boldsymbol{x} x 处的 RGB 值 c。
- 从图中可以看到,本身 3 3 3 维的 x \boldsymbol{x} x 经过位置编码后变成了 60 60 60 维,而本身 3 3 3 维的 d \boldsymbol{d} d 经过位置编码后变成了 24 24 24 维
- 每一层的激活函数都使用 ReLU
- 经过 8 8 8 层神经网络以后,会输出一个 σ \sigma σ 值, σ \sigma σ 只和位置有关,与视角方向无关(前面均没有传入 d \boldsymbol{d} d);同时这一层传入了 d \boldsymbol{d} d 的位置编码,用于得到该方向观察到的 RGB 值
- 将两个信息拼接起来送给神经网络的下一层,最后使用 sigmoid 输出一个 RGB 值,范围(0~1)
4.2 视线角度
为什么需要视角向量 d \boldsymbol{d} d?
图二为 NeRF 论文内得到的结果,图三为不加视角向量得到的结果,可以看到图二中的反光比较明显,而图三中的镜面反射消失了,添加视角向量得到的结果更符合真实场景。
4.3 位置编码
为什么需要位置编码?
将图二和图四进行比较,明显可以看到图四中的高频几何和纹理细节丢失了。
高频与低频
这里首先来理解一下高频与低频的概念:
- 图像信号:在图像中,也就是这个像素与旁边像素颜色的差异,在边缘处得到应该是高频信息,在颜色变化不大的位置得到的就是低频信息了;
- 三维场景:与邻居点在颜色和体密度上的差异。
总的来说,高频就意味着差异。比如相邻的颜色属于不同俩物质,颜色差异比较大,如果这两个颜色学到一起去了,那肯定是不行的。
那么位置编码上的高频与低频,到底是怎样产生作用的?
有两张图像,图一输入一个
A
A
A 点 和 一个
B
B
B 点坐标,希望输出颜色值
C
A
C_A
CA 和
C
B
C_B
CB,
A
A
A 和
B
B
B 是相邻的;图二也有两个点,希望输出值为
C
A
‾
\overline{C_{A}}
CA 和
C
B
‾
\overline{C_{B}}
CB。
A A A 和 B B B 的坐标在相差比较小的时候,假设 A A A 输给神经网络得到 C A C_A CA, B B B 输给神经网络产生的 C B C_B CB,因为这两个坐标是相邻的,理论上 C A C_A CA、 C B C_B CB 俩颜色差异也应该不大。
而事实上 A A A 和 B B B 的差异很小,但希望在边界上学出来的俩差距要大。这就需要神经网络学会放大信息,但这会出现的一个情况是: C A C_A CA 的值是红色, C B C_B CB 的值突然一下变成一个差异很大的值,就会导致本来是一个很连续的图像,突然蹦出来一个阶跃信号,看起来非常不自然,就像多了一个噪声,这样的感受会非常难受。但是如果都差异小,这也不是所希望的。
比如像图中的情况那样,想让 C A C_A CA 和 C B C_B CB 差距小,而 C A ‾ \overline{C_{A}} CA 和 C B ‾ \overline{C_{B}} CB 差距大。这样神经网络就没法抉择,到底学大的还是学小。但是我们又知道,神经网络内学大的还是学小的是由神经网络内训练样本所占比例决定-------谁的比例多,就会倾向于谁。在真实样本中,处于边界上的点对关系比较少,所以神经网络内大量的样本是颜色差距比较小的,这就导致了神经网络偏向于学习更平滑的信息,这样细节就丢失了。这也是为什么说,神经网络更倾向于学习低频信息。
所以怎样让神经网络既学习到高频又学习到低频信息呢?
⟶
\longrightarrow
⟶ 将
A
A
A 和
B
B
B 的位置进行编码
NeRF 中如何进行位置编码
γ ( p ) = ( sin ( 2 0 π p ) , cos ( 2 0 π p ) , ⋯ , sin ( 2 L − 1 π p ) , cos ( 2 L − 1 π p ) ) {\gamma(p)=\left(\sin \left(2^{0} \pi p\right), \cos \left(2^{0} \pi p\right), \cdots, \sin \left(2^{L-1} \pi p\right), \cos \left(2^{L-1} \pi p\right)\right)} γ(p)=(sin(20πp),cos(20πp),⋯,sin(2L−1πp),cos(2L−1πp))这样做的意义可见下图。 γ ( ⋅ ) \gamma(\cdot) γ(⋅) 越前面的维度,差异越小;越往后的维度差异越大。图中的空间位置改变了一点点,低频的位置编码特征变化并不大,而高频的位置编码特征连符号都变了。这样关注哪一维取决于场景是什么-----用低维的差异看,差距小是有道理的;用高维的差异看,差距大也是可以的。不同维度的差距可以让神经网络去选择,到底是关注大差距还是关注小差距,这样就有的选。神经网络也就具有了自适应的调节能力。
γ
(
⋅
)
\gamma(\cdot)
γ(⋅) 独立应用于待编码向量的各个维度,对于位置向量
x
\boldsymbol{x}
x 编码时
L
L
L 取
10
10
10,对于视角向量
d
\boldsymbol{d}
d 编码时
L
L
L 取
4
4
4。
经过位置编码,
x
\boldsymbol{x}
x 的编码
γ
(
x
)
\gamma(\boldsymbol{x})
γ(x) 维度为
60
60
60,
d
\boldsymbol{d}
d 的编码
γ
(
d
)
\gamma(\boldsymbol{d})
γ(d) 维度为
24
24
24。
L
L
L 取
10
10
10 的时候,因为有
sin
\sin
sin 和
cos
\cos
cos,位置向量有三维,所以
γ
(
x
)
\gamma(\boldsymbol{x})
γ(x) 的维度是
3
∗
2
∗
10
=
60
3*2*10=60
3∗2∗10=60;
γ
(
d
)
\gamma(\boldsymbol{d})
γ(d) 同理为
3
∗
2
∗
4
=
24
3*2*4=24
3∗2∗4=24。
5. 损失函数与训练策略
5.1 网络训练
难点:每条射线需要在近点与远点之间采样大量的点进行评估,导致计算量大
先验:射线上大部分区域都是空的,或者是被遮挡,对最终的颜色没有贡献(被遮挡的点的
σ
\sigma
σ 更新毫无意义,就算更新也是给出一个错误的值)
根据于此,采用了一种 coarse to fine
的层级采样策略,同时优化 coarse 网络
和 fine 网络
。在 coarse 的时候大致确定一下,在射线上,哪些点的位置、颜色、
σ
\sigma
σ 是很重要的(也就是颜色不是零和没有遮挡的),针对这些位置做下采样,将 coarse 网络
和 fine 网络
一起做更新。具体步骤如下:
-
coarse网络层次采样 N c N_c Nc 个位置,计算 w i ^ \hat{w_i} wi^ 权重;
C ( r ) = ∑ i = 1 N T i ( 1 − exp ( − σ i δ i ) ) c i ⟶ C ( r ) = ∑ i = 1 N w i c i w i = T i ( 1 − exp ( − σ i δ i ) ) {C(\boldsymbol{r})=\sum_{i=1}^{N} T_{i}\left(1-\exp \left(-\sigma_{i} \delta_{i}\right)\right) c_{i} \longrightarrow C(r)=\sum_{i=1}^{N} w_{i} c_{i} \quad w_{i}=T_{i}\left(1-\exp \left(-\sigma_{i} \delta_{i}\right)\right)} C(r)=i=1∑NTi(1−exp(−σiδi))ci⟶C(r)=i=1∑Nwiciwi=Ti(1−exp(−σiδi))其中 C ( r ) = ∑ i = 1 N w i c i C(r)=\sum_{i=1}^{N} w_{i} c_{i} C(r)=∑i=1Nwici 为色彩加权求和。- 左式中 T i ( 1 − exp ( − σ i δ i ) ) T_{i}\left(1-\exp \left(-\sigma_{i} \delta_{i}\right)\right) Ti(1−exp(−σiδi)) 被简化为了 w i w_i wi
- w i = 0 w_i=0 wi=0 的情况,要么是被遮挡了,不重要,要么是体密度为 0 0 0,也不重要
但是如果以 w i w_i wi 去采样,它是一个绝对量,只会告诉我这个位置重不重要,没有相对关系,所以需要对射线上所有位置的权重 w i w_i wi 进行归一化,这就是一个
概率密度函数
:
w ^ i = w i / ∑ j = 1 N c w j {\widehat{w}_{i}=w_{i} / \sum_{j=1}^{N_{c}} w_{j}} w i=wi/j=1∑Ncwj在概率大的地方多采样,这些位置的值和颜色对于更新是比较重要的。 -
依据权重 w i ^ \hat{w_i} wi^ 采样出 N f N_f Nf 个位置。
-
单条光线总采样数为 N c + N f N_c+N_f Nc+Nf
在该更新的地方更新,不该更新的地方不更新,这样学习的效率就比较高。
5.2 损失函数
因为要同时优化 coarse 和 fine 两个网络,损失函数为实际颜色
C
(
r
)
C(r)
C(r) 与 渲染结果
C
^
c
(
r
)
\hat{C}_c(r)
C^c(r) 和
C
^
f
(
r
)
\hat{C}_f(r)
C^f(r) 的平方误差:
L
=
∑
r
∈
R
[
∥
C
^
c
(
r
)
−
C
(
r
)
∥
2
2
+
∥
C
^
f
(
r
)
−
C
(
r
)
∥
2
2
]
{\mathcal{L}=\sum_{\mathbf{r} \in \mathcal{R}}\left[\left\|\hat{C}_{c}(\mathbf{r})-C(\mathbf{r})\right\|_{2}^{2}+\left\|\hat{C}_{f}(\mathbf{r})-C(\mathbf{r})\right\|_{2}^{2}\right]}
L=r∈R∑[
C^c(r)−C(r)
22+
C^f(r)−C(r)
22]其中,
r
\mathbf{r}
r 表示一条采样光线,
R
\mathcal{R}
R 表示所有采样光线。也就是将粗网络与标答比较+细网络与标答比较,一起放入损失函数内。