Reference:
系列文章:
首先来看看 NeRF 开山之作的标题----NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis:
因此 NeRF 的目标也就相当清楚了:首先拍摄多视角的图像,然后要拿到这些图像对应的内外参数(可以使用 COLMAP),这时使用神经网络去学习三维场景内的辐射值(学习不同方向的光线),然后就可以在不同视角下做可视化了。
这里要明白 光线(ray)
的含义,它是由 相机/眼睛 发出,已知会穿过实际 3D 物体的一条虚拟射线,如下图所示:
图中的 Image Plane
就是我们最后想得到的图,我们要用 NeRF 找到 Image Plane 上面每个像素的 RGB。
那么怎么找呢?就是利用图中的箭头,也就是我们说的 Ray,首先使用 NeRF 找到箭头与 Volume Data
也就是 3D 物体的若干交点,然后对这些交点做积分就行了。
这里并不是眼睛能发出光,而是相反只能接收光。但因为光路是可逆的,在理解问题时,可以假设眼睛发光,如下图所示。所以,一条光线对应最终图片中的一个像素,而确定光线的关键,就是要搞清楚相机的位姿,即光线的原点,因为所有的光线都是从相机 “发出” 的。
上图表示了 ReRF 内全部的 Pipeline。假设感兴趣的三维物体场景被包含在一个虚拟的 3D 立方体内。三维空间的一个点经过一个 [R|t],就可以转到摄像机坐标系,再通过相机内参就可以转到像素平面上,这样就可以将三维空间中的颜色赋给这个值,于是整张图像就都可以得到了。但是 NeRF 中考虑的不是一个点,而是考虑这条光线上所有的点,最后生成这个像素值,这个过程就被称为体渲染技术
。
论文内有以下几个重点概念:
辐射场
为定义在空间中的函数,记录了空间中每个点的体密度
σ
\sigma
σ 以及该点每个方向上的颜色值
C
C
C(也就是辐射场内辐射值
的概念)全连接神经网络
,来记录辐射场信息。图像内包含两个流程:训练流程+推理流程。
有了这些概念,我们先描述推理流程:给我一个相机位置,就可以一个像素点一个像素点的生成一张图片。从一个像素点打一条光线出去,会与虚拟立方体空间相交获得一系列的点,通过这些点和训练好的辐射场信息,可以得到每个点的颜色和体密度。再通过体渲染将这些点合成起来,即可得到当前像素点的值。
训练流程的区别在于,获得了体渲染的颜色后需要和真实值进行比较,这就形成了一个 Loss(见图(d)),使用这个 Loss 来指导神经网络的更新,越接近越好。
上一节提到了,视线上某一点由 5 5 5 维变量表示:
这里滚转角对于射线没有意义。激光雷达也是使用的这种模型,在《自动驾驶与机器人中的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
注意在论文中使用的是 θ \theta θ 和 ? \phi ? 二维信息,但在代码内使用的还是三维向量。给定一个视线 r \boldsymbol{r} r,即 o \boldsymbol{o} o 和 d \boldsymbol{d} d 已知,给定一个 t t t,就可以得到一个点的世界坐标。
体渲染
:视线上
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)=∫tn?tf??T(t)σ(r(t))c(r(t),d)dt,?where?T(t)=exp(?∫tn?t?σ(r(s))ds)其中,
从上面公式可以知道,
σ
\sigma
σ 和
c
\boldsymbol{c}
c 都是把当前三维点信息丢进神经网络内算出来的,而
T
T
T 是通过积分得到的。看下面一个例子:
在 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即分成两个步骤:
在这种采样下,如何进行体渲染呢?
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∑N?Ti?(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? 表示相邻采样点之间的距离。
图二为 NeRF 论文内得到的结果,图三为不加视角向量得到的结果,可以看到图二中的反光比较明显,而图三中的镜面反射消失了,添加视角向量得到的结果更符合真实场景。
将图二和图四进行比较,明显可以看到图四中的高频几何和纹理细节丢失了。
这里首先来理解一下高频与低频的概念:
总的来说,高频就意味着差异。比如相邻的颜色属于不同俩物质,颜色差异比较大,如果这两个颜色学到一起去了,那肯定是不行的。
那么位置编码上的高频与低频,到底是怎样产生作用的?
有两张图像,图一输入一个
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 的位置进行编码
γ ( 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。
难点:每条射线需要在近点与远点之间采样大量的点进行评估,导致计算量大
先验:射线上大部分区域都是空的,或者是被遮挡,对最终的颜色没有贡献(被遮挡的点的
σ
\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∑N?Ti?(1?exp(?σi?δi?))ci??C(r)=i=1∑N?wi?ci?wi?=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=1N?wi?ci? 为色彩加权求和。
但是如果以
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∑Nc??wj?在概率大的地方多采样,这些位置的值和颜色对于更新是比较重要的。
依据权重 w i ^ \hat{w_i} wi?^? 采样出 N f N_f Nf? 个位置。
单条光线总采样数为 N c + N f N_c+N_f Nc?+Nf?
在该更新的地方更新,不该更新的地方不更新,这样学习的效率就比较高。
因为要同时优化 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 表示所有采样光线。也就是将粗网络与标答比较+细网络与标答比较,一起放入损失函数内。