-
train.py
NerfTree_Pytorch类:
用于管理NeRF模型中的体素信息,包括体素的初始化、体素在空间中的定位以及体素数据的存储等,这些操作对于实现高效和精确的三维场景渲染至关重要。
class NerfTree_Pytorch(object): # 基于pytorch实现的NerfTree
定义了一个名为 NerfTree_Pytorch
的类。这个类的目的是实现NerfTree,这是一个为NeRF(Neural Radiance Fields)模型设计的数据结构,用于高效地管理和查询场景中不同区域的信息。
1.初始化类的函数:
def __init__(self, xyz_min, xyz_max, grid_coarse, grid_fine, deg, sigma_init, sigma_default, device):
super().__init__()
self.sigma_init = sigma_init
self.sigma_default = sigma_default
类的构造函数,用于初始化类的实例。,其中各个参数的含义:
xyz_min
: 场景边界的最小坐标值,格式为长度为3的列表或者形状为(1, 3)
的张量,表示三维空间中的最小点。xyz_max
: 场景边界的最大坐标值,格式同xyz_min
。grid_coarse
: 粗糙网格的边长大小,用于创建初始的、较粗糙的体素网格。表示粗糙(或初级)体素网格中每个维度的体素数量。例如,如果grid_coarse
为 64,那么粗糙网格将在每个维度(X、Y、Z)上有 64 个体素,总共会有 64^3个体素。grid_fine
: 细网格的边长大小,用于在粗糙网格的基础上创建更细致的体素网格。用于表示每个粗糙体素将被进一步细分成多少个细体素。如果grid_fine
为 2,那么每个粗糙体素将在每个维度上被细分成 2 个细体素。deg
: 用于球面谐波函数的度数,影响球面谐波表示的维度。sigma_init
: 体素初始的密度值。sigma_default
: 体素默认的密度值,用于填充未明确设置密度的体素。device
: 指定运算的设备,比如'cuda'
或者'cpu'
self.sigma_voxels_coarse = torch.full((grid_coarse,grid_coarse,grid_coarse), self.sigma_init, device=device)
self.index_voxels_coarse = torch.full((grid_coarse,grid_coarse,grid_coarse), 0, dtype=torch.long, device=device)
torch.full()
函数的这个参数用来指定创建的Tensor中所有元素的初始值。
创建一个形状为 (grid_coarse, grid_coarse, grid_coarse)
的Tensor,即指定了Tensor每个维度上的大小,在这个例子中,grid_coarse
指定了每个维度(X、Y、Z维度)的体素数量,因此创建的Tensor是一个立方体形状的3维数组。每个元素的值都初始化为 sigma_init
,
第一行代码:这个Tensor表示粗糙网格中每个体素的初始密度值。
第二行代码:创建一个形状和上一行代码相同的Tensor,但用于存储每个体素在细网格中的索引,初始化所有值为0。
注:Tensor是一个多维数组,Tensor可以包含不同类型的数据。
self.voxels_fine = None
初始化一个用于存储细网格体素信息的属性,初始状态下没有任何细网格体素,所以设置为None
。
self.xyz_min = xyz_min[0]
self.xyz_max = xyz_max[0]
self.xyz_scope = self.xyz_max - self.xyz_min
计算并存储场景在每个维度上的范围。
self.grid_coarse = grid_coarse
self.grid_fine = grid_fine
self.res_coarse = grid_coarse # 粗糙网格分辨率
self.res_fine = grid_coarse * grid_fine # 细网格的分辨率
前两行代码:粗糙网格的边长大小和细网格的边长大小分别赋值给类实例的相应属性,用于候需创建和操作体素网格。
后两行代码: 设置粗糙和细致网格的分辨率。
res_fine
是细网格的分辨率,由粗糙网格的大小乘以细网格的大小计算得出,用于定义在粗糙网格基础上增加的细分层数。(为什么这样计算,保留疑问)
self.dim_sh = 3 * (deg + 1)**2
self.device = device
1.计算并存储球面谐波(Spherical Harmonics)函数维度的大小。这个维度是根据球面谐波的度数deg
来确定的,用于后续处理光照和颜色信息。
2.存储计算设备的信息,该信息用于指定后续操作应该在哪个设备上执行(CPU或GPU)。
2.calc_index_coarse方法:
这个方法 calc_index_coarse
的目的是将三维空间中的点(由其坐标表示)映射到粗糙(coarse)网格中的索引上。这是通过计算点相对于整个场景边界的归一化位置来完成的。
def calc_index_coarse(self, xyz):
这行定义了一个名为 calc_index_coarse
的方法,它接收一个参数 xyz
:
xyz
:一个形状为(N, 3)
的张量,表示N
个点在三维空间中的坐标,其中每个点由其x
、y
、z
坐标组成。
ijk_coarse = ((xyz - self.xyz_min) / self.xyz_scope * self.grid_coarse).long().clamp(min=0, max=self.grid_coarse-1)
return ijk_coarse
xyz - self.xyz_min
:首先,计算每个点的坐标与场景最小边界坐标(xyz_min
)的差,这个操作将点的坐标转换到一个以场景的最小点为原点的坐标系统中。(xyz - self.xyz_min) / self.xyz_scope
:然后,用上一步的结果除以场景在每个维度的总范围(xyz_scope
),进行归一化处理,使得所有值落在[0, 1]
区间内。这一步将点的坐标转换为相对于整个场景的归一化位置。注:self.xyz_scope = self.xyz_max - self.xyz_min(xyz - self.xyz_min) / self.xyz_scope * self.grid_coarse
:接下来,将归一化的坐标乘以粗糙网格的大小(grid_coarse
),将归一化坐标转换为对应于粗糙网格单元的实际索引。.long()
:将得到的浮点数索引值转换为长整型,因为索引必须是整数。.clamp(min=0, max=self.grid_coarse-1)
:使用clamp
函数确保所有计算出的索引都在有效范围内,即[0, grid_coarse-1]
,避免索引超出网格边界。
第二行代码:返回计算出的索引值 ijk_coarse
,它是一个形状为 (N, 3)
的张量,每行代表一个点在粗糙网格中的 i
、j
、k
索引。
3.update_coarse方法:
这个方法的目的是更新粗糙网格中体素的密度值 (sigma
)。这里使用了一个插值方式,通过一个权重参数 (beta
) 来混合原始的密度值和新的密度值。
def update_coarse(self, xyz, sigma, beta):
定义了一个名为 update_coarse
的方法,它接收以下参数:
xyz
:形状为(N, 3)
的张量,表示N
个三维空间中的点的坐标。sigma
:形状为(N,)
的张量,表示与xyz
中每个点对应的新的密度值。beta
:一个标量值,用于控制原始密度值与新密度值之间的插值比例。
ijk_coarse = self.calc_index_coarse(xyz)
调用 calc_index_coarse
方法,将 xyz
中的点转换为它们在粗糙网格中对应的索引。结果 ijk_coarse
是一个形状为 (N, 3)
的张量,其中每一行都表示一个点在粗糙网格中的 (i, j, k)
索引。(用到上一个方法)
self.sigma_voxels_coarse[ijk_coarse[:, 0], ijk_coarse[:, 1], ijk_coarse[:, 2]] \
= (1 - beta) * self.sigma_voxels_coarse[ijk_coarse[:, 0], ijk_coarse[:, 1], ijk_coarse[:, 2]] + \
beta * sigma
这段代码实现了密度值的更新。首先,通过 ijk_coarse
中的索引来定位 sigma_voxels_coarse
中的相应体素。然后,使用以下插值公式来更新这些体素的密度值: 新密度=(1−β)×原始密度+β×新密度。(不熟悉,再看)
(1 - beta) * self.sigma_voxels_coarse[ijk_coarse[:, 0], ijk_coarse[:, 1], ijk_coarse[:, 2]]
:计算原始密度值与(1 - beta)
的乘积,即保留原始密度值的一部分。beta * sigma
:计算新密度值与beta
的乘积,即新密度值的一部分。- 最终,通过这两部分的加和来更新
sigma_voxels_coarse
中对应体素的密度值。这种方式允许新的信息以一定比例beta
影响原始密度值,从而平滑地更新体素的密度。
参数含义和重要性:
xyz
指定了需要更新的点的位置。sigma
为这些点提供了新的密度值。beta
控制着新旧密度值的混合程度,如果beta
接近1,新的密度值将对最终结果有更大的影响;如果beta
接近0,原始的密度值将被大部分保留。
这种更新机制非常适用于动态环境或渐进式学习场景,其中密度信息需要根据新收集的数据不断调整和优化。
4.create_voxels_fine方法:
这个方法的目标是基于已有的粗糙网格(sigma_voxels_coarse
)创建更细致的网格(voxels_fine
)。在这个过程中,只有被认为是有效的粗糙体素(即那些密度值大于0且不等于初始密度值的体素)会被进一步细化。
通过筛选出有效的粗糙体素,并为每个有效体素创建一个新的细体素网格,从而为场景的细致渲染做准备。
def create_voxels_fine(self):
ijk_coarse = torch.logical_and(self.sigma_voxels_coarse > 0, self.sigma_voxels_coarse != self.sigma_init).nonzero().squeeze(1) # (N, 3)
- 首先,使用
torch.logical_and
来选出那些密度值大于0且不等于初始密度值(sigma_init
)的体素,确保只处理已被标记为“存在”且非初始状态的体素。筛选出那些“有效”的体素。
选择密度大于0的体素意味着选出了场景中实际被占用或有特定属性的区域,忽略了那些空白或未被占用的空间。
注:torch.logical_and
是 PyTorch 中的一个函数,用于执行元素级的逻辑与(AND)操作
nonzero()
方法找出这些条件为真的体素的索引,返回一个形状为(N, 3)
的张量,其中N
是满足条件的体素数量,3对应于它们在粗糙网格中的(i, j, k)
位置。.squeeze(1)
用于去除张量中长度为1的维度,但在这个上下文中可能是多余的,因为nonzero()
已经返回了期望的形状。
num_valid = ijk_coarse.shape[0] + 1
# 统计有效体素的数量
index = torch.arange(1, num_valid, dtype=torch.long, device=ijk_coarse.device)
# 生成一个新的有效体素索引的整数序列
self.index_voxels_coarse[ijk_coarse[:, 0], ijk_coarse[:, 1], ijk_coarse[:, 2]] = index
# 将生成的索引赋值给有效体素在粗糙网格索引张量(index_voxels_coarse)中对应的位置
- 第一行代码:计算有效体素的数量,并额外加1,这可能是为了创建一个从1开始的索引数组(下一行中),避免使用0作为有效索引,因为0通常用于表示无效或未初始化的索引。
- 第二行代码:生成一个从1到
num_valid
(不包括num_valid
)的整数序列,作为有效体素的新索引。这些索引稍后将被用来标记粗糙体素中的有效体素。 - 注:
torch.arange(start, end, dtype, device)
:这个函数生成一个从start
到end
(不包括end
),类型为dtype
,存储在指定device
上的一维张量 - 第三行代码:将生成的索引赋值给有效体素在粗糙网格索引张量(
index_voxels_coarse
)中对应的位置,为每个有效的粗糙体素分配一个唯一的新索引。
self.voxels_fine = torch.zeros(num_valid, self.grid_fine, self.grid_fine, self.grid_fine, self.dim_sh+1, device=self.device)
self.voxels_fine[..., 0] = self.sigma_default
self.voxels_fine[..., 1:] = 0.0
- 第一行代码:创建一个新的零张量(
voxels_fine
),用于存储细化后的体素数据。张量的形状设计为[num_valid, grid_fine, grid_fine, grid_fine, dim_sh+1]
,以便为每个有效的粗糙体素分配一个细致网格,其中dim_sh+1
维用于存储每个细体素的密度和可能的其他属性(如光照信息)。 - 第二行代码:初始化
voxels_fine
中所有细体素的密度值为sigma_default
。...
用于选择所有的细体素,0
指定了密度值在最后一个维度中的索引。 - 第三行代码:将除了密度值之外的所有其他属性初始化为0。这里使用
1:
来选择最后一个维度中除了第一个元素(密度)之外的所有元素。
5.calc_index_fine方法:
这个方法的目的是为三维空间中的点计算它们在细化(fine)网格中的索引。该方法将这些点从世界坐标转换为相对于细网格的索引。这对于在细分
体素内部进行精确的数据操作非常重要。
def calc_index_fine(self, xyz):
这行定义了一个名为 calc_index_fine
的方法,它接收一个参数 xyz
:
xyz
:一个形状为(N, 3)
的张量,表示N
个点在三维空间中的坐标
xyz_norm = (xyz - self.xyz_min) / self.xyz_scope
# 将点的坐标转化为场景的归一化位置
xyz_fine = (xyz_norm * self.res_fine).long()
# 将归一化的坐标映射到细网络的尺度
index_fine = xyz_fine % self.grid_fine
# 取模操作,计算每个点在粗糙体素内部的细网格索引
return index_fine
# 返回index_fine
- 首先,计算每个点的坐标与场景最小边界坐标(
self.xyz_min
)之差,然后除以场景在每个维度的总范围(self.xyz_scope
),进行归一化处理。这一步将点的坐标转换为相对于整个场景的归一化位置。 - 接下来,将归一化的坐标乘以细网格的分辨率(
self.res_fine
),这个操作将归一化坐标映射到细网格的尺度上。使用.long()
将结果转换为长整型,因为索引需要是整数。 - 然后,使用取模操作
%
来计算每个点在其所在粗糙体素内部的细网格索引。self.grid_fine
表示每个维度上细网格的大小,取模操作确保了计算出的索引在[0, self.grid_fine-1]
的范围内,这样每个点的索引都对应于它在其所属粗糙体素细分网格内的位置。 - 最后,方法返回
index_fine
,它是一个形状为(N, 3)
的张量,其中每行表示一个点在其对应细网格中的(i, j, k)
索引。
6.update_fine方法:
这个方法用于更新细网格(fine voxel grid)中体素的特征。这些特征包括密度(sigma
)和可能的球谐特征(sh
)
def update_fine(self, xyz, sigma, sh):
定义了 update_fine
方法,它接收三个参数:
xyz
:一个形状为(N, 3)
的张量,代表N
个三维空间中的点。sigma
:一个形状为(N, 1)
的张量,代表与xyz
中每个点对应的密度值。sh
:一个形状为(N, F)
的张量,其中F
是球谐特征的维度,代表与xyz
中每个点对应的球谐特征。
index_coarse = self.query_coarse(xyz, 'index')
# 查询每个点在粗网络中对应的索引
nonzero_index_coarse = torch.nonzero(index_coarse).squeeze(1)
# 找出所有有效体素的索引(位置)
index_coarse = index_coarse[nonzero_index_coarse]
# 根据上一行代码找到的位置再重新找到该位置的索引值
- 调用
query_coarse
方法查询每个xyz
点在粗网格中对应的索引。返回的index_coarse
是一个向量,其中包含xyz
中每个点对应的粗网格索引。 - 使用
torch.nonzero
找出所有非零索引(即有效体素索引),squeeze(1)
去除多余的维度。这一步筛选出了那些实际上存在于粗网格中的点的索引。 - 使用筛选出的非零索引,重新从
index_coarse
中选出对应的索引值。这步确保了之后处理的只是那些有效的体素索引。
ijk_fine = self.calc_index_fine(xyz[nonzero_index_coarse])
# 计算有效体素在细网络中的索引(位置)
feat = torch.cat([sigma, sh], dim=-1)
# 创建一个柏寒密度值和球谐特征的新张量
self.voxels_fine[index_coarse, ijk_fine[:, 0], ijk_fine[:, 1], ijk_fine[:, 2]] = feat[nonzero_index_coarse]
# 更新细网格中相应的体素的特征
- 对于筛选后的
xyz
点(即那些在粗网格中有对应索引的点),计算它们在细网格中的索引。calc_index_fine
方法根据点的位置返回细网格中的(i, j, k)
索引。 - 将密度值(
sigma
)和球谐特征(sh
)沿着最后一个维度(dim=-1
)拼接,创建一个包含所有特征的新张量。这个操作组合了每个点的密度值和对应的球谐特征,为每个点形成一个完整的特征集。结果张量feat
包含了每个点的密度值和球谐特征,这种组合可以用于更复杂的计算和模型训练,如用于神经辐射场 (NeRF) 中的光场建模和渲染。 - 最后,这行代码更新细网格中相应体素的特征,使每个有效体素的密度值和球谐特征都被更新为最新的值。它使用从
index_coarse
和ijk_fine
得到的索引来定位self.voxels_fine
中的体素,并将筛选后的特征(feat[nonzero_index_coarse]
)赋给这些体素。(懵懵) - 注:
- 左侧:这部分代码指向
self.voxels_fine
中具体的体素,这些体素是根据点在粗网格中的索引以及点在细网格中的具体位置确定的 - 右侧:特征赋值。只有那些有效体素的特征会被提取出来用于更新
7.query_coarse方法:
其功能是查询粗糙网格(coarse voxel grid)中给定点的特定属性。
def query_coarse(self, xyz, type='sigma'):
xyz
: 形状为(N, 3)
的张量,代表N
个点在三维空间中的坐标。type
: 一个字符串参数,默认值为'sigma'
。这个参数指定查询的类型,决定返回的是体素的密度值 (sigma
) 还是索引 (index
)
ijk_coarse = self.calc_index_coarse(xyz)
这行调用 calc_index_coarse
方法,传入点的坐标 xyz
。该方法计算并返回这些点在粗糙网格中的索引,索引形式为 (N, 3)
,其中每一行包含一个点的 (i, j, k)
索引。
if type == 'sigma':
# 检查传入的‘type’参数是否为sigma。是:
out = self.sigma_voxels_coarse[ijk_coarse[:, 0], ijk_coarse[:, 1], ijk_coarse[:, 2]]
如果 type
为 'sigma'
,这行代码通过使用 ijk_coarse
中的索引从 self.sigma_voxels_coarse
中提取相应的密度值。self.sigma_voxels_coarse
是一个多维张量,存储了粗糙网格中每个体素的密度值。
else:
# ‘type’不是sigma,应该是index索引
out = self.index_voxels_coarse[ijk_coarse[:, 0], ijk_coarse[:, 1], ijk_coarse[:, 2]]
在这里,如果 type
不是 'sigma'
(通常意味着它应该是 'index'
),则这行代码从 self.index_voxels_coarse
中提取相应体素的索引值。self.index_voxels_coarse
是一个多维张量,存储了每个体素在更细网格中的索引。
return out
返回查询结果 out
。根据 type
的不同,out
可能包含密度值或索引值,形状通常为 (N,)
,其中每个元素对应于输入点集 xyz
中的一个点。
8.query_fine方法:
用于查询细网格(fine voxel grid)中给定点的特性。这个方法的主要用途是在更详细的分辨率层面上获取体素数据。
def query_fine(self, xyz):
xyz
: 形状为 (N, 3)
的张量,代表 N
个点在三维空间中的坐标。这是方法的输入参数,用于指定要查询的空间点的位置。
index_coarse = self.query_coarse(xyz, 'index')
这行代码调用 query_coarse
方法,传入点的坐标 xyz
和参数 'index'
。该方法计算并返回这些点在粗糙网格中的索引(位置)。这里的 'index'
参数指定方法应返回粗糙网格的体素索引,而不是体素的密度值。
ijk_fine = self.calc_index_fine(xyz)
这行调用 calc_index_fine
方法,传入同样的点坐标 xyz
。该方法计算并返回这些点在细网格中的索引(位置)。返回的 ijk_fine
是形状为 (N, 3)
的张量,其中每一行包含一个点的 (i, j, k)
索引,指示点在其所在粗糙体素的细网格内的位置。
return self.voxels_fine[index_coarse, ijk_fine[:, 0], ijk_fine[:, 1], ijk_fine[:, 2]]
- 这一行是方法的返回语句,它使用从
index_coarse
和ijk_fine
计算得到的索引来访问self.voxels_fine
(存储细网格体素信息的属性) 张量。self.voxels_fine
是一个多维张量,存储了细网格中每个体素的数据(例如密度、光照数据等)。 index_coarse
提供了每个查询点所属的粗糙体素的索引。ijk_fine[:, 0], ijk_fine[:, 1], ijk_fine[:, 2]
分别提取ijk_fine
中每个点的i
,j
,k
索引,用来定位细网格内的具体体素位置。
EfficientNeRFSystem类:
构建和训练一个高效的神经辐射场(NeRF)模型
1.初始化类的函数:
class EfficientNeRFSystem(LightningModule):
这行代码定义了一个名为 EfficientNeRFSystem
的类,它继承自 LightningModule
,LightningModule
是 PyTorch Lightning 框架中的一个核心组件,用于简化训练流程和代码组织。
def __init__(self, hparams):
这是 EfficientNeRFSystem
类的构造函数,接收一个参数 hparams
,这通常是一个包含各种超参数的命名空间或字典。
super(EfficientNeRFSystem, self).__init__()、
# 这行调用父类 LightningModule 的构造函数,是类继承中常见的做法,确保父类被正确初始化。
self.save_hyperparameters(hparams)
使用 PyTorch Lightning 的 save_hyperparameters
方法,自动保存传入的超参数,便于后续在模型保存和加载时保持配置一致。
self.loss = loss_dict[hparams.loss_type]()
从一个预定义的字典 loss_dict
中获取指定类型的损失函数。hparams.loss_type
指定了使用哪种损失函数。
self.embedding_xyz = Embedding(3, 10) # 这意味着在构建这个嵌入层时,选择的输出维度10是一个默认的、通常使用的设置。这个数值可能是基于经验或先前实验确定的,用于平衡模型的复杂度和性能。
# 将输入的三维空间位置映射到10维的特征向量
self.embedding_dir = Embedding(3, 4) # 4 is the default number
# 将输入的三维方向向量映射到4维的特征向量
初始化两个嵌入层,embedding_xyz
和 embedding_dir
,分别用于空间位置和方向的特征嵌入。这些嵌入层将三维向量映射到更高维的空间,以增强模型的表达能力。将低维的空间坐标和方向向量转换为高维特征向量
- 第一句:用于处理空间坐标相关的数据。这个嵌入层将输入的三维向量(具有3个元素)映射到一个10维的特征向量。在这里,3表示输入特征的维度(通常对应于空间坐标的x, y, z),而10表示输出特征的维度。
- 第二句:专门用于处理方向向量相关的数据。这个嵌入层将输入的三维方向向量映射到一个4维的特征向量。同样,3表示输入特征的维度(可能是方向的角度或其他表示形式),而4表示输出特征的维度。
self.embeddings = [self.embedding_xyz, self.embedding_dir]
将嵌入层存储在一个列表中,以便于管理和使用。
self.deg = 2
# 设置球谐函数的度(degree)为2
self.dim_sh = 3 * (self.deg + 1)**2
# 计算了基于当前球谐函数度数的维度(dim_sh)
第一行代码:代码设置球谐函数的度(degree)为2。在球谐函数中,“度”是一个重要的概念,它决定了函数的复杂度和细节级别。度数越高,能表示的光照和颜色细节就越丰富,但相应地计算成本也更高。这里选择的度数2是一个比较常见的选择,它提供了一个在复杂度和性能之间较好的平衡。
第二行代码:计算了基于当前球谐函数度数的维度(dim_sh
)。计算公式 3 * (self.deg + 1)**2
是从球谐函数的数学表达式衍生出来的。具体来说,对于给定的度 d
,球谐函数的项数(即维度)是 (d + 1)^2
。这包括了所有从0度到 d
度的球谐项。因此,对于 deg = 2
:
(2 + 1)^2 = 3^2 = 9
- 因此,球谐函数会有9个独立的项。
- 乘以3的原因是每个方向(x, y, z 或 RGB颜色通道)可能都需要这些系数来描述场景中的光照和颜色。这就使得总维度变为
3 * 9 = 27
。
1.粗糙NeRF模型:
self.nerf_coarse = NeRF(D=4, W=128, in_channels_xyz=63, in_channels_dir=27, skips=[2], deg=self.deg)
# 粗糙NeRF模型
self.models = [self.nerf_coarse]
# 创建一个列表,并将上面创建的粗糙NeRF模型存储在列表
这两行代码初始化和管理一个粗糙的神经辐射场(NeRF)模型,并将其存储在类属性中以便使用。
第一行代码:self.nerf_coarse是一个类的属性,用于存储初始化后的粗糙NeRF模型对象。NeRF(...)
:这调用了一个名为 NeRF
的类或函数,用于创建一个NeRF模型实例:
D=4
:表示网络的深度。这里的深度为4,意味着网络包含4层。对于粗糙模型,深度通常较浅,以减少计算成本并快速提供大范围的场景概述。W=128
:指定每层网络的宽度(即每层的神经元或单元数量)。这里宽度为128,它决定了模型处理信息的能力。in_channels_xyz=63
:定义处理空间坐标(xyz)输入的特征(或通道)数量。这个数量(63)可能是通过某种特征提取或嵌入技术从3维坐标增加的,可能与位置编码或其他预处理步骤有关。in_channels_dir=27
:定义处理方向(方向性光照或视角)输入的特征数量。同样,这个数值(27)表示从原始方向数据(通常是3维)增加的特征数量,通常通过一些形式的编码实现。skips=[2]
:定义在网络中哪些层后添加跳跃连接。这里列表[2]
表示在第二层后添加跳跃连接,这有助于改善梯度流和训练稳定性,也可以帮助网络更好地学习和重建复杂场景。deg=self.deg
:将类属性self.deg
(控制球谐函数的度数,从而影响光照和颜色处理的复杂度)传递给模型,确保模型能以适当的复杂度处理光照变化。
第二行代码:创建一个列表,名为 self.models
,并将之前创建的 self.nerf_coarse
模型对象存储在这个列表中。这样做的目的是将所有的模型(粗糙和可能的细化模型)组织在一起,便于管理和迭代访问,特别是在训练过程中或当需要对多个模型进行相同的操作时。
2.细化NeRF模型:
if hparams.N_importance > 0:
# N_importance 是一个用于指定场景重要性采样步骤数的参数
self.nerf_fine = NeRF(D=8, W=256, in_channels_xyz=63, in_channels_dir=27, skips=[4], deg=self.deg)
# 细化的NeRF模型
self.models += [self.nerf_fine]
# 将细化的NeRF模型加入model列表
N_importance
是一个用于指定场景重要性采样步骤数的参数。重要性采样是一种在光线追踪和渲染中常用的技术,用于提高图像质量,尤其是在处理复杂光照和阴影的情况下。如果 N_importance
大于0,意味着模型配置要求进行重要性采样,因此需要一个额外的细化模型来处理这些额外的采样步骤。
在条件为真的情况下,这行代码初始化一个细化的 NeRF 模型。参数解释如下:
D=8
:定义了网络的深度为8,即模型有8层深度。W=256
:设置网络的宽度为256,即每一层有256个神经元。in_channels_xyz=63
和in_channels_dir=27
:分别指定了空间坐标和方向坐标的输入通道数。这些参数与之前嵌入层的输出维度一致,确保输入特征与嵌入层的输出相匹配。skips=[4]
:指定在网络中哪些层后应添加跳跃连接。这里,第4层后添加跳跃连接,有助于改善梯度流动和训练稳定性。deg=self.deg
:使用与粗糙模型相同的球谐度数,确保光照模型的一致性。
将新创建的 self.nerf_fine
模型添加到 self.models
列表中。self.models
是一个用于存储所有 NeRF 模型(粗糙和细化)的列表,方便管理和访问。通过将细化模型添加到此列表,可以在训练和推理过程中统一处理所有模型,例如在批处理数据时一起调用它们。
self.sigma_init = hparams.sigma_init
self.sigma_default = hparams.sigma_default
用于初始化体素密度的值 (sigma_init
) 和默认密度值 (sigma_default
)。
coord_scope = hparams.coord_scope
从超参数中获取坐标范围 (coord_scope
),用于定义场景的空间边界。
self.nerf_tree = NerfTree_Pytorch(xyz_min=[-coord_scope, -coord_scope, -coord_scope],
xyz_max=[coord_scope, coord_scope, coord_scope],
grid_coarse=384,
grid_fine=3,
deg=self.deg,
sigma_init=self.sigma_init,
sigma_default=self.sigma_default, device='cuda')
这行代码非常关键,因为它设定了 NeRF 模型空间数据管理的基础架构。
xyz_min=[-coord_scope, -coord_scope, -coord_scope]
: 这是一个列表,定义了空间体素网格的最小坐标边界。-coord_scope
是一个超参数,指定了每个维度上的最小坐标值,通常这个值设定为负的,使得网格可以中心对称地围绕原点展开。xyz_max=[coord_scope, coord_scope, coord_scope]
: 类似于xyz_min
,这是一个列表,定义了空间体素网格的最大坐标边界。coord_scope
是一个超参数,指定了每个维度上的最大坐标值。grid_coarse=384
: 这个参数指定了粗糙网格的分辨率。384
表示在每个维度上,粗糙网格被划分为 384 个体素。这个分辨率直接影响到体素的总数以及NeRF模型的空间解析能力。grid_fine=3
: 这个参数指定了每个粗糙体素被进一步细分的程度。3
表示每个粗糙体素在每个维度上都被细分为3个更小的体素,从而增加了细节的捕捉能力。deg=self.deg
:deg
是用于计算或表示光照和其他效果的球面谐波函数的度数。它在计算球面谐波相关特征时被用作参数。sigma_init=self.sigma_init
:sigma_init
是初始化体素密度值的参数。这个值用于初始化那些还未通过模型训练具体更新过的体素。sigma_default=self.sigma_default
:sigma_default
是设置默认体素密度的参数,用于那些在初始化后可能仍需要默认值的情形。device='cuda'
: 指定NerfTree_Pytorch
对象应该在哪个设备上运行。在这里,'cuda'
指的是使用 NVIDIA 的 GPU 来加速计算。这对于处理大量数据和复杂计算的NeRF模型是非常重要的。
os.makedirs(f'logs/{self.hparams.exp_name}/ckpts', exist_ok=True)
self.nerftree_path = os.path.join(f'logs/{self.hparams.exp_name}/ckpts', 'nerftree.pt')
第一行代码:这行代码确保了所需的目录结构存在,使得模型训练过程中产生的数据(如检查点文件)可以被正确地保存到指定位置。存储模型检查点的目录
第二行代码:设置检查点文件的路径。
if self.hparams.ckpt_path != None and os.path.exists(self.nerftree_path):
# 检查是否提供了检查点路径 ,即上述两行代码所创建的文件路径(ckpt_path)
# 并且该路径下的检查点文件 ,即上述代码所创建的文件路径(self.nerftree_path) 是否存在
voxels_dict = torch.load(self.nerftree_path)
# 如果文件存在,这行代码使用torch.load 函数加载检查点文件
self.nerf_tree.sigma_voxels_coarse = voxels_dict['sigma_voxels_coarse']
# 从 voxels_dict 字典中提取键为 'sigma_voxels_coarse' 粗糙体素网格密度的值
第二句代码:torch.load
读取文件并将其内容反序列化成一个字典(或其他Python对象),这里存储到变量 voxels_dict
中。
第三句:从 voxels_dict
字典中提取键为 'sigma_voxels_coarse'
的值,这个值包含了粗糙体素网格的密度信息。然后,将这些密度数据赋值给 self.nerf_tree.sigma_voxels_coarse
,更新 NerfTree_Pytorch
实例中管理的粗糙网格体素的密度状态。
其中的参数:
self.nerf_tree.sigma_voxels_coarse
:NerfTree_Pytorch
实例中用于存储粗糙网格体素密度的属性。voxels_dict['sigma_voxels_coarse']
:从文件中加载的密度数据,这些数据在之前的训练过程中可能已被计算并保存
self.xyz_min = self.nerf_tree.xyz_min
# 最小空间坐标界限
self.xyz_max = self.nerf_tree.xyz_max
# 最大空间坐标界限
self.xyz_scope = self.nerf_tree.xyz_scope
# 场景空间的范围
self.grid_coarse = self.nerf_tree.grid_coarse
# 粗网格大小(空间分辨率),粗网络在每个维度上的体素(网格单元)数
self.grid_fine = self.nerf_tree.grid_fine
# 细网格大小(空间分辨率),每个粗网络体素内的细网格在每个维度上的体素数
self.res_coarse = self.nerf_tree.res_coarse
# 整个粗网络的分辨率,整个粗网格的维度
self.res_fine = self.nerf_tree.res_fine
# 真个细网络的分辨率,通常是 grid_coarse 乘以 grid_fine,表示整个细网格的维度
空间分辨率和分辨率的区别:
- 空间分辨率主要指的是一个三维空间模型中能够区分的最小的空间单位的大小,
grid_coarse
和grid_fine
参数直接影响了模型可以表示的空间细节的级别。粗网格有较低的空间分辨率,意味着每个体素覆盖的实际空间区域更大;而细网格有较高的空间分辨率,意味着每个体素覆盖的实际空间区域更小,可以捕捉更细微的空间变化。 - 分辨率是一个更广泛的术语,可以用来描述任何类型的数据集中单元格或像素的数量,在三维建模中,分辨率通常泛指网格或体素在三维空间中的分布密度,这与空间分辨率类似,但更偏向于描述整体的数据密度,而不局限于空间的具体区域。
2.decode_batch方法:
这个方法的作用是从输入的批次数据中提取出射线(rays)和对应的RGB颜色值(rgbs)。
def decode_batch(self, batch):
这行定义了一个名为 decode_batch
的方法,它接收一个参数 batch
。这个 batch
通常是一个字典,包含了批量处理中每个样本的数据,如射线信息和颜色值等。
rays = batch['rays'] # (B, 8) B:表示有多少条射线一同被处理,8:每条射线处理8个元素
# 从batch字典中提取键为rays的数据,包含射线的起点和方向信息
rgbs = batch['rgbs'] # (B, 3)
# 从batch字典中提取键为rgbs的数据,包含每个射线对应的RGB颜色值
return rays, rgbs
# 返回两个变量值
'rays'
通常包含了射线的起点和方向信息,对于神经辐射场(NeRF)模型,每个射线还可能包括附加的参数,如射线的近点和远点距离等,总共有 8 个元素。因此,rays
的形状是(B, 8)
,其中B
是批次大小,表示有多少射线被同时处理。'rgbs'
包含了每个射线对应的RGB颜色值。这是神经辐射场(NeRF)在训练时用来与模型输出进行比较的真实颜色数据。每个RGB颜色由三个分量组成(红、绿、蓝),因此rgbs
的形状是(B, 3)
。
3.sigma2weights方法:
这个方法用于计算体素密度值转换成采样权重的过程,是渲染过程中重要一个步骤。
def sigma2weights(self, deltas, sigmas):
方法定义:定义了一个名为 sigma2weights
的方法,接收两个参数:deltas
和 sigmas
。
deltas
:形状为(N_rays, N_samples_)
的张量,表示在光线上连续采样点之间的距离。这些距离用于计算沿射线的累积透明度(或体积密度的积分)。sigmas
:形状为(N_rays, N_samples_)
的张量,代表各样本点的密度估计值。密度用于计算每个采样点处的不透明度,进一步用于体积渲染。
noise = torch.randn(sigmas.shape, device=sigmas.device)
# 生成一个随机噪声张量
sigmas = sigmas + noise
# 将张量添加到密度张量上
第一行代码:torch.randn
生成的是均值为0、标准差为1的正态分布随机数。训练过程中引入随机性,帮助模型更好地泛化并避免过拟合。
第二行代码:将前面生成的噪声张量添加到密度估计张量 sigmas
上。位密度估计值引入一定的随机扰动,称为数据增强。增强鲁棒性。
alphas = 1-torch.exp(-deltas*torch.nn.Softplus()(sigmas)) # (N_rays, N_samples_)
# 使用Softplus函数确保密度值非负
Softplus
是一个平滑的、非线性的激活函数,形式为 log(1+exp(x)),确保输出始终为正,适合处理密度估计,这在物理意义上更合理(密度不应为负)。这里用 Softplus
替代 ReLU
可以提供更平滑的梯度行为
使用处理过的 sigmas
和 deltas
来计算 alphas
,公式依然是 α=1−exp(−δ⋅σ)。
alphas_shifted = torch.cat([torch.ones_like(alphas[:, :1]), 1-alphas+1e-10], -1) # [1, a1, a2, ...]
# 计算沿射线的累积权重
用于计算从光线入射点到每个采样点之间的累积透明度,以便计算最终每个采样点的贡献权重。逐点计算累计透明度。(不熟悉,懵)
alphas_shifted
张量的构成:[1, a1, a2, ...]
,其中 1
表示每条光线初始的透明度(完全透明),a1, a2, ...
表示从第一个采样点到最后一个采样点的透明度。这个张量用于计算光线在介质中传播时的累积透明度,对每个采样点的权重计算至关重要。
weights = alphas * torch.cumprod(alphas_shifted, -1)[:, :-1] # (N_rays, N_samples_)
# 计算最终权重
- 计算最终的权重。
- 用
torch.cumprod
沿最后一个维度(即每条射线的采样点上)计算alphas_shifted
的累积乘积,表示从光线起点到当前采样点之前所有透明度的乘积。 -
alphas * torch.cumprod(alphas_shifted, -1)[:, :-1]
计算每个采样点的权重,该权重是该点的不透明度乘以到达该点之前的累积透明度。使用[:, :-1]
是为了排除最后一个累积乘积,因为它是在所有采样处理后计算的。
return weights, alphas
# 返回计算得到的权重(weights)和每个采样点的不透明度(alphas)
4.render_rays方法:
这个方法用于渲染光线并计算与这些光线相关的不同属性,如颜色、深度等。这是一个复杂的过程,涉及粗糙和细化两级体素的处理。
def render_rays(self, models, embeddings, rays, N_samples=64, use_disp=False, noise_std=0.0, N_importance=0, chunk=1024*32, white_back=False):
- models: 一个包含NeRF模型的列表,通常包括一个粗糙模型和一个细化模型。
- embeddings: 一个包含嵌入函数的列表,用于将输入特征转换成更高维的表示。
- rays: 射线数据,通常包含光线的起点和方向。
- N_samples: 在每条射线上进行采样的点的数量。
- use_disp: 是否使用视差(dispersal)代替距离来计算采样点。
- noise_std: 在模型预测中加入的噪声标准差。
- N_importance: 用于重要性采样的额外采样点数。
- chunk: 为了内存效率,处理数据的批大小。
- white_back: 是否在背景是白色的情况下渲染图像。
(1)inference(光线处理和体素查询,对给定的光线进行模型推断)函数:
def inference(model, embedding_xyz, xyz_, dir_, dir_embedded, z_vals, idx_render):
- 内部函数,用于对给定的光线进行模型推断。
- model: 要使用的NeRF模型。
- embedding_xyz: 空间位置的嵌入函数。
- xyz_: 采样点的空间位置。
- dir_: 光线的方向。
- dir_embedded: 方向的嵌入表示。
- z_vals: 光线上的深度值。
- idx_render: 渲染索引,指示哪些采样点将被用于模型推断。
N_samples_ = xyz_.shape[1]
# 获取每条射线的采样点数量
xyz_ = xyz_[idx_render[:, 0], idx_render[:, 1]].view(-1, 3)
# 根据渲染索引调整采样点位置的形状,以供模型使用。
第二句代码:用于重构和选择渲染所需的采样点的空间位置。
- idx_render 是一个形状为
(M, 2)
的索引张量,其中M
是有效采样点的数量。这个张量的每一行包含两个整数,分别代表要参与渲染的光线的索引和该光线上的采样点的索引。它用于从所有采样点中选择参与进一步计算的特定采样点。 - idx_render[:, 0] 和 idx_render[:, 1] 分别提取所有行的第一个和第二个元素,这些元素代表光线索引和该光线上的采样点索引。
- 使用这两个数组作为索引,xyz_[idx_render[:, 0], idx_render[:, 1]] 选择了所有有效采样点的坐标。这个操作实质上是从
xyz_
张量中挑选出由idx_render
指定的特定采样点的坐标。 .view(-1, 3)
是一个张量重塑操作,用于将选定的采样点坐标转换成新的形状。
view_dir = dir_.unsqueeze(1).expand(-1, N_samples_, -1)
view_dir = view_dir[idx_render[:, 0], idx_render[:, 1]]
这两句代码的作用是:用于准备和选择用于模型推断的光线方向数据。
B = xyz_.shape[0]
# 所有采样点总数
out_chunks = []
# 初始化了一个空列表
for i in range(0, B, chunk): #从 0 到 B,步长为 chunk
out_chunks += [model(embedding_xyz(xyz_[i:i+chunk]), view_dir[i:i+chunk])]
# 计算从当前批次的采样点得到的输出(如颜色和密度)。
out = torch.cat(out_chunks, 0)
- B: 这行代码定义了变量
B
,它是从张量xyz_
的第一个维度大小中获取的,代表所有采样点的总数。在这个上下文中,xyz_
包含所有光线上的采样点坐标,它的形状是(N_rays * N_samples, 3)
,因此B
实际上是N_rays
(射线数量)乘以N_samples
(每条射线上的采样点数)。 embedding_xyz
是位置嵌入函数,用于将空间坐标xyz_
转换成更高维的表示以输入到模型中。view_dir[i:i+chunk]
是对应批次的视角方向数据。- out: 这行代码使用
torch.cat
函数将所有批次的输出张量拼接在一起,形成一个连续的张量。参数0
指定了拼接的维度(第一个维度),这意味着它会将每个批次的输出垂直堆叠起来,形成一个完整的输出张量,包含所有采样点的渲染结果。
这段代码的关键在于实现了模型的批量处理,以便在资源有限的情况下有效地处理大量数据。
体积渲染计算:
out_rgb = torch.full((N_rays, N_samples_, 3), 1.0, device=device)
# 初始化存储每个采样RGB颜色值的张量
out_sigma = torch.full((N_rays, N_samples_, 1), self.sigma_default, device=device)
# 初始化存储每个采样的密度值
out_sh = torch.full((N_rays, N_samples_, self.dim_sh), 0.0, device=device)
# 存储每个采样点的球谐系数
out_defaults = torch.cat([out_sigma, out_rgb, out_sh], dim=2)
# 把上述三个变量拼接在一个张量中
out_defaults[idx_render[:, 0], idx_render[:, 1]] = out
- 以上代码的作用:初始化输出张量,然后将模型的推断结果填入相应位置。
- out_rgb: 初始化一个形状为
(N_rays(
光线的数量), N_samples_(
每条光线上的采样点数量), 3(
RGB颜色空间的三个通道(红、绿、蓝)))
的张量,每个元素填充为1.0
。这个张量用于存储渲染过程中每个采样点的RGB颜色值。 - out_sigma: 初始化一个形状为
(N_rays, N_samples_, 1(
每个采样点的密度值))
的张量,每个元素填充为self.sigma_default(
默认的密度值)
。这个张量用于存储每个采样点的密度估计值。 - out_sh: 初始化一个形状为
(N_rays, N_samples_, self.dim_sh)
的张量,每个元素填充为0.0
。这个张量用于存储每个采样点的可能的额外属性,如球谐系数等。self.dim_sh: 代表每个采样点的额外属性或特征的维度。 - out_defaults: 将
out_sigma
,out_rgb
, 和out_sh
沿着最后一个维度(dim=2)拼接起来。这样每个采样点的所有相关信息(密度、颜色、额外属性)都存储在同一个张量中 - 将模型推断结果
out
填充到out_defaults
的指定位置。idx_render[:, 0]: 选择需要更新的光线索引。idx_render[:, 1]: 选择对应光线的采样点索引。
deltas = z_vals[:, 1:] - z_vals[:, :-1]
# 计算沿光线方向各个采样点之间的距离
delta_inf = 1e10 * torch.ones_like(deltas[:, :1])
deltas = torch.cat([deltas, delta_inf], -1)
# 这一行将 delta_inf 添加到 deltas 的末尾。
weights, alphas = self.sigma2weights(deltas, sigmas)
# 传入更新后的 deltas 和 sigmas(密度估计值)。
- 计算各采样点之间的距离(
deltas
),并为最后一个采样点到无穷远处添加一个极大值,用于计算体积渲染的权重和透明度。z_vals[:, 1:]
和z_vals[:, :-1]
分别是从第二个元素到最后一个元素和从第一个元素到倒数第二个元素的切片。这种操作提取了所有采样点对(即每个连续的两个点),并计算它们之间的距离。 - delta_inf: 这是一个非常大的数,用于表示最后一个采样点到理论上的无穷远点的距离。这样做是为了在最后一个采样点后不会有任何进一步的贡献被意外截断,模拟光线在空间中无限传播的物理现象。
weights_sum = weights.sum(1)
# 计算每天光线上所有采样点的权重值
rgb_final = torch.sum(weights.unsqueeze(-1)*rgbs, -2)
# 计算每条光线的最终的RGB颜色
depth_final = torch.sum(weights*z_vals, -1)
# 计算每条光线的加权的平均深度值
- 计算累积权重和,最终的RGB颜色和深度值。
if white_back:
# 判断是否启用白色背景选项(white_back=True)
rgb_final = rgb_final + 1-weights_sum.unsqueeze(-1)
- 如果设置了白色背景,则根据未吸收的光线比例调整最终颜色。
-
rgb_final: 这是一个张量,包含从模型中计算得到的每条射线的最终RGB颜色值。尺寸通常为
(N_rays, 3)
,其中N_rays
是光线的数量,每个光线有对应的RGB颜色向量。 -
rgb_final + 1 - weights_sum.unsqueeze(-1): 这个操作将每条射线上未被吸收的光线部分的亮度增加到
rgb_final
。因为在RGB颜色模型中,白色可以表示为(1, 1, 1)
,所以这种方式实际上是将那些未被完全吸收的光线部分补足为白色,从而创建了白色背景的效果。这种方法在视觉上增加了背景的亮度,尤其是在场景中的介质没有完全吸收所有光线时。 -
.unsqueeze(-1): 这个操作将
weights_sum
张量从(N_rays,)
扩展到(N_rays, 1)
。这是为了让其在与rgb_final
进行算术操作时能够广播到每个颜色通道。 -
1 - weights_sum.unsqueeze(-1): 这个表达式计算了每条射线上未被吸收的光线部分。理论上,如果
weights_sum
的值是1,表示该射线上的光线完全被吸收;如果小于1,那么1 - weights_sum
就表示未被场景中的介质吸收的光线比例。 -
rgb_final + 1 - weights_sum.unsqueeze(-1): 这个操作将每条射线上未被吸收的光线部分的亮度增加到
rgb_final
。因为在RGB颜色模型中,白色可以表示为(1, 1, 1)
,所以这种方式实际上是将那些未被完全吸收的光线部分补足为白色,从而创建了白色背景的效果。这种方法在视觉上增加了背景的亮度,尤其是在场景中的介质没有完全吸收所有光线时。
return rgb_final, depth_final, weights, sigmas, shs
以上方法是NeRF渲染流程中的核心,处理从光线追踪到体积渲染的整个过程,确保可以生成高质量的3D渲染图像。
提取模型和嵌入:
model_coarse = models[0]
# 从模型的列表中提取粗糙渲染模型
embedding_xyz = embeddings[0]
# 从嵌入函数列表中提取位置数据的嵌入
device = rays.device
# 用GPU还是CPU
is_training = model_coarse.training
# 判断模型是否处于训练模式
光线数据的分解:
N_rays = rays.shape[0]
# 光线数量,由光线rays的第一维度给出
rays_o, rays_d = rays[:, 0:3], rays[:, 3:6] # both (N_rays, 3)
rays_o:每条光线的起点
rays_d:每条光线的方向
光线采样和坐标变换:
N_samples_coarse = self.N_samples_coarse
# 每条光线上进行粗糙采样的样本数
z_vals_coarse = self.z_vals_coarse.clone().expand(N_rays, -1)
# 深度值数组
if is_training:
# 判断是否为训练阶段
delta_z_vals = torch.empty(N_rays, 1, device=device).uniform_(0.0, self.distance/N_samples_coarse)
# 创建随机深度偏移
z_vals_coarse = z_vals_coarse + delta_z_vals
# 更新深度值
xyz_sampled_coarse = rays_o.unsqueeze(1) + rays_d.unsqueeze(1) * z_vals_coarse.unsqueeze(2)
- 根据起点、方向和深度值计算每个采样点的三维坐标。
xyz_coarse = xyz_sampled_coarse.reshape(-1, 3)
- 将采样点的坐标重塑为二维张量,以便进一步处理。
查询粗糙体素数据:
sigmas = self.nerf_tree.query_coarse(xyz_coarse, type='sigma').reshape(N_rays, N_samples_coarse)
- 查询每个采样点的密度估计值。使用
NerfTree_Pytorch
的方法query_coarse
从粗糙网格中获取密度数据,然后重塑以匹配光线和采样点的维度。
处理采样点数据并进行推断:
1.根据模型训练阶段(预热/非预热)选择有效的采样点
if is_training and self.nerf_tree.voxels_fine == None:
# 判断是否处于训练阶段,且属于粗糙体素网格的训练
with torch.no_grad():
# 无梯度操作
sigmas[torch.rand_like(sigmas[:, 0]) < self.hparams.uniform_ratio] = self.sigma_init
# 随机初始化部分密度值
if self.hparams.warmup_step > 0 and self.trainer.global_step <= self.hparams.warmup_step:
# 选择有效的采样点。判断是否仍处于预热阶段内
idx_render_coarse = torch.nonzero(sigmas >= -1e10).detach()
# 在预热阶段内所有的采样点都被视为有效的
else:
# 预热阶段结束
idx_render_coarse = torch.nonzero(sigmas > 0.0).detach()
# 预热期结束后,只有密度值>0才被视为有效点
with torch.no_grad():
是一个在 PyTorch 中常用的上下文管理器,用于临时关闭梯度计算。- 预热步骤(warmuo_step):指在训练初期采用的一种技术,目的是逐渐将模型引入到全面训练状态。这种方法通常用于优化过程,特别是当涉及到复杂的优化算法或需要精细调控的学习率调整时。
- 预热阶段结束后,只有密度值(sigma)大于0的点视为有效点,才能进一步被处理。意味着只关注对渲染结果有贡献的区域
- torch.nonzero(...).detach(): 找出满足条件的索引,并通过
.detach()
从当前计算图中分离出来,这意味着这些索引将不参与梯度计算。
2.
- 调用
inference
函数进行粗糙级别的渲染推断,获取颜色、深度和权重等数据。
rgb_coarse, depth_coarse, weights_coarse, sigmas_coarse, _ = \
inference(model_coarse, embedding_xyz, xyz_sampled_coarse, rays_d, dir_embedded, z_vals_coarse, idx_render_coarse)
(仍处于训练阶段的if)
存储渲染结果:
(仍处于训练阶段的if)
result['rgb_coarse'] = rgb_coarse
# 每条光线在粗糙渲染级别上的颜色值,尽管这个值是从采样点计算得出的,但最终结果是通过对所有采样点的贡献进行加权平均后得到的,因此是针对每条光线的。
result['z_vals_coarse'] = self.z_vals_coarse
# 采样点在每条光线上采样的深度值。表示每个采样点在光线上的位置
result['depth_coarse'] = depth_coarse
# 每条光线的深度估计值
result['sigma_coarse'] = sigmas_coarse
# 每个采样点的深度估计值
result['weight_coarse'] = weights_coarse
# 每个点的权重值
result['opacity_coarse'] = weights_coarse.sum(1)
# 每条光线的总不透明度,通过对每条光线上所有采样点的权重进行求和得出。它表示了光线穿过介质后的累积吸收。
result['num_samples_coarse'] = torch.FloatTensor([idx_render_coarse.shape[0] / N_rays])
# 平均每条光线用于渲染的有效采样点数量。
更新粗糙体素数据:
(仍处于训练阶段的if)
xyz_coarse_ = xyz_sampled_coarse[idx_render_coarse[:, 0], idx_render_coarse[:, 1]]
# 光线上每个采样点的三维坐标值
sigmas_coarse_ = sigmas_coarse.detach()[idx_render_coarse[:, 0], idx_render_coarse[:, 1]]
# 每个采样点的密度值
self.nerf_tree.update_coarse(xyz_coarse_, sigmas_coarse_, self.hparams.beta)
# 更新数据信息
接下来跳出if:
处理粗糙阶段的深度间隔和权重:
with torch.no_grad():
# 无梯度操作
deltas_coarse = z_vals_coarse[:, 1:] - z_vals_coarse[:, :-1] # (N_rays, N_samples_-1)
# 光线上相邻采样点的距离差
delta_inf = 1e10 * torch.ones_like(deltas_coarse[:, :1]) # (N_rays, 1) the last delta is infinity
# 在每条光线的末端添加一个极大值,模拟到无穷远距离,保证最后一个采样点正确处理
deltas_coarse = torch.cat([deltas_coarse, delta_inf], -1) # (N_rays, N_samples_)
# 将计算的无穷远距离 delta_inf 添加到 deltas_coarse 的最后,使得每条射线的采样点包括了到无穷远的距离。
weights_coarse, _ = self.sigma2weights(deltas_coarse, sigmas)
# 用sigma2weights方法计算基于密度和距离差的权重
weights_coarse = weights_coarse.detach()
# 将 weights_coarse 从当前计算图中分离出来,这意味着对 weights_coarse 的后续操作将不会影响梯度计算。