NeRF算法

目录

算法介绍

基本原理

1. 体渲染

2. 多层感知机(MLP)

3. 位置编码

4. 两阶段层次化体采样

实验展示

代码解析


算法介绍

        NeRF(Neural Radiance Fields)是一种用于从2D图像中重建3D场景的神经网络模型。它通过训练一个深度神经网络来预测任意3D空间点的颜色和密度,从而实现对场景的精确重建。为了训练网络,针对一个静态场景,需要提供包含大量相机参数已知的图片的训练集,以及图片对应的相机所处3D坐标,相机朝向(2D,但实际使用3D单位向量表示方向)。使用多视角的数据进行训练,空间中目标位置具有更高的密度和更准确的颜色,促使神经网络预测一个连续性更好的场景模型。

        NeRF的关键思想是将场景表示为辐射场,即每个空间点的颜色和密度可以由一个神经网络来表示。通过在训练与真实观察值之间的差异,学习到场景的几何形状和光照信息。

基本原理

1. 体渲染

        体渲染是指根据三维空间中的密度和颜色信息,通过光线追踪等技术将体积数据转换成图像的过程。在 NeRF 中,体渲染用于生成逼真的图像,通过对场景中的三维结构和光照进行建模,从而实现高质量的渲染效果。

        下图是体渲染建模的示意图。光沿直线方向穿过一堆粒子 (粉色部分),如果能计算出每根光线从最开始发射,到最终打到成像平面上的辐射强度,我们就可以渲染出投影图像。为了简化计算,我们就假设光子只跟它附近的粒子发生作用,这个范围就是图中圆柱体大小的区间。

2. 多层感知机(MLP)

        多层感知机(MLP,Multilayer Perceptron)也叫人工神经网络,除了输入输出层,它中间可以有多个隐层,最简单的MLP只含一个隐层,即三层的结构,如下图:

        从上图可以看到,多层感知机层与层之间是全连接的。多层感知机最底层是输入层,用于接收外部数据。中间是隐藏层,用于提取和学习数据中的特征,并西将信号加权求和输出给下一次。最后是输出层,用于输出模型预测结果。

        NeRF函数是将一个连续的场景表示为一个输入为5D向量的函数,下图的实现中,x首先输入到MLP网络中,并输出σ和一个256维的中间特征,中间特征和d再一起输入到额外的全连接层(128维)中预测颜色。

3. 位置编码

        在神经网络中,特别是用于处理三维空间数据的模型中,位置编码是一种用来表示对象或特征在空间中位置信息的技术。如NeRF通过使用位置编码来捕获场景中不同点的位置信息以实现多个视角重建三维场景。

        比较下图中的四个效果,可以观察到第四个效果没有加位置编码,使得该图的效果就不清晰。

4. 两阶段层次化体采样

        NeRF的渲染策略是对相机发出的每条射线进行N个采样,将颜色加权求和,得到该射线颜色。为了更好的采样,提出两阶段层次化体素采样 的方式,即先按照均匀随机采样进行一次粗采样,将粗采样的输出的结果转化为分布,再根据分布进行一次精采样,最后NeRF训练的损失也是粗采样和精采样结果相加的结果,这样就实现了一个自动化Coarse-To-Fine的训练过程。如下图所示。

实验展示

下列是运用nerf在不同场景下渲染出的不同角度的视频截图:

效果一:

效果二:

 

 

效果三:

 

  

代码解析

run_nerf.py

        该代码使用了一个结构化的方法来实现基于NeRF的渲染系统,提供必要的工具,有效地训练和渲染3D场景。代码中主要实现了使用神经辐射场(NeRF)的体积渲染功能。它包括用于设置NeRF模型、渲染场景、处理数据和优化训练过程的实用程序。关键的功能,例如 batchify, run_network, render, raw2outputs, 和 render_rays定义用于处理任务,如将函数应用于批处理、运行神经网络、渲染射线以及将模型预测转换为有意义的输出(如RGB颜色和深度图)。该代码还支持重要性采样、检查点加载和精细的NeRF模型实例化,以增强渲染质量。

def batchify(fn, chunk):  # 构建一个将原始函数fn应用于较小批次的函数。"""Constructs a version of 'fn' that applies to smaller batches."""if chunk is None:  # 如果chunk为None,return fn  # 则直接返回原始函数fndef ret(inputs):  # 如果chunk不为None,则定义一个新的函数ret,该函数接受输入inputsreturn torch.cat([fn(inputs[i:i + chunk]) for i in range(0, inputs.shape[0], chunk)],0)  # 将输入数据按照chunk大小分成小批次,然后逐个批次应用原始函数fn,最后将结果连接起来并返回return ret  # 返回一个函数对象retdef run_network(inputs, viewdirs, fn, embed_fn, embeddirs_fn,netchunk=1024 * 64):  # 定义了一个函数run_network,用于准备输入数据并将其应用于网络函数fn"""Prepares inputs and applies network 'fn'."""inputs_flat = torch.reshape(inputs, [-1, inputs.shape[-1]])  # 将输入数据inputs进行扁平化处理,以便传入嵌入函数。embedded = embed_fn(inputs_flat)  # 使用embed_fn对扁平化后的输入数据进行嵌入操作,得到嵌入向量embeddedif viewdirs is not None:  # 如果viewdirs不为Noneinput_dirs = viewdirs[:, None].expand(inputs.shape)  # 将视角方向viewdirs扩展为与输入数据inputs相同的形状input_dirs_flat = torch.reshape(input_dirs,[-1, input_dirs.shape[-1]])  # 将经过扩展后的视角方向张量input_dirs重新整形为一个二维张量input_dirs_flatembedded_dirs = embeddirs_fn(input_dirs_flat)  # 调用函数embeddirs_fn,将经过重新整形的视角方向数据input_dirs_flat作为输入,进行视角方向的嵌入操作embedded = torch.cat([embedded, embedded_dirs], -1)  # 将视角方向的嵌入表示embedded_dirs与输入数据的嵌入表示embedded进行了拼接outputs_flat = batchify(fn, netchunk)(embedded)  # 将拼接后的嵌入表示embedded分批次应用给定的网络函数fn,使用batchify函数处理outputs = torch.reshape(outputs_flat,list(inputs.shape[:-1]) + [outputs_flat.shape[-1]])  # 将输出结果重新整形为与输入数据相同形状的张量outputsreturn outputs  # 返回处理后的输出结果def batchify_rays(rays_flat, chunk=1024 * 32, **kwargs):  # 定义了一个函数batchify_rays,用于在较小的小批次中渲染光线,以避免内存溢出问题"""Render rays in smaller minibatches to avoid OOM."""all_ret = {}  # 创建一个空字典,用于存储所有小批次渲染的结果for i in range(0, rays_flat.shape[0], chunk):  # 通过循环,将光线数据rays_flat按照指定的chunk大小分成小批次进行处理ret = render_rays(rays_flat[i:i + chunk],**kwargs)  # 当前批次的光线数据进行渲染操作,调用render_rays函数,并将渲染结果存储在ret中。rays_flat[i:i+chunk]表示当前批次的光线数据。for k in ret:  # 遍历渲染结果ret中的键(key)if k not in all_ret:  # 如果当前键k不在all_ret字典中all_ret[k] = []  # 将其初始化为空列表all_ret[k].append(ret[k])  # 将当前批次的渲染结果ret[k]添加到all_ret[k]列表中,实现将不同批次的渲染结果按键分别存储在all_ret字典中all_ret = {k: torch.cat(all_ret[k], 0) for k in all_ret}  # 通过字典推导式,遍历all_ret字典中的每个键值对,对值(列表)进行拼接操作return all_ret  # 将合并后的结果字典返回def render(H, W, K, chunk=1024 * 32, rays=None, c2w=None, ndc=True,near=0., far=1.,use_viewdirs=False, c2w_staticcam=None,**kwargs):  # 定义了一个render函数,用于渲染场景并返回渲染结果if c2w is not None:  # 如果提供了相机到世界坐标系的变换矩阵c2w# special case to render full imagerays_o, rays_d = get_rays(H, W, K,c2w)  # 调用get_rays函数,根据图像的高度H、宽度W和相机内参K以及相机到世界坐标系的变换矩阵c2w,获取光线的起点rays_o和方向rays_delse:  # 如果未提供相机到世界坐标系的变换矩阵c2w# use provided ray batchrays_o, rays_d = rays  # 使用提供的光线数据rays作为光线的起点和方向,即直接使用提供的光线数据作为渲染的输入if use_viewdirs:  # 如果需要使用视角方向信息# provide ray directions as inputviewdirs = rays_d  # 将光线方向rays_d作为视角方向viewdirsif c2w_staticcam is not None:  # 如果存在静态相机到世界坐标系的变换矩阵c2w_staticcam# special case to visualize effect of viewdirsrays_o, rays_d = get_rays(H, W, K,c2w_staticcam)  # 根据图像的高度H、宽度W和相机内参K以及静态相机到世界坐标系的变换矩阵c2w_staticcam,获取新的光线的起点和方向viewdirs = viewdirs / torch.norm(viewdirs, dim=-1, keepdim=True)  # 对视角方向进行归一化处理,即将视角方向向量除以其模长,使其变为单位向量viewdirs = torch.reshape(viewdirs, [-1, 3]).float()  # 将归一化后的视角方向重新整形为二维张量,以便后续处理sh = rays_d.shape  # [..., 3] # 获取光线方向rays_d的形状,用变量sh保存,形状为[..., 3],表示每个光线方向由三个分量组成if ndc:  # 如果需要使用归一化设备坐标系(NDC),则执行以下操作# for forward facing scenesrays_o, rays_d = ndc_rays(H, W, K[0][0], 1., rays_o, rays_d)  # 调用ndc_rays函数,对光线的起点和方向进行NDC转换,以适应前向场景的渲染需求# Create ray batchrays_o = torch.reshape(rays_o, [-1, 3]).float()  # 将光线起点rays_o重新整形为二维张量,并转换为浮点型数据类型rays_d = torch.reshape(rays_d, [-1, 3]).float()  # 将光线方向rays_d重新整形为二维张量,并转换为浮点型数据类型near, far = near * torch.ones_like(rays_d[..., :1]), far * torch.ones_like(rays_d[..., :1])  # 根据近平面和远平面的距离,创建与光线方向相同形状的张量rays = torch.cat([rays_o, rays_d, near, far], -1)  # 将光线起点、方向、近平面和远平面拼接成一个光线批次数据rays,以便进行批次渲染if use_viewdirs:  # 如果使用视角方向信息rays = torch.cat([rays, viewdirs], -1)  # 将视角方向信息拼接到光线批次数据中,以考虑视角方向对渲染的影响# Render and reshapeall_ret = batchify_rays(rays, chunk, **kwargs)  # 调用batchify_rays函数,对光线批次数据进行分批次渲染,返回渲染结果的字典all_retfor k in all_ret:  # 遍历渲染结果字典all_ret中的每个键(key)k_sh = list(sh[:-1]) + list(all_ret[k].shape[1:])  # 根据光线方向的形状sh和当前渲染结果的形状,构建新的形状k_sh,保持与原始光线方向形状相同all_ret[k] = torch.reshape(all_ret[k], k_sh)  # 将当前渲染结果按照新的形状k_sh进行重新整形,以确保与光线方向形状相匹配k_extract = ['rgb_map', 'disp_map', 'acc_map']  # 指定需要提取的渲染结果的键列表ret_list = [all_ret[k] for k in k_extract]  # 根据指定的键列表k_extract,提取对应的渲染结果,存储在ret_list中ret_dict = {k: all_ret[k] for k in all_ret ifk not in k_extract}  # 根据渲染结果字典all_ret中的键,将不在提取列表k_extract中的渲染结果存储在ret_dict中return ret_list + [ret_dict]  # 将提取的渲染结果列表和剩余的渲染结果字典作为列表的形式返回,其中列表包含提取的渲染结果和剩余的渲染结果字典def render_path(render_poses, hwf, K, chunk, render_kwargs, gt_imgs=None, savedir=None, render_factor=0):# 定义了一个render_path函数,用于根据渲染姿态、相机参数等信息渲染场景,并返回渲染结果H, W, focal = hwf  # 从hwf中解包出图像的高度、宽度和焦距if render_factor != 0:  # 如果render_factor不等于0,则执行下面的操作# Render downsampled for speedH = H // render_factor  # 将图像的高度按照render_factor进行缩放W = W // render_factor  # 将图像的宽度按照render_factor进行缩放focal = focal / render_factor  # 将焦距按照render_factor进行缩放rgbs = []  # 初始化一个空列表rgbs,用于存储渲染的RGB图像disps = []  # 初始化一个空列表disps,用于存储渲染的深度图像t = time.time()  # 记录当前时间,用于计算渲染时间for i, c2w in enumerate(tqdm(render_poses)):  # 历渲染姿态列表render_poses,使用tqdm显示进度条,并对每个姿态进行渲染print(i, time.time() - t)  # 打印当前渲染的索引和上一个渲染的时间间隔t = time.time()  # 更新时间记录rgb, disp, acc, _ = render(H, W, K, chunk=chunk, c2w=c2w[:3, :4],**render_kwargs)  # 调用render函数进行图像渲染,获取RGB图像、深度图像、准确度和其他信息rgbs.append(rgb.cpu().numpy())  # 将渲染得到的RGB图像转换为NumPy数组并添加到rgbs列表中disps.append(disp.cpu().numpy())  # 将渲染得到的深度图像转换为NumPy数组并添加到disps列表中if i == 0:  # 如果是第一次渲染,则执行以下操作print(rgb.shape, disp.shape)  # 打印RGB图像和深度图像的形状"""if gt_imgs is not None and render_factor==0:p = -10. * np.log10(np.mean(np.square(rgb.cpu().numpy() - gt_imgs[i])))print(p)"""if savedir is not None:  # 如果指定了保存目录,则执行以下操作rgb8 = to8b(rgbs[-1])  # 将最新的RGB图像转换为8位表示filename = os.path.join(savedir, '{:03d}.png'.format(i))  # 构建保存文件的路径和文件名imageio.imwrite(filename, rgb8)  # 将RGB图像保存为PNG格式文件rgbs = np.stack(rgbs, 0)  # 将所有RGB图像堆叠成一个数组disps = np.stack(disps, 0)  # 将所有深度图像堆叠成一个数组return rgbs, disps  # 返回所有渲染的RGB图像和深度图像数组def create_nerf(args):  # 定义了一个create_nerf函数,用于实例化 NeRF 的 MLP 模型,并设置训练所需的参数和优化器"""Instantiate NeRF's MLP model."""embed_fn, input_ch = get_embedder(args.multires, args.i_embed)  # 调用get_embedder函数获取嵌入器函数和输入通道数input_ch_views = 0  # 初始化视角方向的输入通道数为0embeddirs_fn = None  # 初始化视角方向的嵌入器函数为Noneif args.use_viewdirs:  # 如果使用视角方向信息,则执行以下操作embeddirs_fn, input_ch_views = get_embedder(args.multires_views,args.i_embed)  # 根据视角方向的多分辨率参数和输入嵌入维度获取视角方向的嵌入器函数和输入通道数output_ch = 5 if args.N_importance > 0 else 4  # 根据重要性采样数量确定输出通道数skips = [4]  # 设置跳跃连接列表model = NeRF(D=args.netdepth, W=args.netwidth,input_ch=input_ch, output_ch=output_ch, skips=skips,input_ch_views=input_ch_views, use_viewdirs=args.use_viewdirs).to(device)  # 创建主要的 NeRF 模型,并将其移动到指定设备grad_vars = list(model.parameters())  # 获取模型的参数列表model_fine = None  # 初始化细化模型为Noneif args.N_importance > 0:  # 如果需要重要性采样,则执行以下操作model_fine = NeRF(D=args.netdepth_fine, W=args.netwidth_fine,input_ch=input_ch, output_ch=output_ch, skips=skips,input_ch_views=input_ch_views, use_viewdirs=args.use_viewdirs).to(device)  # 创建细化的 NeRF 模型,并将其移动到指定设备grad_vars += list(model_fine.parameters())  # 将细化模型的参数添加到参数列表中network_query_fn = lambda inputs, viewdirs, network_fn: run_network(inputs, viewdirs, network_fn,embed_fn=embed_fn,embeddirs_fn=embeddirs_fn,netchunk=args.netchunk)  # 定义网络查询函数,用于运行网络# Create optimizeroptimizer = torch.optim.Adam(params=grad_vars, lr=args.lrate, betas=(0.9, 0.999))  # 创建 Adam 优化器start = 0  # 初始化起始步数为0basedir = args.basedir  # 获取基础目录expname = args.expname  # 获取实验名称########################### Load checkpointsif args.ft_path is not None and args.ft_path != 'None':  # 如果提供了微调路径,则执行以下操作ckpts = [args.ft_path]  # 将微调路径添加到检查点列表中else:  # 否则,执行以下操作ckpts = [os.path.join(basedir, expname, f) for f in sorted(os.listdir(os.path.join(basedir, expname))) if'tar' in f]  # 根据基础目录和实验名称获取所有检查点文件路径print('Found ckpts', ckpts)  # 打印找到的检查点文件路径if len(ckpts) > 0 and not args.no_reload:  # 如果存在检查点文件且不禁止重新加载,则执行以下操作ckpt_path = ckpts[-1]  # 获取最新的检查点文件路径print('Reloading from', ckpt_path)  # 打印重新加载的检查点文件路径ckpt = torch.load(ckpt_path)  # 加载检查点文件start = ckpt['global_step']  # 获取全局步数optimizer.load_state_dict(ckpt['optimizer_state_dict'])  # 加载优化器状态字典# Load modelmodel.load_state_dict(ckpt['network_fn_state_dict'])  # 如果存在细化模型,则执行以下操作if model_fine is not None:  # 如果存在细化模型,则执行以下操作model_fine.load_state_dict(ckpt['network_fine_state_dict'])  # 加载细化模型的状态字典########################### NDC only good for LLFF-style forward facing dataif args.dataset_type != 'llff' or args.no_ndc:  # # 如果数据集类型不是LLFF或禁用NDC,则执行以下操作print('Not ndc!')render_kwargs_train['ndc'] = False  # 设置不使用NDCrender_kwargs_train['lindisp'] = args.lindisp  # 设置线性深度render_kwargs_test = {k: render_kwargs_train[k] for k in render_kwargs_train}  # 设置测试渲染参数render_kwargs_test['perturb'] = False  # 表示在测试阶段不进行扰动操作,即不对输入进行随机扰动,保持输入不变render_kwargs_test['raw_noise_std'] = 0.  # 表示在测试阶段不添加原始噪声return render_kwargs_train, render_kwargs_test, start, grad_vars, optimizer  # 返回训练和测试的渲染参数、起始步数、梯度变量和优化器def raw2outputs(raw, z_vals, rays_d, raw_noise_std=0, white_bkgd=False, pytest=False):  # 将原始数据转换为输出结果。raw2alpha = lambda raw, dists, act_fn=F.relu: 1. - torch.exp(-act_fn(raw) * dists)  # 定义一个名为raw2alpha的lambda函数,接受三个参数:raw, dists和act_fn(默认为F.relu)# 函数的主要功能是计算1减去以act_fn(raw)乘以dists为指数的负指数值dists = z_vals[..., 1:] - z_vals[..., :-1]  # 计算z_vals数组中相邻元素之间的差值,并将结果存储在dists变量中dists = torch.cat([dists, torch.Tensor([1e10]).expand(dists[..., :1].shape)],-1)  # [N_rays, N_samples]  将dists张量与一个值为1e10的张量进行拼接,新张量的维度与dists的前n-1个维度相同,最后一个维度为1dists = dists * torch.norm(rays_d[..., None, :], dim=-1)  # 计算射线方向的范数,并将其与距离相乘rgb = torch.sigmoid(raw[..., :3])  # [N_rays, N_samples, 3] # 使用torch.sigmoid函数对raw张量的最后一维的前三个通道进行激活操作,并将结果赋值给rgb变量noise = 0.  # 初始化噪声值为0if raw_noise_std > 0.:  # 判断原始噪声标准差是否大于0noise = torch.randn(raw[..., 3].shape) * raw_noise_std  # 如果大于0,则生成一个与raw[...,3]形状相同的随机噪声矩阵,并乘以原始噪声标准差# Overwrite randomly sampled data if pytestif pytest:  # 如果pytest为真,执行以下代码块np.random.seed(0)  # 设置随机数种子,确保每次运行结果一致noise = np.random.rand(*list(raw[..., 3].shape)) * raw_noise_std  # 生成与raw[...,3]形状相同的随机噪声,并乘以raw_noise_stdnoise = torch.Tensor(noise)  # 将生成的噪声转换为PyTorch张量alpha = raw2alpha(raw[..., 3] + noise,dists)  # [N_rays, N_samples] # 定义一个变量alpha,将raw数组的第四维(索引为3)与noise相加,然后将结果传递给raw2alpha函数,同时传入dists参数# weights = alpha * tf.math.cumprod(1.-alpha + 1e-10, -1, exclusive=True)weights = alpha * torch.cumprod(torch.cat([torch.ones((alpha.shape[0], 1)), 1. - alpha + 1e-10], -1), -1)[:,:-1]  # 创建一个全1矩阵,形状与alpha相同,列数为1 #使用alpha值计算权重rgb_map = torch.sum(weights[..., None] * rgb, -2)  # [N_rays, 3] # 使用torch库计算权重和rgb的加权和,将结果存储在rgb_map中depth_map = torch.sum(weights * z_vals, -1)  # 计算权重和z_vals的逐元素乘积,然后沿着最后一个维度求和,得到深度图disp_map = 1. / torch.max(1e-10 * torch.ones_like(depth_map), depth_map / torch.sum(weights,-1))  # 创建一个与depth_map形状相同的全1张量,并乘以1e-10,用于避免除以0的情况 计算depth_map与weights的逐元素相除,然后沿着最后一个维度求和 # 使用torch.max函数找到ones_like_depth_map和depth_map_divided_by_sum_weights中的最大值 计算1除以最大值,得到disp_mapacc_map = torch.sum(weights, -1)  # 使用torch库的sum函数,对weights张量沿着最后一个维度(-1表示最后一个维度)求和,得到的结果赋值给acc_map变量if white_bkgd:  # 判断是否需要白色背景rgb_map = rgb_map + (1. - acc_map[..., None])  # 如果需要白色背景,将rgb_map与(1.-acc_map[...,None])相加return rgb_map, disp_map, acc_map, weights, depth_map  # 返回rgb_map, disp_map, acc_map, weights, depth_map# 渲染射线的函数
def render_rays(ray_batch,  # 包含射线信息的字典network_fn,  # 用于生成射线的神经网络函数network_query_fn,  # 用于查询射线结果的神经网络函数N_samples,  # 采样点的数量retraw=False,  # 是否返回原始数据,默认为Falselindisp=False,  # 是否使用线性视差,默认为Falseperturb=0.,  # 扰动值,默认为0.0N_importance=0,  # 重要采样点的数量,默认为0network_fine=None,  # 精细网络函数,默认为Nonewhite_bkgd=False,  # 是否使用白色背景,默认为Falseraw_noise_std=0.,  # 原始噪声标准差,默认为0verbose=False,  # 是否输出详细信息,默认为Falsepytest=False):  # 是否进行测试,默认为FalseN_rays = ray_batch.shape[0]  # 获取光线的数量rays_o, rays_d = ray_batch[:, 0:3], ray_batch[:, 3:6]  # [N_rays, 3] each  # 将光线批次分为起点和方向,每个都是一个形状为 [N_rays, 3] 的张量viewdirs = ray_batch[:, -3:] if ray_batch.shape[-1] > 8 else None  # 如果光线批次的最后一个维度大于8,那么取最后三个元素作为视角方向,否则视角方向为Nonebounds = torch.reshape(ray_batch[..., 6:8], [-1, 1, 2])  # 将光线批次的第7和第8个元素(即 near 和 far)重塑为形状为 [-1,1,2] 的张量near, far = bounds[..., 0], bounds[..., 1]  # [-1,1] # 从 bounds 张量中提取出 near 和 far,它们的形状都是 [-1,1]t_vals = torch.linspace(0., 1., steps=N_samples)  # 创建一个等差数列t_vals,范围从0到1,步长为N_samplesif not lindisp:  # 根据lindisp的值,使用不同的公式计算z_valsz_vals = near * (1. - t_vals) + far * (t_vals)  # 如果lindisp为False,那么z_vals等于near乘以(1-t_vals)加上far乘以t_valselse:z_vals = 1. / (1. / near * (1. - t_vals) + 1. / far * (t_vals))  # 如果lindisp为True,那么z_vals等于1除以(1/near乘以(1-t_vals)加上1/far乘以t_vals)z_vals = z_vals.expand([N_rays, N_samples])  # 将z_vals扩展为[N_rays, N_samples]的形状if perturb > 0.:  # 判断 perturb 是否大于 0# get intervals between samples # 获取样本之间的间隔mids = .5 * (z_vals[..., 1:] + z_vals[..., :-1])  # 计算z_vals数组中相邻元素的平均值,并将结果存储在mids变量中upper = torch.cat([mids, z_vals[..., -1:]], -1)  # 将mids和z_vals的最后一个维度进行拼接,并将结果赋值给upperlower = torch.cat([z_vals[..., :1], mids], -1)  # 将z_vals的前n-1个元素与mids进行拼接,形成一个新的张量lower# stratified samples in those intervals  # 在那些间隔中进行分层采样t_rand = torch.rand(z_vals.shape)  # 生成一个与z_vals形状相同的随机张量,并将其赋值给t_rand# Pytest, overwrite u with numpy's fixed random numbers # Pytest, 用numpy的固定随机数覆盖uif pytest:  # 如果pytest为真,则执行以下代码块np.random.seed(0)  # 设置随机数种子,确保每次运行结果一致t_rand = np.random.rand(*list(z_vals.shape))  # 生成与z_vals形状相同的随机数数组t_randt_rand = torch.Tensor(t_rand)  # 将t_rand转换为PyTorch张量z_vals = lower + (upper - lower) * t_rand  # 根据给定的范围和随机数计算z_vals的值pts = rays_o[..., None, :] + rays_d[..., None, :] * z_vals[..., :, None]  # [N_rays, N_samples, 3] # 计算射线上的采样点坐标#     raw = run_network(pts)raw = network_query_fn(pts, viewdirs, network_fn)  # 调用网络查询函数,传入参数pts, viewdirs和network_fn,获取原始输出结果rawrgb_map, disp_map, acc_map, weights, depth_map = raw2outputs(raw, z_vals, rays_d, raw_noise_std, white_bkgd,pytest=pytest)  # 将原始输出结果raw转换为最终的输出结果rgb_map, disp_map, acc_map, weights, depth_mapif N_importance > 0:  # 判断 N_importance 是否大于0rgb_map_0, disp_map_0, acc_map_0 = rgb_map, disp_map, acc_map  # 将rgb_map的值赋给rgb_map_0  将disp_map的值赋给disp_map_0  将acc_map的值赋给acc_map_0z_vals_mid = .5 * (z_vals[..., 1:] + z_vals[..., :-1])  # 计算z_vals中相邻元素的平均值,并将结果存储在z_vals_mid中z_samples = sample_pdf(z_vals_mid, weights[..., 1:-1], N_importance, det=(perturb == 0.),pytest=pytest)  # 使用给定的参数生成采样点z_samples = z_samples.detach()  # 将z_samples从计算图中分离,以便在反向传播过程中不计算梯度z_vals, _ = torch.sort(torch.cat([z_vals, z_samples], -1), -1)  # 将z_vals和z_samples沿着最后一个维度进行拼接pts = rays_o[..., None, :] + rays_d[..., None, :] * z_vals[..., :,None]  # [N_rays, N_samples + N_importance, 3]# 计算射线与场景中的点的交点坐标# rays_o: 射线的起点坐标,形状为 (N_rays, 3)# rays_d: 射线的方向向量,形状为 (N_rays, 3)# z_vals: 射线与场景中的点的交点的深度值,形状为 (N_rays, N_samples + N_importance)# pts: 存储射线与场景中的点的交点坐标,形状为 (N_rays, N_samples + N_importance, 3)run_fn = network_fn if network_fine is None else network_fine  # 判断 network_fine 是否为 None,如果是,则将 network_fn 赋值给 run_fn,否则将 network_fine 赋值给 run_fn# raw = run_network(pts, fn=run_fn)raw = network_query_fn(pts, viewdirs, run_fn)  # 调用network_query_fn函数,传入参数pts, viewdirs和run_fn,并将结果赋值给raw变量rgb_map, disp_map, acc_map, weights, depth_map = raw2outputs(raw, z_vals, rays_d, raw_noise_std, white_bkgd,pytest=pytest)  # 调用 raw2outputs 函数,传入相应的参数ret = {'rgb_map': rgb_map, 'disp_map': disp_map,'acc_map': acc_map}  # 定义一个字典变量 ret,用于存储三个键值对  存储颜色映射信息  存储视差映射信息  存储累积映射信息if retraw:  # 判断变量retraw的值是否为Trueret['raw'] = raw  # 将原始数据赋值给字典ret的'raw'键if N_importance > 0:  # 判断 N_importance 是否大于0ret['rgb0'] = rgb_map_0  # 将rgb_map_0的值赋给ret字典中的'rgb0'键ret['disp0'] = disp_map_0  # 将disp_map_0的值赋给ret字典中的'disp0'键ret['acc0'] = acc_map_0  # 将变量acc_map_0的值赋给字典ret的键'acc0'ret['z_std'] = torch.std(z_samples, dim=-1,unbiased=False)  # [N_rays] # 计算张量z_samples沿着最后一个维度的标准差,并将结果存储在ret字典的'z_std'键中 # N_rays表示射线的数量,unbiased=False表示使用无偏估计for k in ret:  # 遍历ret列表中的每个元素,将每个元素赋值给变量kif (torch.isnan(ret[k]).any() or torch.isinf(ret[k]).any()) and DEBUG:  # 判断 ret[k] 中是否存在 NaN 或 Inf 值,如果存在并且 DEBUG 为 True,则执行后续代码print(f"! [Numerical Error] {k} contains nan or inf.")  # 打印格式化字符串,输出错误信息,提示变量k包含nan或infreturn ret  # 返回变量ret的值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/9262.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SAP-ABAP-视图

1、什么是视图? 当需要查询多个表中的某些字段的数据时,就可以使用视图。视图不影响数据库中的数据,仅作为查询手段或工具。 2、视图类型: 数据库视图和维护视图经常使用。 3、创建视图SE11 3.1、数据库视图 可以直接输入表名…

Linux 文件

文章目录 文件操作回顾(C/C)系统调用接口 管理文件认识一切皆文件C/C的文件操作函数与系统调用接口的关系……重定向与缓冲区 -- 认识重定向与缓冲区 -- 理解使用重定向缓冲区实现一个简单的Shell(加上重定向)标准输出和标准错误(在重定向下的意义) 磁盘文件磁盘存储文件操作系…

【BUUCTF】[RoarCTF 2019]Easy Java1

工具:hackbar发包,bp抓包。 解题步骤:【该网站有时候send不了数据,只能销毁靶机重试】 这里的登录界面是个天坑【迷魂弹】 直接点击help,然后进行打开hackbar——通过post请求,再通过bp抓包,…

....comic科学....食用手册....

1.点击链接后,保存漫画至夸克网盘,若是新用户需要用手机注册. 2.在应用商店下载夸克APP. 3.登录APP下载已保存的漫画. 3.1 进入APP点击 夸克网盘 3.2 点击“转存的内容”后,长按 漫画文件夹,点击下载,下载速度400K左…

桥田汉诺威工业展观察:走好脚下更需着眼未来

2024年4月21日,桥田创始人刘小平携核心团队6人共赴“制造业展会天花板”——德国汉诺威工业博览会参观学习,此次参访,是桥田智能组队出海的第二次学习之旅,未来,我们将组织更多优秀员工出海交流学习,让每一…

一套C语言VC + MSSQL开发PACS系统源码 带三维重建和还原的PACS医学影像全套系统源码

一套C语言VC MSSQL开发PACS系统源码 带三维重建和还原的PACS医学影像全套系统源码 本套PACS系统成品源码,自主版权。集成三维影像后处理功能,包括三维多平面重建、三维容积重建、三维表面重建、三维虚拟内窥镜、最大/小密度投影、心脏动脉钙化分析等功…

03-单片机商业项目编程,从零搭建低功耗系统设计

一、本文内容 上一节《02-单片机商业项目编程,从零搭建低功耗系统设计-CSDN博客》引出了伪时间片的概念,这也是再低功耗系统设计中必须使用的程序设计逻辑,本文着重来讲解如何利用伪时间片来设计伪多任务,以及伪时间片多任务内核设…

Abp框架,EF 生成迁移文件时,自动添加表和字段注释内容

在使用 abp 框架,或者ef 的时候都会遇到一个问题,就是建实体后要将实体描述生成到数据库中,就需要手动去添加 [Comment("注释内容")] 注解,这样相当于手动写两次注释(即使你是 Ctrl C)&#x…

景源畅信电商:抖音小店有哪些比较热门的宣传方法?

抖音小店的热门宣传方法,是许多商家关注的焦点。在数字化营销时代,有效的宣传手段不仅能提升品牌知名度,还能吸引潜在消费者,促进销售。以下是针对抖音小店热门宣传方法的详细阐述: 一、短视频内容营销 作为抖音的核心…

【进程等待】阻塞等待 | options非阻塞等待

目录 waitpid 阻塞等待 options&非阻塞等待 pid_t返回值 阻塞等待VS非阻塞等待 waitpid 回顾上篇: pid_ t waitpid(pid_t pid, int *status, int options); 返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了…

PSCA电源管理软件栈示例

安全之安全(security)博客目录导读 目录 1、移动通讯系统 2、基础设施系统 本博客就PSCA电源管理软件栈进行举例,主要以移动通讯系统和基础设施系统为例来说明。 1、移动通讯系统 图3.4显示了一个可以在基于Linux的移动设备中实现的电源管理堆栈示例。 在Linux…

使用WPF中的Trigger实现按钮样式动态更改

使用WPF中的Trigger实现按钮样式动态更改 在Windows Presentation Foundation (WPF)中,Trigger 是一种强大的机制,它可以基于控件的属性值来动态更改控件的样式。这篇博客将介绍如何使用Trigger实现按钮在鼠标悬停时样式动态更改的效果。我们将详细讨论为…

Faiss核心解析:提升推荐系统的利器【AI写作免费】

首先,这篇文章是基于笔尖AI写作进行文章创作的,喜欢的宝子,也可以去体验下,解放双手,上班直接摸鱼~ 按照惯例,先介绍下这款笔尖AI写作,宝子也可以直接下滑跳过看正文~ 笔尖Ai写作:…

STM32使用L9110驱动电机自制小风扇

1.1 介绍: 该电机控制模块采用L9110电机控制芯片。该芯片具有两个TTL/CMOS兼容输入端子,并具有抗干扰特性:具有高电流驱动能力,两个输出端子可直接驱动直流电机,每个输出端口可提供750800mA动态电流,其峰值…

【适用全主题】WordPress原创插件:弹窗通知插件 支持内容自定义

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 适用于所有WordPress主题的弹窗插件 一款WordPress原创插件:弹窗通知插件 支持内容自定义 二、效果展示 1.部分代码 代码如下(示例)&#xff1…

【java.io.IOException: java.lang.IllegalArgumentException: db.num is null】

默认用户名:nacos 密码:nacos解决方法: a)在conf目录下将nacos-mysql.sql脚本创建完成; b)修改application.properties,在内容里添加如下内容 spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://localho…

有刷电机、无刷电机

阅读引言: 最近在备赛, 自己之前虽然用过电机, 但是发现在一些高要求的应用场景, 发现自己对电机的知识理解得不是很透彻, 所以写下这篇文章。 目录 一、 有刷电机内部原理 二、有刷电机一些关键参数 三、无刷电机内…

机器学习初学者 6 个核心算法!建议收藏,反复观看!

今天再来介绍机器学习算法的基本概念和适用场景! 首先,引用一句英国统计学家George E. P. Box的名言:All models are wrong, but some are useful. 没有哪一种算法能够适用所有情况,只有针对某一种问题更有用的算法。 也就是说&…

STM32理论 —— μCOS-Ⅲ(新)

文章目录 1. 任务调度器1.1 抢占式调度 μCos-Ⅲ全称是Micro C OS Ⅲ,由Micriμm 公司发布的一个基于C 语言编写的第三代小型实时操作系统(RTOS); RTOS 与裸机相比最大的优势在于多任务管理与实时性,它提供了多任务管理和任务间通信的功能&a…

一文玩转Vue3参数传递——全栈开发之路--前端篇(8)

全栈开发一条龙——前端篇 第一篇:框架确定、ide设置与项目创建 第二篇:介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇:setup语法,设置响应式数据。 第四篇:数据绑定、计算属性和watch监视 第五篇 : 组件…