作者实现的 ngp_pl
代码还存在的两个问题:
- 收敛后的采样点数比NGP多(这导致计算量变大,渲染帧率下降);
- 有些场景会失败;
一、数据准备
代码都在 dataset
文件夹下面。
作者支持大部分算法的数据集格式,包括:
- NSVF:讲解的时候展示了
nsvf.py
这个文件,但是自己没找到它 - NeRF++
- COLMAP data
- RTMV: NeRF专用数据集,600个场景,但是因为下载贼慢,导致很少人用;
补充:创建自己的数据集可以参考 _synth_nerf_dataset_creator 这个项目。
补充说明
第一:作者默认所有相机的内参一致,如果需要支持多个相机,需要自己在数据集读取代码中添加多个K。
第二:坐标系的转换。作者把NeRF的坐标系改成了常见的相机坐标系(都是右手系,只不过z轴的方向不同,如下图左侧所示)。
第三:范围的修改。以NSVF的合成数据集(SYN)为例,合成数据集是指定了 bounding box 的范围,但是物体不一定处于坐标系正中心,所以需要使用 shift 操作使之置于原点,此外还需要使用 scale 操作把范围值归一化到 \([-0.5, 0.5]\) 内。如下图所示:
但是真实场景(real)中,box 的范围其实是不确定的,此时需要调整 scale 进行试错(trial and error)达到最好的效果(也不一定能训练成功,太大的场景需要借鉴 MipNeRF360 的 contraction 想法,把无限远的场景“投影”到比较近的地方)。
第四:采样策略。要么从所有图片中取采样点,要么只从一幅图片中取,这两种和NeRF一脉相承,分别是下面代码中的 all_images
和 same_image
两种策略。
还有一种做法是:从一幅图片中取某个小区域,因为所有点都在小区域内,所以它们之间是存在某种联系的,从而引入 perceptual similarity,这样会对最后的 Loss 计算提供帮助(因为现阶段所有的 Loss 计算都是二范数的形式,只是据说采取这种做法效果可能会更好,作者没有实现这种做法)。
二、模型构造
(一)训练
训练部分代码在 models/networks.py
网络结构:总共可以分成三大部分。xyz 输入到 HashGrid 中得到 16 维特征向量,它的第一个位元使用 \(e^x\) 作为 \(\sigma\) ;方向向量通过Spherical harmonics(一系列的正余弦计算),生成16维向量,二者 concat 后输入神经网络,得到最后的 RGB 值。
如果得到/实时更新 occupancy grid ?
以训练是否超过256步为界:前256步阶段会取得所有的格子 get_all_cells
,从cell中随机取一个点作为 xyz 投入上图中,计算其 \(\sigma\) 值,以此判断该 cell 为 0 or 1(threshold,预设值是 0.01);超过256步后,减少取样的程度,uniform 是随机取样(取所有格子的 \(\frac{1}{4}\) 出来),或者只从有东西的 cell 取 \(\frac{1}{4}\) 出来。
(二)渲染
代码在 models/rendering.py
如何进行渲染?volume Rendering
首先计算射线和 box 的交点(有现成的方法,速度其实很快)。根据训练和测试两个阶段的任务需求不同,具体做法也不同。
(1)render_ray_train
对于 syn 合成数据,box 的边长是 1,步长是 \(\frac{\sqrt{3}}{1024}\)(对角线长度);对于 real 真实场景,离光心越远,步长越大,这样既加快计算,又把关注点放在最近的物体中,范围是 \(\frac{\sqrt{3}}{1024}\) 到 \(\frac{1}{256}\)。并且,每走一步要根据 occupancy field 是否有东西,决定是否放置采样点。
(2)render_ray_test
判断射线是否和 box 有交点,逐步向前 marching,直到遇到 occupancy filed 中有东西的交点,如果没有交点那么说明这条射线不用渲染,就可以把计算资源分配给其他射线;其他射线便继续往前 marching 重复这个过程,直到走完。并且,采样点数是逐步分配、增加的。
如果 transimitance 已经很小了(几乎所有的颜色都被决定了,没必要再继续了),就可以停止渲染。early ray termination
三、定义Loss
作者在 MSE 的基础上(渲染的 RGB 和原始 RGB 的差值的平方),还引入了:
- opacity loss: 让 opacity 的值要么是 0 要么是 1,避免出现小数值,而且还能减少漂浮物。这样做是忽略了透明的物体,所以会对透明物体的效果不太好(Depth NeRF 针对透明物体做了优化);
- distortion Loss:MipNeRF360 中提出的新算法。原始NeRF会更倾向于所有的都是半透明的,累加在一起成为不透明,导致有很多没有用的“云”(漂浮物);当加了这个Loss后,最后的结果更倾向于只有表面一层,而没有后面那些半透明的云,从而减少采样、加快计算。