原始NeRF代码学习记录

本文记录学习 ECCV 2020 的论文 NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 对应的开源 tensorflow 代码
对NeRF的原始论文的解读请见博文 原始 NeRF 论文主要点细致介绍

2022-09-14 疑惑

  1. 为啥丢进 sample_pdf 函数的 bins 是 第一阶段采样点的中点?
 if N_importance > 0: # sample points for fine model
        z_vals_mid = 0.5 * (z_vals[: ,:-1] + z_vals[: ,1:]) # (N_rays, N_samples-1) interval mid points
        z_vals_ = sample_pdf(z_vals_mid, weights_coarse[:, 1:-1],
                             N_importance, det=(perturb==0)).detach()
  1. z 应该是depth(先不考虑 inverse depth)?,depth是在 z 轴的投影,下面代码却用它乘上 rays_d,这合理吗?
    xyz_coarse_sampled = rays_o.unsqueeze(1) + \
                         rays_d.unsqueeze(1) * z_vals.unsqueeze(2)
  1. 在这里插入代码片

run_nerf.py

NeRF 方法的数据集是同一场景下不同视角的若干张图像。
设一共有 N 张图像,每张图像的大小是 H × W H \times W H×W。相当于有 N × H × W N\times H\times W N×H×W个像素作为数据集。

  1. hwf 变量的含义是:图像的 heightwidthfocal length
  2. 下面的代码暗示我们,如果想要了解 函数 load_blender_data 的细节,那么就要去 load_blender.py
from load_blender import load_blender_data

images, poses, render_poses, hwf, i_split = load_blender_data(
            args.datadir, args.half_res, args.testskip)
  1. use_batching,它是一个 flag 参数。
    假如 use_batching == true,则一次从 N × H × W N\times H\times W N×H×W个像素中随机选取 B 个像素来训练。这暗示了它们可能来自不同的图像。
    假如use_batching == false,则一次先随机取1张图像出来,进而在 此张图像的 H × W H\times W H×W 个像素中选 B 个作为数据。

  2. 下面代码中 i_split 的含义是 每个 split 中各自有多少张图像,因为要把所有的数据集划分为 train / val / test。

    elif args.dataset_type == 'blender':
        images, poses, render_poses, hwf, i_split = load_blender_data(
            args.datadir, args.half_res, args.testskip)
        print('Loaded blender', images.shape,
              render_poses.shape, hwf, args.datadir)
        i_train, i_val, i_test = i_split
  1. 合成数据集知道 near,far 变量的值。
    near far的值是指 near,far 平面与相机(光心)的距离
        near = 2.
        far = 6.

在这里插入图片描述

  1. args.white_bkgd,是一个 flag 变量,如果它为 true,则会做如下处理。
    首先 images 代表图像,图像有 4 个通道 RGB α \alpha α α \alpha α代表透明度。
    假如某个像素点的透明度为1,即
    i m a g e s [ … , − 1 ] = 1 ( o p a q u e ) images[…,-1] = 1 (opaque) images[,1]=1(opaque)
    则该像素点的 RGB 值不改变。
    假如某个像素点的透明度为0,即
    i m a g e s [ … , − 1 ] = 0 ( t r a n s p a r e n t ) images[…,-1] = 0 (transparent) images[,1]=0(transparent)
    则该像素点的被纠正为白色。
    注意,images[…,-1] = 0 或者1。
        if args.white_bkgd:
            images = images[..., :3]*images[..., -1:] + (1.-images[..., -1:])
        else:
            images = images[..., :3]

如果 images[…,-1] = 0,则
i m a g e s [ . . . , : 3 ] ∗ i m a g e s [ . . . , − 1 : ] : = i m a g e s [ . . . , : 3 ] ∗ 0 images[..., :3]*images[..., -1:]:= images[..., :3]*0 images[...,:3]images[...,1:]:=images[...,:3]0
1. − i m a g e s [ . . . , − 1 : ] : = 1 − 0 : = 1 1.-images[..., -1:] := 1-0:=1 1.images[...,1:]:=10:=1
i m a g e s [ . . . , : 3 ] : = 1 images[..., :3] :=1 images[...,:3]:=1因为像素强度经过归一化,所以 1 即 255,此时为白色。

如果 images[…,-1] = 1,则 images[…, :3] 的值无变化。

  1. Kwargs变量:key word arguments
  2. grad_vars 变量,含义是:可以训练的变量。
  3. images[:, None, …] 代码的效果:(N,H,W,3) -> (N,1,H,W,3),即增加了一个维度
    等价于代码:np.expand_dims(images, 1)
  4. batchify_rays 函数的功能如下:因为 NeRF 用两个网络进行训练,分别是:coarse model 和 fine model。
    在 coarse 模型中,每条相机光线采样 64 个空间点,在fine 模型中,每条相机光线采样 192 个空间点。
    所以一条相机光线一次要采样 256 个空间点。
    训练网络一次 forward pass 要 1024个像素,对应 1024 条相机光线。
    所以一次 forward pass 要有 1024*256 := 2^18 个空间点丢给网络
    光看这些点的数据量不大,但是相应的神经网络的参数的量会很大~
    一次的计算量比较大,GPU恐怕吃不消
    所以就把它切成 chunk
    每个 chunk 对应 1024*32 个空间点的计算量
    先切分开,然后再组装
    这就是 batchify_rays 函数的功能。
    在这里插入图片描述
def batchify_rays(rays_flat, chunk=1024*32, **kwargs):
    """Render rays in smaller minibatches to avoid OOM."""
    all_ret = {}
    for i in range(0, rays_flat.shape[0], chunk):
        ret = render_rays(rays_flat[i:i+chunk], **kwargs)
        for k in ret:
            if k not in all_ret:
                all_ret[k] = []
            all_ret[k].append(ret[k])

    all_ret = {k: tf.concat(all_ret[k], 0) for k in all_ret}
    return all_ret
  1. Python里面 命名空间和字典数据结构取值的区别:
    字典数据结构:
    D = {‘a’:1 , b’:2}
    D[‘a’]=2 (取值,赋值)
    命名空间
    parser = config_parser()
    Args = parser.parser_args() # NameSpace()
    Args. (一个点,取值,赋值)
  2. N_rand 变量,含义:采样的射线个数即采样的像素点个数,这个数字在 model_c 和 model_f 中是一样的,在 lego 数据集中默认 1024,则在model_c 和 model_f 中各自采样 1024 条射线。

load_blender.py

  1. 在注释处出现了一个词 RGBA,这代表图像是 4 通道图像——RGB Alpha,其中 Alpha 代表透明度。
  2. camera_angle_x 变量的含义是 FOV in x dimension,即 fov,从本地 json 文件中获取。第三行代码是通过 fov 求 focal length。
H, W = imgs[0].shape[:2]
camera_angle_x = float(meta['camera_angle_x'])
focal = .5 * W / np.tan(.5 * camera_angle_x)

如下图所示, f , W f,W f,W分别代表 焦距和图像宽度,fov 代表 field of view。
( W 2 ) / f = tan ⁡ ( f o v 2 ) (\frac{W}{2})/f=\tan(\frac{fov}{2}) (2W)/f=tan(2fov)
f = W 2 / tan ⁡ ( f o v 2 ) f=\frac{W}{2}/\tan(\frac{fov}{2}) f=2W/tan(2fov)
这和第三行代码吻合。
在这里插入图片描述

  1. half_res ,是一个 flag变量,加入它为 true,则将原图像 resize ,注意 焦距要和图像边长大小成比例,如下列代码所示。
    if half_res:
        imgs = tf.image.resize_area(imgs, [400, 400]).numpy()
        H = H//2
        W = W//2
        focal = focal/2.

这部分代码更好的写法是指定训练图像的分辨率,比如用一个 target_wh变量指定训练图像的宽度和高度, 而不是只有让它的长宽分别缩小到原来一半的可能性。

run_nerf_helper.py

  1. multires 变量的含义是 positional encoding 中的最大频率,即论文中的 L \mathbf{L} L
    在这里插入图片描述
  2. use_viewdirs 变量,是个 flag 变量,假如 use_viewdirstrue,则随着角度( d \mathbf{d} d)不同,空间点的颜色不同,论文中 use_viewdirs 是为 true的。这点模拟了 specular reflection(反光)。有时, view变化一点点,颜色就会变很大。
  3. init_nerf_model 函数的一个形式参数:input_ch,含义为 input channel( x \mathbf{x} x)默认值为 3 。误导人,其实是 63。因为 x \mathbf{x} x 是(x,y,z)有三个分量,当 L = 10 \mathbf{L}=10 L=10,而且同时有 sin ⁡ , cos ⁡ \sin,\cos sin,cos函数,所以 x \mathbf{x} x经过Positional Encoding后的向量的长度是
    3 × 2 × 10 : = 60 3\times2\times10:=60 3×2×10:=60
    但是 NeRF 初期其实 Positional Encoding 的函数还有 f ( x ) = x f(x)=x f(x)=x,所以长度还要加 3 3 3
    所以,input_ch 在形式参数那里默认值应该赋为 63
  4. 然后针对 d \mathbf{d} d,因为 L \mathbf{L} L 4 4 4,所以 d \mathbf{d} d经过Positional Encoding后的向量长度为
    3 × 2 × 4 + 3 : = 27 3\times2\times4+3:=27 3×2×4+3:=27
    input_ch_views 在形式参数那里默认值应该是 27
def init_nerf_model(D=8, W=256, input_ch=3, input_ch_views=3, output_ch=4, skips=[4], use_viewdirs=False):
  1. alpha_out 变量,把它的名字改为 sigma_out 比较合适,因为它实际上是 volume density 的含义,在论文中,用的是 sigma- σ \sigma σ。而在论文中 alpha- α \alpha α 表达的含义是透明度。
  2. get_rays_np 函数:计算所有 rays 的原点坐标在世界坐标系下的表达和 direction 向量世界坐标系中的方向
    所有相机都有pose ,相机的原点坐标是可以直接从pose中取得的。
    direction 向量具体是指相机原点和某个像素中心所连成的射线的向量。
def get_rays_np(H, W, focal, c2w):
    """Get ray origins, directions from a pinhole camera."""
    i, j = np.meshgrid(np.arange(W, dtype=np.float32),
                       np.arange(H, dtype=np.float32), indexing='xy')
    dirs = np.stack([(i-W*.5)/focal, -(j-H*.5)/focal, -np.ones_like(i)], -1)
    rays_d = np.sum(dirs[..., np.newaxis, :] * c2w[:3, :3], -1)
    rays_o = np.broadcast_to(c2w[:3, -1], np.shape(rays_d))
    return rays_o, rays_d

在这里插入图片描述
注意,上图
( H 2 , W 2 ) (\frac{H}{2},\frac{W}{2}) (2H,2W)
的索引方式是把图像看成矩阵,先索引行,再索引列。
但是上述代码中 的 i , j i,j i,j分别对应索引列和索引行。
——————注意求 dirc 的代码是在相机坐标系下的——————
所以,求 dirs的代码的含义是:
矩阵中第 i 行第 j 列的位置,先减掉图像中心位置对应的水平位置( W 2 \frac{W}{2} 2W)再除以焦距,
然后是减掉图像中心位置对应的竖直位置( H 2 \frac{H}{2} 2H)再除以焦距,
direction 向量的 z 分量始终是 -1
——————注意求 dirc 的代码是在相机坐标系下的——————

而求 rays_d 的代码其实就是
将相机坐标系中的 视角向量 direction 转换到 世界坐标系下
如果代码用 C2W @ direction (其中@是python中矩阵相乘操作符),会更好理解,但是NeRF中的代码和这个diamante是等价的。

z_vals[: ,:-1],所有 rays 的 从near 开始的点,最后一个点的前一个点的 z 值
除了最后一个点没取,其他都取了

z_vals[: ,1:],所有 rays 的 从 near后面一个点开始,到最后一个点的 z 值
除了第一个点没取,其他都取了

z_vals_mid = 0.5 * (z_vals[: ,:-1] + z_vals[: ,1:]) # (N_rays, N_samples-1) interval mid points
相邻两个点的中点
  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

培之

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

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

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

打赏作者

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

抵扣说明:

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

余额充值