NeRF中的位置编码

朴素NeRF中直接采用频率变换来做位置编码,为的是避免空间相邻采样点在MLP表示中的过平滑问题。比如位置(237, 332, 198)和位置(237,332,199)这两个点作为MLP的输入,MLP可能对个位不够敏感,导致输出过平滑的问题。例如:
NeRF Ablation
由于缺乏位置编码,导致纹理相近区域的细节会丢失。
我们来看一下原文中关于Position Encoding的公式:
γ ( p ) = ( sin ⁡ ( 2 0 π p ) , cos ⁡ ( 2 0 π p ) , ⋯   , sin ⁡ ( 2 L − 1 π p ) , cos ⁡ ( 2 L − 1 π p ) ) (1) \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)\tag{1} γ(p)=(sin(20πp),cos(20πp),,sin(2L1πp),cos(2L1πp))(1)
频率编码,其实是一种广义的傅里叶变换,代码如下:

import torch

class FreqEmbedder:
    def __init__(self, multi_freq, include_input=True, input_dims=3, log_sampling=True):
        self.multi_freq = multi_freq
        self.input_dims = input_dims
        self.include_input = include_input
        self.log_sampling = log_sampling
        self.periodic_fns = [torch.sin, torch.cos]

        self.embed_fns = None
        self.out_dim = None
        self.create_embedding_fn()

    def create_embedding_fn(self):
        embed_fns = []
        d = self.input_dims
        out_dim = 0
        if self.include_input:
            embed_fns.append(lambda x: x)
            out_dim += d

        max_freq = self.multi_freq - 1
        N_freqs = self.multi_freq

        if self.log_sampling:
            freq_bands = 2. ** torch.linspace(0., max_freq, steps=N_freqs)
        else:
            freq_bands = torch.linspace(2. ** 0., 2. ** max_freq, steps=N_freqs)

        for freq in freq_bands:
            for p_fn in self.periodic_fns:
                embed_fns.append(lambda x, p_fn=p_fn, freq=freq: p_fn(x * freq))
                out_dim += d

        self.embed_fns = embed_fns
        self.out_dim = out_dim
	def embed(self, inputs):
        return torch.cat([fn(inputs) for fn in self.embed_fns], -1)

其中torch.sintorch.cos实现的就是数学意义的功能,举个例子:

import torch
pi = 3.1415926
degree_30 = pi / 6 # 30 degree

a = torch.Tensor([degree_30])
r = torch.sin(a)
print(r) # tensor([0.5000])

上面实验表明了 s i n ( 30 ° ) = 1 2 sin(30\degree)={1\over{2}} sin(30°)=21

对于频率位置编码:假设一个位置的 x 0 = 30 x_0=30 x0=30,它相邻的位置是 x 1 = 31 x_1=31 x1=31,经过 r = s i n ( x ∗ 512 ) r=sin(x*512) r=sin(x512)编码以后, x 0 x_0 x0编码后的位置为 − 0.6842 -0.6842 0.6842,而 x 1 x_1 x1编码后的位置为 0.6240 0.6240 0.6240。差距一目了然。
这里的512则表示频率,如公式(1)所示的 2 L − 1 π 2^{L-1}\pi 2L1π

当然,也如公式(1)所示,我们并不以单一的频率来表示位置编码,比如我们挨个用 [ 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 , 256 , 512 ] [1,2,4,8,16,32,64,128,256,512] [1,2,4,8,16,32,64,128,256,512]这10种频率来表示编码位置(只需用公式 r = s i n ( p ∗ x ) r=sin(p*x) r=sin(px),然后简单concat到一起)。这就完成了基本的位置编码。当然,我们还可以加入相位平移,把 c o s ( p ∗ x ) cos(p*x) cos(px)的结果也concat到一起。

所以,对于一个位置 p ( x , y , z ) p(x,y,z) p(x,y,z),我们用10种频率(如 [ 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 , 256 , 512 ] [1,2,4,8,16,32,64,128,256,512] [1,2,4,8,16,32,64,128,256,512])来编码,每种频率采用两种相位(sincos),那编码后的位置应该有 3 × 10 × 2 = 60 3\times10\times2=60 3×10×2=60维来表示原始的三维坐标向量。通常,我们会把原始的三维坐标向量也concat到一起,那么就输出 60 + 3 = 63 60+3=63 60+3=63维,直接喂到MLP里去。

众所周知,NeRF除了位置 ( x , y , z ) (x,y,z) (x,y,z)输入外,还需要输入观测角度 ( θ , ϕ ) (\theta, \phi) (θ,ϕ)。观测角度可以用ray direction来表示,通常采用三维向量。也需要进行编码,也可以统称为位置编码。我们用同样的方法,但可以少用一些频率,比如我们用 [ 1 , 2 , 4 , 8 ] [1,2,4,8] [1,2,4,8]这四种频率来编码观测角度。编码后的维度也可计算出来: 3 × 4 × 2 + 3 = 27 3\times4\times2+3=27 3×4×2+3=27
NeRF_MLP
上图就是NeRF中MLP的输入顺序,图中并没有加原始位置,所以位置编码的维度为60,而方向编码的维度为24。输入阶段一目了然~

本文内容由本人亲自整理,如有疑问请留言交流~

  • 22
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木盏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值