一、NeRF 介绍 1. 背景传统计算机图形学技术通过几十年倒退,次要技术路线曾经绝对稳固。随着深度学习技术的倒退,新兴的神经渲染技术给计算机图形学带来了新的时机,受到了学界和工业界的宽泛关注。神经渲染是深度网络合成图像的各类办法的总称,各类神经渲染的指标是实现图形渲染中建模和渲染的全副或局部的性能,基于神经辐射场(Neural Radiance Field, NeRF)的场景三维重建是近期神经渲染的热点方向,指标是应用神经网络实现新视角下的 2D 图像生成,在 20 和 21 年的 CVPR、NeuIPS 等各大 AI 顶会上,咱们能够看到几十、上百篇相干的高水平论文。2. 网络结构 NeRF 应用多层感知机(Multilayer Perceptron,MLP)来重建三维场景,也就是去拟合空间点的色彩散布和光密度散布的函数。NeRF 的网络结构如图 1 所示,网络的输出是采样点对应的空间坐标和视角,输入是采样点对应的密度和 RGB 值。因为色彩 \boldsymbol{c}c 和光密度 \sigmaσ 在空间中并不是平滑的,变动是比拟激烈的,这意味着函数存在很多高频的局部,让模型去示意这种函数比拟艰难,所以 NeRF 通过 positional encoding,对输出的 \boldsymbol{r},\boldsymbol{d}r,d 进行编码、升维,从而可能让模型更好地学出场景的一些细节局部,具体映射形式如下所示,该映射将标量 pp 映射成一个 2L+12L+ 1 维的向量:\boldsymbol{\gamma}(p)=\left[p,\sin(2^0\pi p),\cos(2^0\pi p),\cdots,\sin(2^{L-1}\pi p), \cos(2^{L-1}\pi p)\right]γ(p)=[p,sin(20πp),cos(20πp),⋯,sin(2L−1πp),cos(2L−1πp)]NeRF 采纳的 MLP 残缺架构如下图所示:
- 光线步进法 NeRF 应用 MLP 隐式重建三维场景时的输出是采样点的位姿,NeRF 的指标是实现 2D 新视角图像生成,那么要怎么失去采样点的位姿,又怎么应用重建失去的 3D 密度和色彩呢,失去新视角下的 2D 图像?为了解决这两个问题,NeRF 应用了光线步进(Ray Marching)这一经典办法,设 \boldsymbol{o}o 代表相机原点 O_cOc在世界坐标系中的地位,\boldsymbol{d}d 代表射线的单位方向矢量,tt 代表从 O_cOc登程,沿射线方向前进的间隔。应用光线步进法时,2D 图像的每个像素对应于一条射线,每条射线上的任意地位能够示意为 \boldsymbol{r}(t)=\boldsymbol{o}+t\boldsymbol{d}r(t)=o+td,对这些射线进行采样(具体应用下文介绍的随机采样和重要性采样)即可失去采样点的位姿,对这些射线上的采样点进行积分(具体应用下文介绍的体绘制办法)即可失去 2D 像素的 RGB 值。
- 随机采样和重要性采样 NeRF 应用随机采样和重要性采样联合的形式在光线步进法生成的射线上进行采样,这是因为空间中的物体散布是稠密的,一条射线上可能只有很小的一段区域是对最终渲染起作用的,如果用平均采样会节约很多采样点,网络也难以学到整个间断空间中的散布,所以采纳 coarse to fine 的思维,构建粗采样网络和细采样网络能够更好的对空间进行采样。随机采样:将射线从近场到远场的范畴 t_n,t_f 平均划分成 N_cNc个区间,在每段区间内随机取一个点,将其空间坐标 \boldsymbol{r}r 和空间视角 \boldsymbol{d}d 输出粗采样网络,失去粗采样网络预测的该空间点的 RGB\sigmaRGBσ。重要性采样:粗采样网络的输入中包含一条射线上所有点的权重 w_iwi(具体将在下一节解释),将其归一化后作为采样区间的概率密度函数 PDF,依照概率密度函数随机采样 N_fNf个点,与后面分段平均采样的 N_cNc个点合并后输出细采样网络。NeRF 将粗采样网络和细采样网络的渲染后果(2D 像素点的 RGB 值),别离与 ground truth 计算均方误差,将两者之和作为总的 loss,来同时训练两个网络。
- 体绘制原理失去采样点的密度和 RGB 值之后,NeRF 应用计算机图形学中经典的体绘制技术(Voulume rendering)将 3D 采样值渲染至 2D 立体,具体原理如下。光在介质中的衰减满足以下微分方程:dL=-L\cdot \sigma \cdot dtdL=−L⋅σ⋅dt 其中 LL 为光强,\sigmaσ 为衰减系数,其解为:L=L_0\cdot \exp(-\int{\sigma}dt)L=L0⋅exp(−∫σdt)假如:1) 空间点的色彩 \boldsymbol{c}=[R,G,B]^Tc=[R,G,B]T 和眼帘方向 \boldsymbol{d}d 无关;2) 空间点的光密度 \sigmaσ 和眼帘方向 \boldsymbol{d}d 无关。因为察看到的物体色彩会受到察看视角的影响(比方金属反射面),而光密度是由物体材质所决定。这一点假如也体现在了 NeRF 的网络结构当中。依据光在介质中的衰减方程和体绘制原理,对于一个像素,其渲染色彩为 \boldsymbol{C}=\int_{t_{n}}^{t_{f}} T(t) \sigma(\boldsymbol{r}(t)) c(\boldsymbol{r}(t), \boldsymbol{d}) dtC=∫tntfT(t)σ(r(t))c(r(t),d)dt 其中,T(t)=\exp(-\int_{t_n}^{t}{\sigma(\boldsymbol{r}(s))}ds)T(t)=exp(−∫tntσ(r(s))ds)代表 t_n,t 内的累积透光率,\sigma(\boldsymbol{r}(t))dtσ(r(t))dt 代表间隔微元 dtdt 内的光强衰减率,等效为 \boldsymbol{r}(t)r(t)处的反射率。再对积分式进行离散化解决,使之适于计算机解决:沿着射线取 NN 个点,在每个点 ii 所代表的区间长度 \delta_iδi内,\sigma, \mathbf{c}σ,c 视作常数,失去渲染失去的 2D 像素 RGB 值表达式为 \hat{C}(\mathbf{r})=\sum_{i=1}^{N}{T_i(1-\exp(-\sigma_i \delta_i))\mathbf{c_i}}C^(r)=i=1∑NTi(1−exp(−σiδi))ci其中,累计透光率为 T_i=\exp(-\sum_{j=1}^{i-1}\sigma_j \delta_j)Ti=exp(−∑j=1i−1σjδj)用体绘制解决失去的 2D 像素 RGB 值,可用于计算 loss,也可用于生成最终的 2D 新视角图像。同时,也能够失去如下两个量:\alpha_i=1-\exp(-\sigma_i \delta_i)αi=1−exp(−σiδi),为第 ii 个区间的不透明度。因而,T_i=\prod_{j=1}^{i-1}{(1-\alpha_i)}Ti=∏j=1i−1(1−αi),为前 i -1i−1 个区间的累积透明度;w_i=T_i\cdot \alpha_iwi=Ti⋅αi,为第 ii 个点的色彩对渲染色彩的贡献度,也就是权重,可用于计算粗采样网络失去的 PDF。6. NeRF 的总体流程综上所述,NeRF 实现场景新视角合成的工作流程如下图所示。输出多视角图片(包含像素坐标、像素色彩),以及相机内参、位姿等数据;依据光线步进法产生射线,用随机采样和重要性采样失去空间采样点的坐标;将空间采样点的坐标以及射线的视角输出含有地位编码的 NeRF,失去网络对于空间点 RGB\sigmaRGBσ 值的预测。依据空间点的 RGB\sigmaRGBσ 值,用体绘制原理,渲染出射线对应的二维像素点的 RGBRGB。将预测、渲染失去的像素点的 RGBRGB,与 ground truth 做 MSE loss,训练神经网络。
二、代码流程介绍我的项目地址:等算子欠缺后公布程序实现环境:mindspore1.6.1+CUDA11.1。整体试验流程:
-
数据筹备 NeRF 的训练,除了对同一场景拍摄的多视角照片外,还须要相机的内参和每张照片的位姿。后者是无奈通过间接测量失去的,因而须要应用肯定的算法来获取。COLMAP 是一款通用的静止复原构造 (SfM) 和多视图平面 (MVS) 软件,可用于点云重建。咱们用 COLMAP 获取相机的内参和每张照片拍摄时的位姿,并依据 3D 特色点坐标,预计相机成像的深度范畴,从而确定远、近立体,也就是边界。操作步骤如下:装置 COLMAP,官网地址:https://demuc.de/colmap/ 关上软件。点击 File—New project,导入照片文件夹 images,并在其所在目录下创立一个 database 文件。
点击 Processing—Feature extraction,提取照片中的特色点。点击 Processing—Feature matching,实现特色点匹配。点击 Reconstruction—Start reconstruction,实现稠密重建。点击 Reconstruction—Dense reconstruction,点击 Undistortion,输入稠密重建的后果,包含相机内参、照片位姿、三维特色点信息的二进制文件。
COLMAP 生成的数据为二进制文件,须要将数据从新保留成 npy 格局的文件 poses_bounds.npy,便于后续数据的加载。操作步骤如下:运行 img2pose.py 文件,运行前须要配置形参为 dense 文件夹所在的目录。
运行后,在相应文件夹下生成 pose_bound.npy 文件,数据预处理工作实现。pose_bound.npy 文件内容:蕴含相机的内参、每幅图像的位姿(从相机坐标系到世界坐标系的变换矩阵),以及依据照片中的三维特色点信息计算出的每幅照片的成像深度范畴。此外,原始的拍摄照片像素数太多,无奈进行计算,须要当时对图片进行下采样,缩小计算量,此处采纳 8 倍降采样。2. 模型搭建 2.1 骨干网络模块首先是骨干模型的搭建。模型输出:空间三维坐标在地位编码后的高维向量;模型输入:相应三维坐标点处的 的预测值。模型的具体架构参考原理局部。这里须要留神模型权重的初始化,它对训练时收敛的快慢影响很大。这里,采纳权重和偏置都在 (-\sqrt{k},\sqrt{k})(−k,k) 内均匀分布,其中 k =1/\text{in-channels}k=1/in-channels,实测这种初始化形式对于 NeRF 的收敛是有帮忙的。class LinearReLU(nn.Cell):
def __init__(self, in_channels, out_channels):super().__init__() self.linear_relu = nn.SequentialCell([ nn.Dense(in_channels, out_channels, weight_init=Uniform(-math.sqrt(1. / in_channels)), bias_init=Uniform(-math.sqrt(1. / in_channels))), nn.ReLU()])
def construct(self, x):
return self.linear_relu(x)
class NeRF(nn.Cell):
def __init__(self, D=8, W=256, input_ch=3, input_ch_views=3, skips=[4], use_viewdirs=False):
super(NeRF, self).__init__()
self.D = D
self.W = W
self.input_ch = input_ch
self.input_ch_views = input_ch_views
self.skips = skips
self.use_viewdirs = use_viewdirs
self.pts_layers = nn.SequentialCell([LinearReLU(input_ch, W)] +
[LinearReLU(W, W) if i not in self.skips else LinearReLU(W + input_ch, W)
for i in range(D - 1)]
)
self.feature_layer = LinearReLU(W, W)
if use_viewdirs:
self.views_layer = LinearReLU(input_ch_views + W, W // 2)
else:
self.output_layer = LinearReLU(W, W // 2)
self.sigma_layer = nn.SequentialCell([
nn.Dense(W, 1,
weight_init=Uniform(-math.sqrt(1. / W)),
bias_init=Uniform(-math.sqrt(1. / W))) if use_viewdirs
else nn.Dense(W // 2, 1,
weight_init=Uniform(-math.sqrt(1. / (W // 2))),
bias_init=Uniform(-math.sqrt(1. / (W // 2)))),
])
self.rgb_layer = nn.SequentialCell(
nn.Dense(W // 2, 3,
weight_init=Uniform(-math.sqrt(1. / (W // 2))),
bias_init=Uniform(-math.sqrt(1. / (W // 2)))),
nn.Sigmoid())
def construct(self, x):
pts, views = mnp.split(x, [self.input_ch], axis=-1)
h = pts
for i, l in enumerate(self.pts_layers):
h = self.pts_layers<i>(h)
if i in self.skips:
h = mnp.concatenate([pts, h], -1)
if self.use_viewdirs:
sigma = self.sigma_layer(h)
feature = self.feature_layer(h)
h = mnp.concatenate([feature, views], -1)
h = self.views_layer(h)
rgb = self.rgb_layer(h)
else:
h = self.feature_layer(h)
h = self.output_layer(h)
sigma = self.sigma_layer(h)
rgb = self.rgb_layer(h)
outputs = mnp.concatenate([rgb, sigma], -1)
return outputs
2.2 地位编码模块地位编码须要将输出的三维坐标映射到高维,因而须要构建一个由 \sin,\cossin,cos 函数形成的列表。对于每个输出,进行编码函数的遍历,将输入后果拼接成高维。class Embedder:
def __init__(self, **kwargs):
self.kwargs = kwargs
self.create_embedding_fn()
def create_embedding_fn(self):
embed_fns = []
d = self.kwargs['input_dims']
out_dim = 0
if self.kwargs['include_input']:
embed_fns.append(lambda x: x)
out_dim += d
max_freq = self.kwargs['max_freq_log2']
N_freqs = self.kwargs['num_freqs']
if self.kwargs['log_sampling']:
pow = ops.Pow()
freq_bands = pow(2., mnp.linspace(0., max_freq, N_freqs))
else:
freq_bands = mnp.linspace(2. ** 0., 2. ** max_freq, N_freqs)
for freq in freq_bands:
for p_fn in self.kwargs['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 mnp.concatenate([fn(inputs) for fn in self.embed_fns], -1)
def get_embedder(L):
embed_kwargs = {
'include_input': True,
'input_dims': 3,
'max_freq_log2': L - 1,
'num_freqs': L,
'log_sampling': True,
'periodic_fns': [mnp.sin, mnp.cos],
}
embedder_obj = Embedder(**embed_kwargs)
embed = lambda x, eo=embedder_obj: eo.embed(x)
return embed, embedder_obj.out_dim
2.3 损失网络 NeRF 波及到两个网络 loss 的相加后作为总的 loss,来优化两个网络。因而,须要用 NeRFWithLossCell 类,将前向网络与损失函数连接起来。其中,前向计算过程比较复杂,波及空间采样、地位编码、神经网络预测、体渲染等步骤,而这些步骤在测试阶段同样须要用到,因而将其封装成一个函数 sample_and_render(),只需输出射线信息 rays、网络信息 net_kwargs、训练 / 测试的参数设置 train_kwargs/test_kwargs,就能输入射线所对应像素点的 RGB 的预测值。类的属性包含优化器、网络信息,还有打印信息所需 psnr。psnr 也是一个损失函数,但它不是优化的指标,因而不能作为 construct 的输入,只能用属性的形式进行记录。class NeRFWithLossCell(nn.Cell):
def __init__(self, optimizer, net_coarse, net_fine, embed_fn_pts, embed_fn_views):
super(NeRFWithLossCell, self).__init__()
self.optimizer = optimizer
self.net_coarse = net_coarse
self.net_fine = net_fine
self.embed_fn_pts = embed_fn_pts
self.embed_fn_views = embed_fn_views
self.net_kwargs = {
'net_coarse': self.net_coarse,
'net_fine': self.net_fine,
'embed_fn_pts': self.embed_fn_pts,
'embed_fn_views': self.embed_fn_views
}
self.psnr = None
def construct(self, H, W, K, rays_batch, rgb, chunk=1024 * 32, c2w=None, ndc=True,
near=0., far=1., use_viewdirs=False, **kwargs):
# 数据筹备,获取射线的地位、方向、远近场、视角
rays = get_rays_info(H, W, K, rays_batch, c2w, ndc, near, far, use_viewdirs=True)
# 采样 + 渲染
rets_coarse, rets_fine = sample_and_render(rays, **self.net_kwargs, **kwargs)
# 计算 loss
loss_coarse = img2mse(rets_coarse['rgb_map'], rgb)
loss_fine = img2mse(rets_fine['rgb_map'], rgb)
loss = loss_coarse + loss_fine
self.psnr = mse2psnr(loss_coarse)
return loss
2.4 训练网络这个类是封装损失网络和优化器,用优化器单步更新网络参数。class NeRFTrainOneStepCell(nn.TrainOneStepCell):
def __init__(self, network, optimizer):
super(NeRFTrainOneStepCell, self).__init__(network, optimizer)
self.grad = ops.GradOperation(get_by_list=True)
self.optimizer = optimizer
def construct(self, H, W, K, rays_batch, rgb, **kwargs):
weights = self.weights
loss = self.network(H, W, K, rays_batch, rgb, **kwargs)
grads = self.grad(self.network, weights)(H, W, K, rays_batch, rgb, **kwargs)
return F.depend(loss, self.optimizer(grads))
-
采样 3.1 粗采样粗采样就是沿着射线进行分段均匀分布的随机采样。输出为射线和采样点数,以及其余参数配置,返回值为采样点的三维坐标 pts、采样点在 - z 方向间隔值 z_vals,- z 方向分段点 z_splits。def sample_coarse(rays, N_samples, perturb=1., lindisp=False, pytest=False):
N_rays = rays.shape[0]
rays_o, rays_d = rays[:, 0:3], rays[:, 3:6]
near, far = rays[…, 6:7], rays[…, 7:8]t_vals = mnp.linspace(0, 1, N_samples + 1)
if not lindisp:z_splits = near * (1. - t_vals) + far * t_vals
else:
z_splits = 1. / (1. / near * (1. - t_vals) + 1. / far * t_vals)
z_splits = mnp.broadcast_to(z_splits, (N_rays, N_samples + 1))
if perturb > 0.:
upper = z_splits[..., 1:] lower = z_splits[..., :-1] t_rand = np.random.rand(*list(upper.shape)) if pytest: np.random.seed(0) t_rand = np.random.rand(*list(z_splits.shape)) t_rand = Tensor(t_rand, dtype=ms.float32) z_vals = lower + (upper - lower) * t_rand
else:
z_vals = .5 * (z_splits[..., 1:] + z_splits[..., :-1])
pts = rays_o[…, None, :] + rays_d[…, None, :] * z_vals[…, :, None]
return pts, z_vals, z_splits
3.2 细采样细采样依据粗采样的失去的分段权重,将其作为概率密度函数进行采样,而后将采样后果和粗采样后果拼接,失去细采样输入。这里用 sample_pdf()依照概率密度函数进行采样,其程序实现的次要步骤为:依据 PDF,计算累积散布函数 CDF,它将是一个分段线性函数。在 [0, 1] 内,对 CDF 值用均匀分布进行采样。将采样到的 CDF 值映射回坐标值。其中,第 3 步须要用高维的 searchsorted 算子去寻找坐标值的索引,然而,目前 MindSpore 的 searchsorted 只反对 1 维输出,无奈实现这一工作,临时用 pytorch 的算子代替。def sample_pdf(bins, weights, N_samples, det=False, pytest=False):
weights = weights + 1e-5
pdf = weights / mnp.sum(weights, -1, keepdims=True)
cdf = mnp.cumsum(pdf, -1)
cdf = mnp.concatenate([mnp.zeros_like(cdf[…, :1]), cdf], -1)if det:
u = mnp.linspace(0., 1., N_samples) u = mnp.broadcast_to(u, tuple(cdf.shape[:-1]) + (N_samples,))
else:
u = np.random.randn(*(list(cdf.shape[:-1]) + [N_samples])) u = Tensor(u, dtype=ms.float32)
if pytest:
np.random.seed(0) new_shape = list(cdf.shape[:-1]) + [N_samples] if det: u = np.linspace(0., 1., N_samples) u = np.broadcast_to(u, new_shape) else: u = np.random.rand(*new_shape) u = Tensor(u, dtype=ms.float32)
cdf_tmp, u_tmp = torch.Tensor(cdf.asnumpy()), torch.Tensor(u.asnumpy())
inds = Tensor(torch.searchsorted(cdf_tmp, u_tmp, right=True).numpy())
below = ops.Cast()(mnp.stack([mnp.zeros_like(inds – 1), inds – 1], -1), ms.float32)
below = ops.Cast()(below.max(axis=-1), ms.int32)
above = ops.Cast()(mnp.stack([(cdf.shape[-1] – 1) * mnp.ones_like(inds), inds], -1), ms.float32)
above = ops.Cast()(above.min(axis=-1), ms.int32)
inds_g = mnp.stack([below, above], -1)matched_shape = (inds_g.shape[0], inds_g.shape[1], cdf.shape[-1])
cdf_g = ops.GatherD()(mnp.broadcast_to(cdf.expand_dims(1), matched_shape), 2, inds_g)
bins_g = ops.GatherD()(mnp.broadcast_to(bins.expand_dims(1), matched_shape), 2, inds_g)denom = (cdf_g[…, 1] – cdf_g[…, 0])
denom = mnp.where(denom < 1e-5, mnp.ones_like(denom), denom)
t = (u – cdf_g[…, 0]) / denom
samples = bins_g[…, 0] + t * (bins_g[…, 1] – bins_g[…, 0])return samples
-
射线获取 get_rays()实现的是依据照片尺寸 H,W、相机内参矩阵 KK,相机位姿 c2wc2w,计算在世界坐标系下每个像素所对应的地位射线 rays_o 和方向射线 rays_d。这里对立采纳 openGL 坐标系,即 xx 为向右、yy 为向上、zz 为向外。其中,rays_d 对立依照 - z 方向,也就是拍摄照片时相机的朝向,进行归一化。因为在进行粗采样和细采样时,对于所有不同方向的射线,都是以 - z 方向上的间隔为准进行采样的。get_rays_info()是将射线的所有信息拼接成 rays 张量,便于后续调用,射线信息包含:地位向量 rays_o;方向向量 rays_d;近场 near(体渲染时的积分下限);远场 far(体渲染时的积分下限);单位化后的视角向量 view_dir;def get_rays(H, W, K, c2w):
i, j = mnp.meshgrid(mnp.linspace(0, W – 1, W), mnp.linspace(0, H – 1, H), indexing=’xy’)
dirs = mnp.stack((i – K[0) / K0, -(j – K1) / K1, -mnp.ones_like(i)], -1)c2w = Tensor(c2w)
rays_d = mnp.sum(dirs[…, None, :] * c2w[:3, :3], -1)rays_o = ops.BroadcastTo(rays_d.shape)(c2w[:3, -1])
return rays_o, rays_d
def get_rays_info(H, W, K, rays_batch=None, c2w=None, ndc=True, near=0, far=1, use_viewdirs=True):
if c2w is not None:
rays_o, rays_d = get_rays(H, W, K, c2w)
else:
rays_o, rays_d = rays_batch
if use_viewdirs:
viewdirs = rays_d
viewdirs = viewdirs / mnp.norm(viewdirs, axis=-1, keepdims=True)
viewdirs = mnp.reshape(viewdirs, [-1, 3])
if ndc:
rays_o, rays_d = ndc_rays(H, W, K[0][0], 1., rays_o, rays_d)
rays_o = mnp.reshape(rays_o, [-1, 3])
rays_d = mnp.reshape(rays_d, [-1, 3])
near, far = near * mnp.ones_like(rays_d[..., :1]), far * mnp.ones_like(rays_d[..., :1])
rays = mnp.concatenate([rays_o, rays_d, near, far], -1)
if use_viewdirs:
rays = mnp.concatenate([rays, viewdirs], -1)
return rays
-
渲染依据网络输入的空间采样点的 RGB\sigmaRGBσ,用体渲染公式计算出射线对应二维像素点的 RGB 值。因而,本函数是 NeRF 前向计算的主体框架。其次要过程为:依据射线数据,进行粗采样。将粗采样失去的空间采样点坐标 pts_coarse,和射线单位方向向量 views_coarse 拼接,输出粗采样网络。通过地位编码和神经网络前向计算,失去空间采样点 RGB\sigmaRGBσ 预测值 raw_coarse。其中,views_coarse 是 rays_d 的单位向量,打消了不同向量模长的影响。将 raw_coarse 输出体绘制函数 render(),失去渲染后的返回值 ret_coarse。它是一个字典,包含了渲染后的所有后果,其中包含粗采样点的权重 rets_coarse[‘weights’]。依据粗采样网络采样点的权重,以及采样分段区间,进行细采样。将细采样失去的空间采样点坐标 pts_fine,和射线单位方向向量 views_fine 拼接,输出细采样网络。通过地位编码和神经网络前向计算,失去空间采样点 RGB\sigmaRGBσ 预测值 raw_fine。将 raw_fine 输出体绘制函数 render(),失去渲染后的返回值 ret_fine,它同样包含了渲染后的所有后果。返回粗采样网络和细采样网络渲染后的返回值 rets_coarse,rets_fine。def sample_and_render(rays, net_coarse=None, net_fine=None, embed_fn_pts=None, embed_fn_views=None, N_coarse=64, N_fine=64, perturb=1., lindisp=False, pytest=False, raw_noise_std=0., white_bkgd=False):
# 数据筹备
rays_d = rays[…, 3: 6]
views = rays[:, -3:] if rays.shape[-1] > 8 else None# 粗采样
pts_coarse, z_coarse, z_splits = sample_coarse(rays, N_coarse, perturb, lindisp, pytest)
views_coarse = mnp.broadcast_to(views[…, None, :], pts_coarse.shape)
sh = pts_coarse.shape# 粗采样网络的 positional encoding
pts_embeded_coarse = embed_fn_pts(pts_coarse.reshape([-1, 3]))
views_coarse_embeded = embed_fn_views(views_coarse.reshape([-1, 3]))# 输出粗采样网络 + 渲染 失去输入
net_coarse_input = mnp.concatenate([pts_embeded_coarse, views_coarse_embeded], -1)
raw_coarse = net_coarse(net_coarse_input).reshape(list(sh[:-1]) + [4])
rets_coarse = render(raw_coarse, z_coarse, rays_d,raw_noise_std, white_bkgd, pytest)
# 细采样
weights = rets_coarse[‘weights’]
pts_fine, z_fine = sample_fine(rays, z_coarse, z_splits, weights,N_fine, perturb, pytest)
views_fine = mnp.broadcast_to(views[…, None, :], pts_fine.shape)
sh = pts_fine.shape# 细采样网络的 positional encoding
pts_embeded_fine = embed_fn_pts(pts_fine.reshape([-1, 3]))
views_embeded_fine = embed_fn_views(views_fine.reshape([-1, 3]))# 输出细采样网络 + 渲染 失去输入
net_fine_input = mnp.concatenate([pts_embeded_fine, views_embeded_fine], -1)
raw_fine = net_fine(net_fine_input).reshape(list(sh[:-1]) + [4])
rets_fine = render(raw_fine, z_fine, rays_d,raw_noise_std, white_bkgd, pytest)
return rets_coarse, rets_fine
- 主函数 main 函数的工作如下:加载图片和位姿、内参等数据,将其分成一个一个 batch 并打乱;用 create_nerf(),创立一个 NeRF 模型,返回值包含:各网络实例形成的字典 net_kwargs,训练参数 train_kwargs,测试参数 test_kwargs,优化器 optimizer。构建损失网络 net_with_loss,并加载之前保留的训练参数。构建训练网络 train_net。迭代优化 train_net。保留训练时参数,保留渲染视频。训练时调用训练网络:train_net(H, W, K, batch_rays, target_s, **train_kwargs)
三、参考资料[1] Mildenhall, B., Srinivasan, P.P., Tancik, M., Barron, J.T., Ramamoorthi, R., Ng, R. (2020). NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis. In: Vedaldi, A., Bischof, H., Brox, T., Frahm, JM. (eds) Computer Vision – ECCV 2020. ECCV 2020.[2] NeRF 论文的官网实现:https://github.com/bmild/nerf[3] NeRF 的 Pytorch 实现:https://github.com/yenchenlin…[4] MindSpore 1.6 API:https://www.mindspore.cn/docs…