- 获取链接
- X
- 电子邮件
- 其他应用
- 获取链接
- X
- 电子邮件
- 其他应用
注意:对于本节内容,相机模型理论是有用的。
先概括一下:NeRF中的采样点,是通过相机孔向照片中的像素作射线,然后在射线上按照一定规则进行采样的。
相机发出射线
具体来说,我们需要知道相机射线的方向:
def make_rays(K, c2w, H, W):
x, y = np.meshgrid(np.arange(W), np.arange(H), indexing="xy")
xy = np.stack([x, y, np.ones_like(x)], axis=-1)
xy = xy.reshape(-1, 3).astype(np.float32)
通过得到的xygrid(代表像素坐标)与相机内参运算,得到相机孔为原点,对应像素为方向的,相机坐标系下的direction。
d_camera = xy @ np.linalg.inv(K).T
通过direction_camera与相机外参运算,得到世界坐标系下的direction。
d_w = d_camera @ c2w[:3, :3].T
d_w = d_w / np.linalg.norm(d_w, axis=1, keepdims=True)
对射线原点的世界坐标系下坐标求取
o_w = np.tile(c2w[:3, 3], (len(d_w), 1))
o_w = o_w.reshape(H, W, 3)
d_w = d_w.reshape(H, W, 3)
return ray_o_w.astype(np.float32), ray_d_w.astype(np.float32)
以下为相关内容解释
HW为图像的高和宽,对应的xy也就是(540, 540, 3),flat开就是(291600, 3)的shape。
相机参数解释
关于相机内参K(intrinsics)举个例子
K [[1.33211487e+03 0.00000000e+00 2.55890277e+02]
[0.00000000e+00 1.33234639e+03 2.83562715e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
对应K内参的物理意义,分别表示焦距和焦心
[[focal_x 0 center_x]
[0 focal_y center_y]
[0 0 1]]
顺便:c2w,相机外参E(extrinsics),表示相机和三维空间之间的位置关系(旋转平移等),一个相机空间的坐标可以经过外参矩阵旋转平移得到相对于世界坐标系原点的坐标。因为我们组的humannerf主要是做单目方法,所以我们可以直接用:
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
代表外参矩阵。一般情况下c2w[:3, :3]代表相机相对世界坐标系的旋转矩阵。
在该例子中,K的逆矩阵为[[ 7.50686011e-04 0.00000000e+00 -1.92093251e-01]
[ 0.00000000e+00 7.50555568e-04 -2.12829574e-01]
[ 0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dc [-0.19209325 -0.21282957 1. ]
对于射线xy (291600, 3) 中的第0个射线的方向xy0 [0. 0. 1.](像素坐标系)
得到相机坐标系下的射线方向[-0.19209325 -0.21282957 1. ]
之后通过其实没什么必要的的一步转换为世界坐标系,(因为相机E我们设定为eye矩阵)
d_w = d_camera @ c2w[:3, :3].T
d_w = d_w / np.linalg.norm(d_w, axis=1, keepdims=True)
并且把这个方向变成单位向量。
dw=dc= [-0.19209325 -0.21282957 1. ]
单位向量:dw [-0.18465416 -0.20458744 0.96127354]
对于世界坐标系原点,因为世界坐标系相对于相机坐标系没有平移,也就是
c2w[:3, 3]=[0,0,0]
因此最后得到一个ow的shape为 (291600, 3)每一个的value都是 [0. 0. 0.]。
到此为止,我们弄懂了rayo和rayd是怎么回事。也就是射线的方向和原点。
射线near与far
有了射线方向,我们还要设置边界,总不能采样到无穷远。因此需要near和far,来事先确定采样点的范围。
dist = np.sqrt(np.square(self.smpl_params["transl"][idx]).sum(-1))
datum["near"] = np.ones_like(rays_d[..., 0]) * (dist - 1)
datum["far"] = np.ones_like(rays_d[..., 0]) * (dist + 1)
在SMPL中,有trans表示人体SMPL坐标系相对于世界坐标系的偏移。这里通过平方和开方求距离。然后规定near和far初始化为人体偏移距离的正负1米。(世界坐标系和SMPL坐标系下的单位一般情况下是米)
获取采样点
我们已经知道了相机发出的射线的方向和原点,也知道射线的采样区间near和far。现在开始最后一步,获取采样点。(以下代码换了个容易看懂的库)
z_vals = self._get_samples_along_ray(N_rays, near, far)
if cfg.perturb > 0.:
z_vals = self._stratified_sampling(z_vals)
pts = rays_o[...,None,:] + rays_d[...,None,:] * z_vals[...,:,None]
@staticmethod
def _get_samples_along_ray(N_rays, near, far):
t_vals = torch.linspace(0., 1., steps=cfg.N_samples).to(near)
z_vals = near * (1.-t_vals) + far * (t_vals)
return z_vals.expand([N_rays, cfg.N_samples])
@staticmethod
def _stratified_sampling(z_vals):
mids = .5 * (z_vals[...,1:] + z_vals[...,:-1])
upper = torch.cat([mids, z_vals[...,-1:]], -1)
lower = torch.cat([z_vals[...,:1], mids], -1)
t_rand = torch.rand(z_vals.shape).to(z_vals)
z_vals = lower + (upper - lower) * t_rand
return z_vals
采样点=射线原点+射线方向*方向上距离zval
很容易看懂所以这里用gpt了:
这段代码的作用是为每个射线生成一组采样点,这些采样点沿着每条射线从近平面到远平面等距分布。具体来说:
- `_get_samples_along_ray()`函数用来生成初始的采样点,其中输入参数`N_rays`表示射线的数量,`near`和`far`分别表示近平面和远平面。函数输出`z_vals`是一个形状为`(N_rays, N_samples)`的Tensor,包含了每条射线上等距分布的采样点的深度值。
- 如果参数`cfg.perturb`的值大于0,则会调用`_stratified_sampling()`函数对`z_vals`进行扰动处理,以增加采样点的丰富性和随机性。该函数使用蒙特卡罗抽样技术,对每条射线上的所有采样点添加一个随机的扰动值。扰动值的大小由`cfg.perturb`参数控制。
- 最后,根据射线的原点和方向以及每条射线采样点的深度值,计算出每个采样点的3D坐标。这里使用了广播运算,让`rays_o`和`rays_d`的维度分别扩展一维,然后将它们与`z_vals`相乘得到每个采样点的深度,再使用加法将它们转换为3D坐标形式。得到的`pts`是一个形状为`(N_rays, N_samples, 3)`的Tensor,包含了每个采样点的3D坐标。
呼,又更完一章。
- 获取链接
- X
- 电子邮件
- 其他应用
评论
发表评论