nerf_factory代码笔记(四)

nerf_factory代码笔记(四)

这一节是关于光线的生成以及光线的点的采样

  1. 光线的生成

    首先看总体的数据处理代码

     def split_each(
            self,
            _images,
            _normals,
            render_poses,
            idx,
            dummy=True,
        ):
       # 这里是如果是fit or test render_poses=None,predict话_images和_normals是None
            images = None
            normals = None
            radii = None
            multloss = None
    
            if _images is not None:# 读取训练的数据:外参内参以及图片大小
                extrinsics_idx = self.extrinsics[idx]
                intrinsics_idx = self.intrinsics[idx]
                image_sizes_idx = self.image_sizes[idx]
            else:  # 进行推理的时读取渲染姿势
                extrinsics_idx = render_poses
                N_render = len(render_poses)
                intrinsics_idx = np.stack([self.intrinsics[0] for _ in range(N_render)])
                image_sizes_idx = np.stack([self.image_sizes[0] for _ in range(N_render)])
    
            _rays_o, _rays_d, _viewdirs, _radii, _multloss = batchified_get_rays(
                intrinsics_idx,
                extrinsics_idx,
                image_sizes_idx,
                self.use_pixel_centers,
                self.load_radii,
                self.ndc_coord,
                self.ndc_coeffs,
                self.multlosses[idx] if self.multlosses is not None else None,
            )
    
            device_count = self.num_devices
            n_dset = len(_rays_o)
            dummy_num = (
                (device_count - n_dset % device_count) % device_count if dummy else 0
            ) # 计算添加虚拟数据的数量,从而最终使得每个gpu均匀获得数据
            #  进行数据填充
            rays_o = np.zeros((n_dset + dummy_num, 3), dtype=np.float32)
            rays_d = np.zeros((n_dset + dummy_num, 3), dtype=np.float32)
            viewdirs = np.zeros((n_dset + dummy_num, 3), dtype=np.float32)
    
            rays_o[:n_dset], rays_o[n_dset:] = _rays_o, _rays_o[:dummy_num]
            rays_d[:n_dset], rays_d[n_dset:] = _rays_d, _rays_d[:dummy_num]
            viewdirs[:n_dset], viewdirs[n_dset:] = _viewdirs, _viewdirs[:dummy_num]
    
            viewdirs = viewdirs / np.linalg.norm(viewdirs, axis=1, keepdims=True)
    
            if _images is not None:
                images_idx = np.concatenate([_images[i].reshape(-1, 3) for i in idx])
                images = np.zeros((n_dset + dummy_num, 3))
                images[:n_dset] = images_idx
                images[n_dset:] = images[:dummy_num]
    
    		......
    
            rays_info = {
                "rays_o": rays_o,
                "rays_d": rays_d,
                "viewdirs": viewdirs,
                "images": images,
                "radii": radii,
                "multloss": multloss,
                "normals": normals,
            }
    
            return RaySet(rays_info), dummy_num
    
    

    具体的获取光线的代码为batchified_get_rays

    这里是具体的代码:

    def batchified_get_rays(
        intrinsics,
        extrinsics,
        image_sizes,
        use_pixel_centers,
        get_radii,
        ndc_coord,
        ndc_coeffs,
        multlosses,
    ):
    
        radii = None
        multloss_expand = None
    
        center = 0.5 if use_pixel_centers else 0.0
        #  为每个图片创建网格坐标
        mesh_grids = [
            np.meshgrid(
                np.arange(w, dtype=np.float32) + center,
                np.arange(h, dtype=np.float32) + center,
                indexing="xy",
            )
            for (h, w) in image_sizes
        ]
    
        i_coords = [mesh_grid[0] for mesh_grid in mesh_grids]
        j_coords = [mesh_grid[1] for mesh_grid in mesh_grids]
    #  计算x方向的方向向量和y方向的方向向量 (点的位置-光心)/f  (转换到了相机坐标系下)
        dirs = [
            np.stack(
                [
                    (i - intrinsic[0][2]) / intrinsic[0][0],
                    (j - intrinsic[1][2]) / intrinsic[1][1],
                    np.ones_like(i),
                ],
                -1,
            )
            for (intrinsic, i, j) in zip(intrinsics, i_coords, j_coords)
        ]
    #  这里的blender的外参矩阵是c2w矩阵,因此第四列代表相机的光心在世界坐标系的位置
        rays_o = np.concatenate(
            [
                np.tile(extrinsic[np.newaxis, :3, 3], (1, h * w, 1)).reshape(-1, 3)
                for (extrinsic, (h, w)) in zip(extrinsics, image_sizes)
            ]
        ).astype(np.float32)
    #  通过爱因斯坦求和,由相机坐标系转到世界坐标系,成为世界坐标系中的方向向量
        rays_d = np.concatenate(
            [
                np.einsum("hwc, rc -> hwr", dir, extrinsic[:3, :3]).reshape(-1, 3)
                for (dir, extrinsic) in zip(dirs, extrinsics)
            ]
        ).astype(np.float32)
    
        viewdirs = rays_d
        viewdirs /= np.linalg.norm(viewdirs, axis=-1, keepdims=True)#  单位方向向量,表示方向但不具有尺度
    
        if ndc_coord: #  是否使用归一化坐标系,即ndc坐标系
            rays_o, rays_d = convert_to_ndc(rays_o, rays_d, ndc_coeffs)
    
        ......
    
        return rays_o, rays_d, viewdirs, radii, multloss_expand
    
    

关于这里维度的变换说一下特殊的说明,方便大家的理解。

首先我们看内参矩阵,观察dirs的由来:
K = [ f x 0 c x 0 f y c y 0 0 1 ] K=\begin{bmatrix} f_x&0&c_x\\ 0&f_y&c_y\\ 0&0&1\\ \end{bmatrix} K= fx000fy0cxcy1
再看图像坐标系和像素坐标系的转换:
[ u v 1 ] = K = [ f x 0 c x 0 f y c y 0 0 1 ] [ x y 1 ] \begin{bmatrix} u\\ v\\ 1\\ \end{bmatrix}=K=\begin{bmatrix} f_x&0&c_x\\ 0&f_y&c_y\\ 0&0&1\\ \end{bmatrix}\begin{bmatrix} x\\ y\\ 1\\ \end{bmatrix} uv1 =K= fx000fy0cxcy1 xy1

u = f x ∗ x + c x , v = f y ∗ y + c y u=f_x*x+c_x,\quad v=f_y*y+c_y u=fxx+cx,v=fyy+cy

因此进而得到归一化坐标系下对应的像素坐标,此时的光心在归一化坐标系为(0,0),从而得到相应的方向向量
[ x − 0 y − 0 1 ] = [ x y 1 ] \begin{bmatrix} x-0\\ y-0\\ 1\\ \end{bmatrix}=\begin{bmatrix} x\\ y\\ 1\\ \end{bmatrix} x0y01 = xy1
在通过外参矩阵转变成世界坐标系的方向向量(模拟光线的生成)

正常情况下会乘以一个Z即深度信息,得到具体的点

所以但这里没加入具体深度信息就指的是这方向是所有的点,即为整条光线,非常合理。

具体参考:

请添加图片描述请添加图片描述

然后就是沿着光线采样,这里的策略是先进行随机采样。然后通过粗网络得出来的值进行反推优化采样方式

随机采样:

def sample_along_rays(
    rays_o,
    rays_d,
    num_samples,
    near,
    far,
    randomized,
    lindisp,
):
    bsz = rays_o.shape[0]  # 读出batchsize
    t_vals = torch.linspace(0.0, 1.0, num_samples + 1, device=rays_o.device) #线性均匀取点 
    if lindisp:
        t_vals = 1.0 / (1.0 / near * (1.0 - t_vals) + 1.0 / far * t_vals)
    else:
        t_vals = near * (1.0 - t_vals) + far * t_vals  #相当于是从near开始,far结束的均匀样点

    if randomized:  # 随机采样
        mids = 0.5 * (t_vals[..., 1:] + t_vals[..., :-1]) # 取点的中间值
        upper = torch.cat([mids, t_vals[..., -1:]], -1)  #  包含最大的那个点
        lower = torch.cat([t_vals[..., :1], mids], -1)  #  包含最小的那个点
        t_rand = torch.rand((bsz, num_samples + 1), device=rays_o.device)
        t_vals = lower + (upper - lower) * t_rand  #  最后进行随机采样
    else:
        t_vals = torch.broadcast_to(t_vals, (bsz, num_samples + 1))

    coords = cast_rays(t_vals, rays_o, rays_d)  # 最后进行计算世界坐标系下的具体坐标

    return t_vals, coords

反推优化采样策略下节再讲

参考:

https://blog.csdn.net/qq_40918859/article/details/122271381

https://zhuanlan.zhihu.com/p/593204605

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值