Ref NeRF复现
Ref NeRF复现
I. Intros
科研恢复性训练。之前把CUDA加速的全并行shadow caster写完了,算是复习了CUDA、Rust以及FFI的使用。深度学习方面就以复现Ref NeRF为一个小任务。论文CVPR 2022 Best Student Honorable Mention: Ref NeRF - Structured View-Dependent Appearance for Neural Radiance Fields对反射现象进行了良好的建模。之前我一直在研究折射现象的NeRF建模,就折射建模思路而言,此文对我有很大的启发。并且个人认为,基于Ref NeRF以及mip NeRF作为框架是比较好的选择(除此之外就是要考虑如何让训练变快了,Instant NGP当然是不二选择)。当然,由于在复现过程中也遇到了许多问题,本文在阐述复现思路以及论文理解的同时,也会探讨踩过的坑。目前,Ref NeRF的复现结果还没有达到令我满意的程度,只是具备雏形,毕竟融合两篇文章的idea可能导致冲突,深度学习这种玄学就更是这样了,模型炸了都不知道从哪一个先开始。复现见 Enigmatisms/NeRF
II. 论文梳理
2.1 Ref NeRF的建模
Ref NeRF建模了表面反射,此工作比之前所看过的一篇处理反射的NeRF(NeRFReN)更有价值。个人认为,Ref NeRF可以被认为是一个不完整的光线传播模型,较好地解决了反射问题,只需要加上折射部分就可以使得NeRF处理基本的光线传播现象。本文对Ref NeRF的主要思路进行梳理,并且基于笔者对本论文的理解,在之前实现的NeRF(mip NeRF 360)基础上,增加Ref NeRF模块。
Ref
NeRF处理的首要问题是光线的反射问题,反射一般分为两种:镜面反射(specularity)以及漫反射(diffusion)。那么根据Phong反射模型(以及之前学过半吊子图形学基础),被观察到的光线(强度)将为:
- 第一项代表环境光对观察结果的影响:环境光只对结果产生常数offset
- 第二项代表了所有光源在物体上产生的漫反射。漫反射与观察方向无关,只要能观察到,就是“恒定”的。光源i
(
为其发射的某一光线的方向)发射光越是能垂直表面(与法向平行),漫反射越强。 - 第三项代表了镜面高光:不一定是完全的镜面反射(所以称之为镜面高光),可以稍显模糊,但是其强度是与视角有关的。
代表了反射光线的方向, 则代表了观察方向。两者重合(点乘结果接近1)时,反射较强。指数p用于加强衰减,可知p越大, 变化越快。也即视线与反射方向重合发生变化时,镜面高光的变化越明显。
Ref NeRF对于后两个部分都进行了建模:
作者花大力气改造原始NeRF的directional MLP,spatial
MLP则只是输出了一些新的辅助信息。directional MLP必须要能够输出真正的view
dependent颜色。作者认为:spatial
MLP也需要直接预测颜色,但是此颜色应该是受环境光、漫反射等影响产生的(由公式
2.2 directional MLP修改
首先,作者做了一个类似于mip NeRF中光锥处理的操作。mip NeRF中,为了达到mip map的效果,作者将点采样变为了光锥采样,以考虑整个光锥中的所有信息。Ref NeRF中,作者认为“反射”也不能仅仅考虑单个反射方向。由于物体实际是凹凸不平的,并非完美的镜面,表面法向量并非完全一致。可以认为,物体表面的凹凸起伏(噪声),使得反射发生了一些改变(distortion),物体表面噪声的概率分布,经过反射的数学操作后被映射成了新的分布。并且:
- 新的分布应该是各方向对称的。由于我们并不知道物体表面能有什么样的各向异性特征。
- 法向量分布的期望应该是
,也即根据求得的表面法向量以及观察方向求出的反射方向。其背后的intuition(总觉得,不应该翻译成直觉,故我这里用了一个英文单词,以表示:我觉得这里用中文表达不了那种意思)很好理解。 - 法向量的方差应该受到物体表面粗糙程度进行控制。表面越粗糙,方差越大(法向量分布较广、分散)。这很符合现实。
作者使用了一种叫做 von
Mises-Fisher(简写为vMF)的概率分布。此分布的pdf非常像高斯分布pdf,估计也有差不多的性质。个人推测作者选这个函数是为了方便进行后续的数学推导以及近似,就像mip
NeRF中,将光锥用混合高斯模型进行近似一样。有了此分布,自然需要使用积分将所有可能的反射方向考虑进去。当然不是直接求期望(求出来就是
当然作者不仅仅给directional MLP提供了
2.3 法向量估计
作者使用volume density的梯度可以估计法向量,但作者说:
- normal vectors estimated from its volume density gradient as in Equation 3 are often extremely noisy
- NeRF tends to "fake" specular highlights by embedding emitters inside the object and partially occluding them with a "foggy" diffuse surface
确实,(1)很显然,已经有很多论文做这方面的工作了,不是添加正则化项就是采用一些表面重建技术获得好的表面。(2)的话通常也是由“集中性”正则化项解决的(只不过作者的想法不太一样)。
问题(1)的解决(prediction penalty):通过spatial MLP预测某一点的法向量
,与梯度法向量 求MSE: 由于 相对比较光滑,而 是对不光滑的density求的一阶导,就更不光滑了。用光滑数据帮助不光滑的梯度进行学习,可以起到对梯度的光滑作用。但为什么 比较光滑?这是作者说的,其实我比较纳闷,毕竟这玩意是学出来的。我的理解是:可以认为,此处需要网络也同时将 学出来(利用 作为监督)。但是网络的表示能力有限,特别是参数较少时,只能达到对 低频部分的学习。问题(2)的解决(orientation loss):作者设计了一个正则化项:此正则化惩罚反向法向量,也即“背面”。但也不是盲目对背面进行惩罚,毕竟volume sampling过程中是可以在有效的背面进行采样的,故用下式:
其中 为点weight值, 为光线方向。此处也即惩罚 法向量与光线方向相同部分。并且与weight有关,这就说明:被遮挡的有效背面不会被影响(weight低),而fake surface(在半透明surface后的embedded emitter表面)将会被惩罚(其density衰减方向与光线方向一致)
作者自己也解释了一下,为什么这两个loss结合在一起就work了:网络对于法向量的预测(
偏离 。那么此时,prediction penalty较大,网络偏向于使得两者相等,则可以达到平滑梯度的目的。 很接近 。那么此时,由于 ,我们可以认为公式 作用在 上。这也就达到了惩罚的目的。
2.4 扩展知识
2.4.1 一些物理概念
(1)能量,功率与辐射通量
能量的单位一般采用(J),而功率则是单位时间内的能量(J/s,定义为W),此概念与通量(flux)是同一个概念。与接收能量的面积没有关系。
(2)辐照度与辐出度
辐照度(irradiance)与辐射度(radiosity)是两个类似的概念,与面积有关。单位面积的通量(或者说,功率),单位是:
(3)立体角与辐射强度(intensity)
立体角表达的物理意义是:1立体角对应在单位球上截取的大小为
(4)辐射率(radiance)
搞NeRF也有一段时间了,还没有搞清楚radiance的意义。此前只是将radiance当作是观察到的物体的颜色,而现在这里将对辐射率的物理含义进行介绍。上文已经介绍了辐射强度的概念,并且可知,辐射强度与空间特性无关,只与光源有关,而我们又希望获得某一光源在空间中某一位置可产生的影响。则定义辐射率:单位时间、单位面积、单位立体角通过的光能。也即单位面积、单位立体角的功率。单位是:
2.4.2 光场
四维光场是否只是一个近似?四维光场认为,在empty space内部,radiance(也就是产生的RGB值)是恒定的。也就是说,无论我从此光线上的哪一点开始积分,最后得到的RGB值都是一样的,此RGB值与光线上的积分距离(或是深度)无关。那么这样可以从五维的光场建模中,去掉一个维度,实际则为四维的。个人更愿意将“四维”理解为一个五维空间中的四维流形,因为此深度维度并非五维度基中之一,也不能用其线性组合描述。理解了四维光场的由来,我们回头检查一下假设:空域内部radiance为常数。正常情况下:无半透明物体或者介质,这是成立的。引入半透明物体与介质后(比如混浊溶液,胶体,雾),个人认为此假设不成立,那么表四光场还是应该使用五维函数。
2.5 球谐函数的使用
打开维基百科,查之。woc,我看到了什么。总之很复杂了,本人本科所学的数学知识不够用(或者说是遗忘了)。关于球谐函数本身的理解,个人的看法是:类似于傅里叶变换,展开成基函数的线性组合(这些基函数独立分析时其实形式较为简单,但组合起来就能表示非常丰富的内容),并且不像泰勒展开(泰勒展开的物理意义不甚好理解),球谐函数有频率概念,并且三维空间中的球谐函数仍然很好理解,非常具有物理直观性。如果想要知道如何初步理解,这里推荐【知乎:球谐函数介绍(Spherical
Harmonics)】,我就不赘述了。下面只给出结论,实球谐函数的形式如下:
1 | # device 某个传入函数的张量t的t.device |
此后,根据lookup或者在线求解公式
- NeRF的positional encoding,不同的相位(
)以及不同的频率是串行计算最后concatenate的 - 大可以自定义CUDA算子,写一个forward再写一个backward函数。forward好写而backward不好写罢了(这玩意显然是有闭式解的,推起来比较麻烦),故CUDA自定义算子:全并行但费脑子(哈,而且还不一定有pytorch快,例如之前我用thrust的sort库对比pytorch的张量sort,前者就比后者慢),pytorch实现弱并行,但不费脑子。
球谐函数的使用也说明了,我们在处理方向时,需要将三维的方向转化为二维的方向角(才能喂给球谐函数,当然,球谐函数确实有笛卡尔坐标系表示,但这不是我们的讨论范畴)。下面,笔者简单讨论一下文中球谐函数在vMF分布下的期望求解(论文附录部分)。作者要证明:
恰好是 的方位角(中的俯仰角 ) 中的 原先需要被提出来,但由于其行列式为1(旋转矩阵嘛),不会影响最后函数输出的单个值的结果(注意,对于每一个 而言,公式 的输出都是一个值),故可以省略
此后,根据球谐函数的性质(比如展开为Wigner D
Matrix表示以及其正交性),我们最后可以将公式

图中的黑色圆圈就是“积分对象”:可以看做球体的上半球表面是由无数个圆环组成的,对应了角度为
但是,选择实函数形式的球谐并不是一个很好的选择。首先,公式
- 首先Pytorch支持虚数运算。这里吐槽一下,Pytorch支持的虚数运算基于
torch.complex64
类型,一看到64
就知道怎么回事了,运算量可能等同于float32运算(两个float32存在一块),在使用auto mixed precision时,complex32将可能产生一系列的问题,很多算子都没有对complex32有支持。而假设我们使用APEX库的O2等级优化,所有的float32都会变成float16,那么这里参与运算的复数就会产生一系列未定义算子的问题。(什么ComplexHalf未定义这些算子啊之类的报错) - SH在这就是用作encoding,所以其实只需要将结果的实部、虚部取出来作为encoding的成分就好了。可以认为,原来的positional encoding是用了正交相位的两种基底进行表示,而SH其实也是正交的两种基底(实与虚在虚平面上是正交的)进行表示。
III. 复现细节
3.1 值得一提的点
其实,没什么好说的。根据Ref NeRF论文图,复现的内容如下:
首先修改spatial network,在spatia network之后追加三个head。三个head的输入就是spatial network的输出:
opacity & roughness head: 由于τ(density)与ρ(roughness)都是没有输出范围限制的(但是都需要大于0,故ρ之后与τ类似,单独处理即可)。注意roughness与opacity都需要经过bias之后再通过softplus激活(软ReLU)
color & tint head:
都是[0, 1] 之间的值,故输出直接变6维。color与tint都使用sigmoid进行激活,不过bias不同。normal head:预测法向量,三维,需要进行normalize,不需要任何激活
需要绕一下的部分是法向量。法向量的来源有两个:(1)spatial network计算的density,可以对位置求导。Pytorch中,要实现对位置求导需要使得输入的位置向量可导:
1 | fine_pos.requires_grad = True # 此步必要 |
另一个来源就是spatial network之后的normal head,预测。预测的结果在经过正则化(orientation loss)以及预测误差(与density grad的差别)backward之后,应当可以学出较好的结果。
此外注意,directional network变深了。可以认为directional network有spatial network一样的结构(包括8层256 hidden units的MLP,并且有skip connection)。
3.2 一些坑
由于笔者复现Ref NeRF基于之前实现的Enigmatisms/NeRF,这个repo中实现的NeRF并非原始NeRF,而是mip NeRF 360(部分),主要包含这样的特性:
- 本repo实现了mip NeRF 360中的proposal network distillation。也即:coarse-to-fine的stratified sampling并没有使用。stratified sampling非常慢(由于coarse network需要forward所有点,fine network也要forward),proposal network则是用浅MLP(5层),forward点后直接输出预测的density,再利用fine network输出的density(weight)进行监督。这样就能完成从fine network到proposal network的蒸馏。此蒸馏的实现较为复杂(weight bound的计算),这里不赘述。我也不想单开一篇博客讨论。
- 使用了自动混合精度(amp),可以选用apex或者torch.native_scaler
proposal network着实坑了我一把。复现完成(第一版)之后,跑了一下shinny blender的ball数据集:
如上图第一列:很多噪点。这些噪点是什么?一开始我也不敢下结论,毕竟不知道是不是自己的ref nerf复现有问题。于是我将深度以及normal都可视化了出来(后两列)。可见,深度图中也存在很多空洞,normal就不说了吧(事实上,normal学习一直有问题,normal本身就比较难学)。空洞产生的可能原因有两个:
- density学习有问题,有些位置density非常低
- 采样(不管是训练还是测试时)位置不对,采样在了一些空的位置
于是我开了一个新的分支,在此分支上我将proposal network删掉了,使用原始NeRF的coarse-to-fine框架进行测试。对比图如下图所示:左边为原始proposal network存在时,训练60轮之后的结果,而右边则为原始NeRF训练40轮之后的结果。可以看出,原始NeRF框架下的采样是(至少相对上是)没问题的。但笔者并不想放弃proposal network(个人觉得这个方法比较优雅),故笔者将proposal network进行了小的改动:
原始prop net输入coarse points之后,输出density,此后coarse points将会被弃用。根据density计算的weight,将指导inverse sampling,fine network的输入只为inverse sampling的结果(也就是说,集中在weight高的地方)。假设,prop net计算的density有缺陷,也即weight有缺陷,在实际的表面附近weight很小,在空域中weight大,那么inverse sampling可能无法在此条光线上采到有效的点。
很简单,就是复用coarse points,将coarse depths(采样的长度)与inverse sampling的采样长度(fine depths)进行拼接,排序。但其实在实现中,要考虑proposal network的weight bound计算。啊,这一步很复杂。可以这么说:
- proposal network需要预测每一个采样点(fine采样点)的weight上界(weight bound)。上界如何计算?两个fine采样点之间会存在一个采样区间,此区间将会与coarse采样的区间重合,则此fine采样区间的weight上界应该是所有与之有交集的coarse区间weight之和。
- coarse points合并到fine points相当于修改了fine采样区间。那么就需要计算更多区间交集。这里我不赘述方法,详见:NeRF/coarseFineMerge以及NeRF/getBounds
这样修改,也就使得每条光线上,既有均匀采样的部分(保证了coverage),又使得density大的部分可以有更多采样点。可能有人觉得,不就是增加了一些采样点吗?这样为什么能保证proposal network的学习是正确的呢?很简单,网络不仅有更加充足的输入,fine network提供给proposal network的监督也更加充足了(见【修改后的Proposal Network】部分,其中我们说到“更多的区间交集”)。
无proposal net 训练40轮(4k次) | 有proposal net 训练60轮(6k次) |
---|---|
![]() |
![]() |
修改后的方法,只训练20轮输出后的结果就让我觉得肯定是改对了,如上图所示。接下来将展示【未完成训练】时的训练结果。由于原始Ref
NeRF使用
从上图看出,虽然训练应该远没有结束(这才不到7小时),PSNR曲线的上升能力属实堪忧。笔者感觉PSNR无法上升至论文中的29,个人认为可能的问题仍然是出在proposal network上。目前正在尝试一种改进的方法。
效果大概就是这样(如下图所示)。从左到右分别是:RGB,深度与法向量。emm,法向量大概是可视化错了。不过学的应该也有一些问题,在某些视角能看到头盔上的条纹对应的法向量与其他位置不一致。PSNR为19,还是太模糊了一点。
Reference
https://en.wikipedia.org/wiki/Spherical_harmonics
https://en.wikipedia.org/wiki/Associated_Legendre_polynomials
扩展阅读: https://dellaert.github.io/NeRF22/