本文最后更新于16 天前,其中的信息可能已经过时,如有错误请发送邮件到chengyulong@csu.edu.cn
这一节是关于光线的生成以及光线的点的采样:
- 光线的生成
首先看总体的数据处理代码
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
的由来:
再看图像坐标系和像素坐标系的转换:
因此进而得到归一化坐标系下对应的像素坐标,此时的光心在归一化坐标系为(0,0),从而得到相应的方向向量:
在通过外参矩阵转变成世界坐标系的方向向量(模拟光线的生成)。
正常情况下会乘以一个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
反推优化采样策略下节再讲。
参考: