3D Gaussian Splatting代码中的Gaussian_Module和Cameras两个类的代码解读

Gaussian_model

讨论Gaussian_model这个类,是因为里面包含了三维高斯分布的基本信息,里面定义了各种参量的构建方式、用于优化学习的激活函数、学习率设置方法和高斯点优化过程中的增加与删除方式及对应优化器的处理方法。这个类定义在scene文件夹中的gaussian_module.py文件里。

scene/gaussian_model中进行函数构建

在这里插入图片描述

协方差矩阵和各种参数的激活函数

首先这个方法构建了协方差矩阵,而其中旋转、缩放矩阵在以下文件中:

utils/general_utils的build_rotation中构建旋转矩阵

'''
用来计算四元数表示的旋转矩阵(Rotation Matrix)的。。首先,通过计算每个四元数的模长,来标准化它们。这是为了确保旋转向量(四元数)的单位长度,以便于正确地进行旋转计算。接着,将标准化后的四元数应用到旋转矩阵上。这里,代码创建了一个大小为 (batch_size, 3, 3) 的零张量 R,然后,通过四元数的各个分量进行矩阵赋值,根据四元数到旋转矩阵的转换公式来填充这个张量。最后,返回填充完的旋转矩阵 R。需要注意的是,这段代码是针对批处理的,因此输入 r 是一个张量,其中每一行代表一个四元数。
'''
def build_rotation(r):norm = torch.sqrt(r[:,0]*r[:,0] + r[:,1]*r[:,1] + r[:,2]*r[:,2] + r[:,3]*r[:,3])q = r / norm[:, None]R = torch.zeros((q.size(0), 3, 3), device='cuda')r = q[:, 0]x = q[:, 1]y = q[:, 2]z = q[:, 3]R[:, 0, 0] = 1 - 2 * (y*y + z*z)R[:, 0, 1] = 2 * (x*y - r*z)R[:, 0, 2] = 2 * (x*z + r*y)R[:, 1, 0] = 2 * (x*y + r*z)R[:, 1, 1] = 1 - 2 * (x*x + z*z)R[:, 1, 2] = 2 * (y*z - r*x)R[:, 2, 0] = 2 * (x*z - r*y)R[:, 2, 1] = 2 * (y*z + r*x)R[:, 2, 2] = 1 - 2 * (x*x + y*y)return R

utils/general_utils的build_scaling_rotation中构建缩放矩阵并将旋转矩阵和缩放矩阵合并

def build_scaling_rotation(s, r):L = torch.zeros((s.shape[0], 3, 3), dtype=torch.float, device="cuda")R = build_rotation(r)L[:,0,0] = s[:,0]L[:,1,1] = s[:,1]L[:,2,2] = s[:,2]L = R @ Lreturn L

scene/gaussian_model的setup_functions中用build_covariance_from_scaling_rotation中将旋转矩阵和缩放矩阵合并并处理成协方差矩阵。并在setup_functions中设定激活函数。

def setup_functions(self):def build_covariance_from_scaling_rotation(scaling, scaling_modifier, rotation):L = build_scaling_rotation(scaling_modifier * scaling, rotation)actual_covariance = L @ L.transpose(1, 2)symm = strip_symmetric(actual_covariance)return symmself.scaling_activation = torch.expself.scaling_inverse_activation = torch.logself.covariance_activation = build_covariance_from_scaling_rotationself.opacity_activation = torch.sigmoidself.inverse_opacity_activation = inverse_sigmoidself.rotation_activation = torch.nn.functional.normalize
'''
self.scaling_activation = torch.exp:这行代码将指数函数 torch.exp 赋值给了 self.scaling_activation。这意味着在网络中,会使用指数函数作为缩放操作的激活函数。self.scaling_inverse_activation = torch.log:这行代码将对数函数 torch.log 赋值给了 self.scaling_inverse_activation。这表示在网络中,会使用对数函数作为缩放的逆操作的激活函数。self.covariance_activation = build_covariance_from_scaling_rotation:这行代码将一个函数 build_covariance_from_scaling_rotation 赋值给了 self.covariance_activation。这可能是一个自定义的函数,用于构建协方差矩阵,该函数可能会使用了缩放和旋转操作。self.opacity_activation = torch.sigmoid:这行代码将 sigmoid 函数 torch.sigmoid 赋值给了 self.opacity_activation。这表示在网络中,会使用 sigmoid 函数作为不透明度的激活函数。self.inverse_opacity_activation = inverse_sigmoid:这行代码将一个函数 inverse_sigmoid 赋值给了 self.inverse_opacity_activation。这可能是一个自定义的函数,用于计算 sigmoid 函数的逆操作。self.rotation_activation = torch.nn.functional.normalize:这行代码将归一化函数 torch.nn.functional.normalize 赋值给了 self.rotation_activation。这表示在网络中,会使用归一化函数作为旋转操作的激活函数。
'''

点云数据的处理

初始化参数的含义:

def __init__(self, sh_degree: int):# 初始化球谐函数相关的度数# 初始化当前活动的球谐函数度数为 0,并将最大球谐函数度数设置为传入的 sh_degree 参数。self.active_sh_degree = 0self.max_sh_degree = sh_degree# 初始化存储点、球谐函数系数、缩放、旋转、不透明度等的张量为空张量self._xyz = torch.empty(0)self._features_dc = torch.empty(0)self._features_rest = torch.empty(0)self._scaling = torch.empty(0)self._rotation = torch.empty(0)self._opacity = torch.empty(0)# 初始化高斯分布投影后的最大二维半径、梯度累积器(用于辨别是否需要新增和删除高斯)和分母张量(表示统计了多少次累计梯度,最后要把这个分母张量除掉)为空张量self.max_radii2D = torch.empty(0)self.xyz_gradient_accum = torch.empty(0)self.denom = torch.empty(0)# 初始化优化器optimizer为 Noneself.optimizer = None# 初始化密度百分比和空间学习率缩放因子(用于处理不同参数对学习率的要求)self.percent_dense = 0self.spatial_lr_scale = 0# 调用设置函数的方法进行必要的初始化self.setup_functions()

scene/gaussian_model的create_from_pcd

def create_from_pcd(self, pcd : BasicPointCloud, spatial_lr_scale : float):self.spatial_lr_scale = spatial_lr_scale# 将点云数据中的点坐标转换为 PyTorch 张量,并放置在 GPU 上进行加速处理fused_point_cloud = torch.tensor(np.asarray(pcd.points)).float().cuda()'''球谐函数这一堆写在另一个文件里。'''print("Number of points at initialisation : ", fused_point_cloud.shape[0])# 计算点云中每个点与原点的欧氏距离的平方,并将其限制在一个最小值以上,以避免出现除以零的情况'''这行代码使用了函数 distCUDA2 来计算点云中每个点之间的距离,并将结果存储在 dist2 变量中。torch.from_numpy(np.asarray(pcd.points)).float().cuda() 将点云数据转换为 PyTorch 张量,并将其移到 GPU 上。torch.clamp_min 函数用于将 dist2 中的所有元素的最小值限制为 0.0000001,以确保不会出现零距离。	'''dist2 = torch.clamp_min(distCUDA2(torch.from_numpy(np.asarray(pcd.points)).float().cuda()), 0.0000001)'''这行代码首先计算了 dist2 中每个元素的平方根,然后取其自然对数。
[..., None] 用于在张量的最后一个维度上添加一个新的维度。
repeat(1, 3) 表示沿着第一个维度将张量复制三次,以便将其扩展为与 fused_point_cloud 相同的形状,其中 fused_point_cloud 是点云的坐标。
最终,scales 是一个与 fused_point_cloud 具有相同形状的张量,用于存储每个点的缩放因子。'''scales = torch.log(torch.sqrt(dist2))[...,None].repeat(1, 3)'''这行代码创建了一个形状为 (点数, 4) 的全零张量 rots,用于存储点云中每个点的旋转信息。
每个点的旋转信息是一个四维向量,其中第一个元素为1,其余元素为0,表示点云中的每个点都没有旋转。'''rots = torch.zeros((fused_point_cloud.shape[0], 4), device="cuda")'''这行代码将 rots 张量的第一列(即第一个元素)设置为1,以表示每个点的旋转信息中的第一个元素为1,其余元素为0,即单位四元数。'''rots[:, 0] = 1# 计算每个点的不透明度,这里使用了一个 sigmoid 函数的逆函数opacities = inverse_sigmoid(0.1 * torch.ones((fused_point_cloud.shape[0], 1), dtype=torch.float, device="cuda"))# 将点云的坐标、特征、尺度、旋转和不透明度分别存储为对象的参数,并设置为可训练self._xyz = nn.Parameter(fused_point_cloud.requires_grad_(True))self._features_dc = nn.Parameter(features[:,:,0:1].transpose(1, 2).contiguous().requires_grad_(True))self._features_rest = nn.Parameter(features[:,:,1:].transpose(1, 2).contiguous().requires_grad_(True))self._scaling = nn.Parameter(scales.requires_grad_(True))self._rotation = nn.Parameter(rots.requires_grad_(True))self._opacity = nn.Parameter(opacities.requires_grad_(True))# 初始化一个用于存储二维最大半径的张量,其形状与点云的数量相同self.max_radii2D = torch.zeros((self.get_xyz.shape[0]), device="cuda")

这里再放一个代码块说一下这个distCUDA2的函数是怎么定义的,在simple-knn/spatial.cu中

// 计算输入张量 `points` 中每个点到其他所有点的距离之和,并返回每个点的平均距离。
torch::Tensor distCUDA2(const torch::Tensor& points)
{const int P = points.size(0); // 点的数量auto float_opts = points.options().dtype(torch::kFloat32); // Float32 类型选项torch::Tensor means = torch::full({P}, 0.0, float_opts); // 用于存储平均距离的张量// 调用 SimpleKNN::knn 函数计算距离SimpleKNN::knn(P, (float3*)points.contiguous().data<float>(), means.contiguous().data<float>());return means; // 返回包含平均距离的张量
}

这就是点云文件ply以文本形式打开后的变量内容,里面的参量很好理解。重点说一下f_dc是球谐函数的直流分量,而f_rest是球谐函数的高阶分量。nx,ny,nz是每个高斯分布的法向量,但考虑到高斯分布的性质,这里的法向量都设置为0。

在这里插入图片描述

def training_setup(self, training_args):# 保存训练参数中的 percent_dense 到对象的属性中,用于后续的训练。self.percent_dense = training_args.percent_dense# 创建一个全零张量 xyz_gradient_accum,用于累积 xyz 坐标的梯度,张量的形状与 self.get_xyz 的第一维度匹配,并将其放置在 GPU 上。self.xyz_gradient_accum = torch.zeros((self.get_xyz.shape[0], 1), device="cuda")# 创建一个全零张量 denom,用于存储某种归一化或计数信息,张量的形状与 self.get_xyz 的第一维度匹配,并将其放置在 GPU 上。self.denom = torch.zeros((self.get_xyz.shape[0], 1), device="cuda")l = [{'params': [self._xyz], 'lr': training_args.position_lr_init * self.spatial_lr_scale, "name": "xyz"},{'params': [self._features_dc], 'lr': training_args.feature_lr, "name": "f_dc"},{'params': [self._features_rest], 'lr': training_args.feature_lr / 20.0, "name": "f_rest"},{'params': [self._opacity], 'lr': training_args.opacity_lr, "name": "opacity"},{'params': [self._scaling], 'lr': training_args.scaling_lr, "name": "scaling"},{'params': [self._rotation], 'lr': training_args.rotation_lr, "name": "rotation"}]# 使用 Adam 优化器,将参数列表 l 传递给优化器,并设置优化器的全局学习率为 0.0(具体学习率在每个参数字典中指定),以及非常小的 eps 值 1e-15。self.optimizer = torch.optim.Adam(l, lr=0.0, eps=1e-15)# lr_init: 初始学习率,按 self.spatial_lr_scale 进行缩放。lr_final: 最终学习率,按 self.spatial_lr_scale 进行缩放。lr_delay_mult: 学习率延迟倍数。max_steps: 最大训练步数。self.xyz_scheduler_args = get_expon_lr_func(lr_init=training_args.position_lr_init*self.spatial_lr_scale,                                                   lr_final=training_args.position_lr_final*self.spatial_lr_scale,lr_delay_mult=training_args.position_lr_delay_mult,max_steps=training_args.position_lr_max_steps)
def update_learning_rate(self, iteration):''' Learning rate scheduling per step '''# 遍历 self.optimizer 中的所有参数组。self.optimizer.param_groups 是一个包含多个字典的列表,每个字典包含了一组参数及其相关的优化信息(例如学习率)。for param_group in self.optimizer.param_groups:# 检查当前参数组的名称是否为 "xyz"。只有名称为 "xyz" 的参数组才会进行学习率更新。if param_group["name"] == "xyz":# 调用 self.xyz_scheduler_args 函数,并传入当前的迭代次数 iteration。self.xyz_scheduler_args 是一个学习率调度函数,根据当前的迭代次数计算并返回新的学习率 lr。 lr = self.xyz_scheduler_args(iteration)# 将计算得到的新学习率 lr 更新到当前的参数组中,使其在接下来的训练步骤中使用新的学习率。param_group['lr'] = lrreturn lr
def construct_list_of_attributes(self):# 初始化一个列表 l,包含一些固定的属性名称:'x', 'y', 'z' 表示点的坐标,'nx', 'ny', 'nz' 表示法线的分量l = ['x', 'y', 'z', 'nx', 'ny', 'nz']# All channels except the 3 DC# 遍历 _features_dc 的通道和特征维度,生成特征属性名称for i in range(self._features_dc.shape[1] * self._features_dc.shape[2]):l.append('f_dc_{}'.format(i))# 遍历 _features_rest 的通道和特征维度,生成特征属性名称for i in range(self._features_rest.shape[1] * self._features_rest.shape[2]):l.append('f_rest_{}'.format(i))# 添加透明度属性l.append('opacity')# 遍历 _scaling 张量的所有通道,生成缩放因子属性名称for i in range(self._scaling.shape[1]):l.append('scale_{}'.format(i))# 遍历 _rotation 张量的所有通道,生成旋转信息属性名称for i in range(self._rotation.shape[1]):l.append('rot_{}'.format(i))return l
def save_ply(self, path):# 创建保存路径的目录,如果目录不存在mkdir_p(os.path.dirname(path))# 获取 xyz 坐标,并将其从 GPU 上移至 CPU,并转换为 numpy 数组xyz = self._xyz.detach().cpu().numpy()# 初始化法向量数组,大小与 xyz 坐标相同normals = np.zeros_like(xyz)# 获取并处理特征数据,将其从 GPU 移至 CPU,并转换为 numpy 数组f_dc = self._features_dc.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()f_rest = self._features_rest.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()# 获取并处理不透明度数据opacities = self._opacity.detach().cpu().numpy()# 获取并处理缩放数据scale = self._scaling.detach().cpu().numpy()# 获取并处理旋转数据rotation = self._rotation.detach().cpu().numpy()# 构建 dtype,用于 numpy 的结构化数组,包含所有属性名称及其类型dtype_full = [(attribute, 'f4') for attribute in self.construct_list_of_attributes()]# 创建一个空的结构化数组,用于存储所有顶点的数据elements = np.empty(xyz.shape[0], dtype=dtype_full)# 将所有属性组合到一个二维数组中attributes = np.concatenate((xyz, normals, f_dc, f_rest, opacities, scale, rotation), axis=1)# 将属性数据逐个赋值到结构化数组中elements[:] = list(map(tuple, attributes))# 创建 PlyElement 并将其描述为 'vertex'el = PlyElement.describe(elements, 'vertex')# 将 PlyData 写入到指定路径PlyData([el]).write(path)
def reset_opacity(self):'''获取当前透明度张量self.get_opacity。将所有透明度值限制在不超过0.01。对限制后的透明度值应用反Sigmoid函数。将新的透明度张量opacities_new替换到优化器中,并更新对象的透明度属性self._opacity。'''opacities_new = inverse_sigmoid(torch.min(self.get_opacity, torch.ones_like(self.get_opacity) * 0.01))optimizable_tensors = self.replace_tensor_to_optimizer(opacities_new, "opacity")self._opacity = optimizable_tensors["opacity"]
def load_ply(self, path):# 读取给定路径的PLY文件,并将数据加载到类的属性中。# 使用PlyData.read方法读取PLY文件的数据。plydata = PlyData.read(path)# 从PLY文件中提取x, y, z坐标并堆叠成一个二维数组xyz。xyz = np.stack((np.asarray(plydata.elements[0]["x"]),np.asarray(plydata.elements[0]["y"]),np.asarray(plydata.elements[0]["z"])), axis=1)# 从PLY文件中提取opacity值,并添加一个新轴,以便与其他属性的维度一致。opacities = np.asarray(plydata.elements[0]["opacity"])[..., np.newaxis]# 创建一个形状为(顶点数, 3, 1)的零数组features_dc。# 从PLY文件中提取f_dc_0, f_dc_1, f_dc_2特征,并分别填充到features_dc中。features_dc = np.zeros((xyz.shape[0], 3, 1))features_dc[:, 0, 0] = np.asarray(plydata.elements[0]["f_dc_0"])features_dc[:, 1, 0] = np.asarray(plydata.elements[0]["f_dc_1"])features_dc[:, 2, 0] = np.asarray(plydata.elements[0]["f_dc_2"])# 提取所有以"f_rest_"开头的属性名,并按后缀数字排序。# 断言这些属性的数量符合期望。# 创建一个形状为(顶点数, 特征数)的零数组features_extra。# 从PLY文件中提取相应的特征,并填充到features_extra中。# 重新调整数组的形状,以便特征维度与SH系数一致。extra_f_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("f_rest_")]extra_f_names = sorted(extra_f_names, key=lambda x: int(x.split('_')[-1]))assert len(extra_f_names) == 3 * (self.max_sh_degree + 1) ** 2 - 3features_extra = np.zeros((xyz.shape[0], len(extra_f_names)))for idx, attr_name in enumerate(extra_f_names):features_extra[:, idx] = np.asarray(plydata.elements[0][attr_name])features_extra = features_extra.reshape((features_extra.shape[0], 3, (self.max_sh_degree + 1) ** 2 - 1))# 提取所有以"scale_"和"rot"开头的属性名,并按后缀数字排序。# 创建零数组scales和rots,并从PLY文件中提取相应的属性值,填充到数组中。scale_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("scale_")]scale_names = sorted(scale_names, key=lambda x: int(x.split('_')[-1]))scales = np.zeros((xyz.shape[0], len(scale_names)))for idx, attr_name in enumerate(scale_names):scales[:, idx] = np.asarray(plydata.elements[0][attr_name])rot_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("rot")]rot_names = sorted(rot_names, key=lambda x: int(x.split('_')[-1]))rots = np.zeros((xyz.shape[0], len(rot_names)))for idx, attr_name in enumerate(rot_names):rots[:, idx] = np.asarray(plydata.elements[0][attr_name])# 将所有提取的数据转换为PyTorch张量,并将其设置为可优化的参数(requires_grad_(True))。# 使用transpose(1, 2)和contiguous()确保张量的内存布局适合后续计算。self._xyz = nn.Parameter(torch.tensor(xyz, dtype=torch.float, device="cuda").requires_grad_(True))self._features_dc = nn.Parameter(torch.tensor(features_dc, dtype=torch.float, device="cuda").transpose(1, 2).contiguous().requires_grad_(True))self._features_rest = nn.Parameter(torch.tensor(features_extra, dtype=torch.float, device="cuda").transpose(1, 2).contiguous().requires_grad_(True))self._opacity = nn.Parameter(torch.tensor(opacities, dtype=torch.float, device="cuda").requires_grad_(True))self._scaling = nn.Parameter(torch.tensor(scales, dtype=torch.float, device="cuda").requires_grad_(True))self._rotation = nn.Parameter(torch.tensor(rots, dtype=torch.float, device="cuda").requires_grad_(True))# 将active_sh_degree设置为max_sh_degree,表示当前使用的SH度数。self.active_sh_degree = self.max_sh_degree

接下来的内容是关于自适应密度控制

这些代码同样在scene/gaussian_model中。

def replace_tensor_to_optimizer(self, tensor, name):# 将新的张量替换为优化器中的参数,同时保留优化器的状态。这个很重要,后面处理参数都要靠这个方法!!!# 用于存储新的可优化张量。optimizable_tensors = {}# 优化器的参数组包含了所有要优化的参数和相关设置。for group in self.optimizer.param_groups:# 检查参数组的名字是否与给定的名字匹配。if group["name"] == name:# 获取当前参数的状态,包括动量项和二次动量项。stored_state = self.optimizer.state.get(group['params'][0], None)# 将动量项和二次动量项重置为与新张量相同形状的零张量。stored_state["exp_avg"] = torch.zeros_like(tensor)stored_state["exp_avg_sq"] = torch.zeros_like(tensor)# 删除旧参数的状态。del self.optimizer.state[group['params'][0]]# 将参数组中的第一个参数替换为新的张量,并设置为需要梯度。group["params"][0] = nn.Parameter(tensor.requires_grad_(True))# 将存储的状态重新设置到新的参数上。self.optimizer.state[group['params'][0]] = stored_state# 将新的参数添加到字典中。optimizable_tensors[group["name"]] = group["params"][0]# 返回包含新的可优化张量的字典。return optimizable_tensors
'''
后面做自适应密度控制的时候,所用的方法如果需要替换优化器中的参数,都需要使用这个replace_tensor_to_optimizer方法。这个方法可以确保新的参数替换后,优化器的状态(例如动量和二次动量)能够被正确保留,使得训练过程平稳进行。通过这种方式,可以有效地管理和更新优化器中的参数,而不会丢失已有的优化状态。
'''
def _prune_optimizer(self, mask):# 通过应用掩码(mask)来选择需要保留的参数,并相应地更新优化器的状态。# 初始化优化参数字典optimizable_tensors = {}# 遍历优化器参数组for group in self.optimizer.param_groups:# 如果有存储状态(例如动量和二次动量),则更新这些状态,使其只包含被掩码保留的部分。stored_state = self.optimizer.state.get(group['params'][0], None)if stored_state is not None:stored_state["exp_avg"] = stored_state["exp_avg"][mask]stored_state["exp_avg_sq"] = stored_state["exp_avg_sq"][mask]# 删除旧参数并用掩码后的参数替换,无论是否有存储状态del self.optimizer.state[group['params'][0]]group["params"][0] = nn.Parameter((group["params"][0][mask].requires_grad_(True)))# 如果有存储状态,则将其重新分配到新的参数上self.optimizer.state[group['params'][0]] = stored_state# 记录更新后的参数optimizable_tensors[group["name"]] = group["params"][0]else:# 如果没有存储状态,仅更新参数组中的参数group["params"][0] = nn.Parameter(group["params"][0][mask].requires_grad_(True))optimizable_tensors[group["name"]] = group["params"][0]# 返回更新后的参数字典return optimizable_tensors
 def prune_points(self, mask):# 对输入掩码取反,得到有效点的掩码valid_points_mask = ~mask# 调用 _prune_optimizer 方法,使用有效点的掩码来更新优化器中的参数optimizable_tensors = self._prune_optimizer(valid_points_mask)# 更新类中的各个属性,使其只包含有效点self._xyz = optimizable_tensors["xyz"]self._features_dc = optimizable_tensors["f_dc"]self._features_rest = optimizable_tensors["f_rest"]self._opacity = optimizable_tensors["opacity"]self._scaling = optimizable_tensors["scaling"]self._rotation = optimizable_tensors["rotation"]# 更新 xyz_gradient_accum 以仅包含有效点self.xyz_gradient_accum = self.xyz_gradient_accum[valid_points_mask]# 更新 denom 和 max_radii2D 以仅包含有效点self.denom = self.denom[valid_points_mask]self.max_radii2D = self.max_radii2D[valid_points_mask]
def cat_tensors_to_optimizer(self, tensors_dict):# 初始化优化参数字典optimizable_tensors = {}# 遍历优化器参数组for group in self.optimizer.param_groups:assert len(group["params"]) == 1# 从字典中获取要扩展的张量extension_tensor = tensors_dict[group["name"]]# 获取参数组的存储状态(例如动量和二次动量)stored_state = self.optimizer.state.get(group['params'][0], None)if stored_state is not None:# 扩展动量和二次动量stored_state["exp_avg"] = torch.cat((stored_state["exp_avg"], torch.zeros_like(extension_tensor)), dim=0)stored_state["exp_avg_sq"] = torch.cat((stored_state["exp_avg_sq"], torch.zeros_like(extension_tensor)), dim=0)# 更新参数组中的参数,并将其设置为需要梯度计算的模式del self.optimizer.state[group['params'][0]]group["params"][0] = nn.Parameter(torch.cat((group["params"][0], extension_tensor), dim=0).requires_grad_(True))self.optimizer.state[group['params'][0]] = stored_state# 将扩展后的参数存储到优化参数字典中optimizable_tensors[group["name"]] = group["params"][0]else:# 如果没有存储状态,只更新参数组中的参数group["params"][0] = nn.Parameter(torch.cat((group["params"][0], extension_tensor), dim=0).requires_grad_(True))optimizable_tensors[group["name"]] = group["params"][0]return optimizable_tensors
def densification_postfix(self, new_xyz, new_features_dc, new_features_rest, new_opacities, new_scaling, new_rotation):# 将新的数据构建成一个字典d = {"xyz": new_xyz,"f_dc": new_features_dc,"f_rest": new_features_rest,"opacity": new_opacities,"scaling": new_scaling,"rotation": new_rotation}# 使用 cat_tensors_to_optimizer 方法,将新的数据添加到优化器中optimizable_tensors = self.cat_tensors_to_optimizer(d)# 更新实例变量,使其指向新的优化张量self._xyz = optimizable_tensors["xyz"]self._features_dc = optimizable_tensors["f_dc"]self._features_rest = optimizable_tensors["f_rest"]self._opacity = optimizable_tensors["opacity"]self._scaling = optimizable_tensors["scaling"]self._rotation = optimizable_tensors["rotation"]# 初始化梯度累积器和相关变量self.xyz_gradient_accum = torch.zeros((self.get_xyz.shape[0], 1), device="cuda")self.denom = torch.zeros((self.get_xyz.shape[0], 1), device="cuda")self.max_radii2D = torch.zeros((self.get_xyz.shape[0]), device="cuda")
def densify_and_split(self, grads, grad_threshold, scene_extent, N=2):# 获取初始点数n_init_points = self.get_xyz.shape[0]# 初始化一个与初始点数相同大小的全零张量,用于存储梯度padded_grad = torch.zeros((n_init_points), device="cuda")# 将输入的梯度值填充到全零张量中padded_grad[:grads.shape[0]] = grads.squeeze()# 选取满足梯度阈值的点,生成布尔掩码selected_pts_mask = torch.where(padded_grad >= grad_threshold, True, False)# 进一步筛选,确保缩放因子大于特定比例的场景范围selected_pts_mask = torch.logical_and(selected_pts_mask,torch.max(self.get_scaling, dim=1).values > self.percent_dense * scene_extent)# 获取选中的点的缩放因子,并重复 N 次stds = self.get_scaling[selected_pts_mask].repeat(N, 1)# 初始化均值为零的张量means = torch.zeros((stds.size(0), 3), device="cuda")# 生成符合正态分布的噪声采样samples = torch.normal(mean=means, std=stds)# 构建旋转矩阵,并重复 N 次rots = build_rotation(self._rotation[selected_pts_mask]).repeat(N, 1, 1)# 计算新的点的坐标new_xyz = torch.bmm(rots, samples.unsqueeze(-1)).squeeze(-1) + self.get_xyz[selected_pts_mask].repeat(N, 1)# 计算新的缩放因子,并进行缩放处理new_scaling = self.scaling_inverse_activation(self.get_scaling[selected_pts_mask].repeat(N, 1) / (0.8 * N))# 复制旋转信息new_rotation = self._rotation[selected_pts_mask].repeat(N, 1)# 复制特征信息new_features_dc = self._features_dc[selected_pts_mask].repeat(N, 1, 1)new_features_rest = self._features_rest[selected_pts_mask].repeat(N, 1, 1)# 复制透明度信息new_opacity = self._opacity[selected_pts_mask].repeat(N, 1)# 调用函数,将新生成的数据传入self.densification_postfix(new_xyz, new_features_dc, new_features_rest, new_opacity, new_scaling, new_rotation)# 构建修剪掩码,将新生成的点也包含在内prune_filter = torch.cat((selected_pts_mask, torch.zeros(N * selected_pts_mask.sum(), device="cuda", dtype=bool)))# 调用修剪函数,移除不需要的点self.prune_points(prune_filter)def densify_and_clone(self, grads, grad_threshold, scene_extent):# 根据梯度值和场景范围,选取满足条件的点selected_pts_mask = torch.where(torch.norm(grads, dim=-1) >= grad_threshold, True, False)selected_pts_mask = torch.logical_and(selected_pts_mask,torch.max(self.get_scaling, dim=1).values <= self.percent_dense * scene_extent)# 克隆选中的点new_xyz = self._xyz[selected_pts_mask]new_features_dc = self._features_dc[selected_pts_mask]new_features_rest = self._features_rest[selected_pts_mask]new_opacities = self._opacity[selected_pts_mask]new_scaling = self._scaling[selected_pts_mask]new_rotation = self._rotation[selected_pts_mask]# 调用函数,将克隆的数据传入self.densification_postfix(new_xyz, new_features_dc, new_features_rest, new_opacities, new_scaling, new_rotation)def densify_and_prune(self, max_grad, min_opacity, extent, max_screen_size):# 计算每个点的梯度值grads = self.xyz_gradient_accum / self.denomgrads[grads.isnan()] = 0.0# 调用密化和克隆函数self.densify_and_clone(grads, max_grad, extent)# 调用密化和分裂函数self.densify_and_split(grads, max_grad, extent)# 根据透明度生成修剪掩码prune_mask = (self.get_opacity < min_opacity).squeeze()# 如果指定了最大屏幕尺寸,根据屏幕尺寸生成修剪掩码if max_screen_size:big_points_vs = self.max_radii2D > max_screen_sizebig_points_ws = self.get_scaling.max(dim=1).values > 0.1 * extentprune_mask = torch.logical_or(torch.logical_or(prune_mask, big_points_vs), big_points_ws)# 调用修剪函数,移除不需要的点self.prune_points(prune_mask)# 清理 CUDA 缓存torch.cuda.empty_cache()
def add_densification_stats(self, viewspace_point_tensor, update_filter):# 累积选中点的梯度值self.xyz_gradient_accum[update_filter] += torch.norm(viewspace_point_tensor.grad[update_filter, :2], dim=-1, keepdim=True)# 更新选中点的计数self.denom[update_filter] += 1

Camera

以下代码定义在scene/cameras.py中。

class Camera(nn.Module):def __init__(self, colmap_id, R, T, FoVx, FoVy, image, gt_alpha_mask, image_name, uid, trans=np.array([0.0, 0.0, 0.0]), scale=1.0, data_device="cuda"):"""初始化 Camera 类:param colmap_id: Colmap 中相机的 ID:param R: 旋转矩阵:param T: 平移向量:param FoVx: 水平方向的视场角:param FoVy: 垂直方向的视场角:param image: 图像数据 (torch.Tensor):param gt_alpha_mask: ground truth alpha mask (torch.Tensor):param image_name: 图像名称:param uid: 相机的唯一标识符:param trans: 平移向量,默认为 [0.0, 0.0, 0.0]:param scale: 缩放因子,默认为 1.0:param data_device: 数据存储设备,默认为 "cuda""""super(Camera, self).__init__()# 初始化相机参数self.uid = uid self.colmap_id = colmap_idself.R = Rself.T = Tself.FoVx = FoVxself.FoVy = FoVyself.image_name = image_name# 尝试将数据设备设置为指定的设备try:self.data_device = torch.device(data_device)except Exception as e:print(e)print(f"[Warning] Custom device {data_device} failed, fallback to default cuda device")self.data_device = torch.device("cuda")# 将图像数据限制在 [0.0, 1.0] 范围内并移动到指定设备self.original_image = image.clamp(0.0, 1.0).to(self.data_device)self.image_width = self.original_image.shape[2]self.image_height = self.original_image.shape[1]# 如果提供了 alpha mask,则将图像数据乘以 alpha maskif gt_alpha_mask is not None:self.original_image *= gt_alpha_mask.to(self.data_device)else:self.original_image *= torch.ones((1, self.image_height, self.image_width), device=self.data_device)# 设置相机的近平面和远平面self.zfar = 100.0self.znear = 0.01# 设置平移和缩放参数self.trans = transself.scale = scale# 计算世界坐标到视图坐标的变换矩阵,并将其移动到 GPUself.world_view_transform = torch.tensor(getWorld2View2(R, T, trans, scale)).transpose(0, 1).cuda()# 计算投影矩阵,并将其移动到 GPUself.projection_matrix = getProjectionMatrix(znear=self.znear, zfar=self.zfar, fovX=self.FoVx, fovY=self.FoVy).transpose(0, 1).cuda()# 计算完整的投影变换矩阵self.full_proj_transform = (self.world_view_transform.unsqueeze(0).bmm(self.projection_matrix.unsqueeze(0))).squeeze(0)# 计算相机中心位置self.camera_center = self.world_view_transform.inverse()[3, :3]
# 这个不是很重要
class MiniCam:def __init__(self, width, height, fovy, fovx, znear, zfar, world_view_transform, full_proj_transform):"""初始化 MiniCam 类:param width: 图像宽度:param height: 图像高度:param fovy: 垂直方向的视场角:param fovx: 水平方向的视场角:param znear: 近平面:param zfar: 远平面:param world_view_transform: 世界坐标到视图坐标的变换矩阵:param full_proj_transform: 完整的投影变换矩阵"""self.image_width = widthself.image_height = heightself.FoVy = fovyself.FoVx = fovxself.znear = znearself.zfar = zfarself.world_view_transform = world_view_transformself.full_proj_transform = full_proj_transform# 计算相机中心位置view_inv = torch.inverse(self.world_view_transform)self.camera_center = view_inv[3][:3]

其中的转换矩阵

def getWorld2View2(R, t, translate=np.array([.0, .0, .0]), scale=1.0):"""计算从世界坐标系到相机视图坐标系的变换矩阵。:param R: 旋转矩阵 (3x3 numpy array):param t: 平移向量 (3-dimensional numpy array):param translate: 额外的平移向量,用于对相机中心进行平移 (默认为 [0.0, 0.0, 0.0]):param scale: 缩放因子,用于对相机中心进行缩放 (默认为 1.0):return: 从世界坐标系到相机视图坐标系的变换矩阵 (4x4 numpy array)"""# 初始化一个 4x4 矩阵 Rt,全为零Rt = np.zeros((4, 4))# 将旋转矩阵的转置赋值给 Rt 的左上 3x3 子矩阵Rt[:3, :3] = R.transpose()# 将平移向量赋值给 Rt 的第4列的前3个元素Rt[:3, 3] = t# 将 Rt 的右下角元素赋值为 1.0Rt[3, 3] = 1.0# 计算相机从相机坐标系到世界坐标系的变换矩阵 C2WC2W = np.linalg.inv(Rt)# 提取相机中心坐标cam_center = C2W[:3, 3]# 对相机中心进行平移和缩放cam_center = (cam_center + translate) * scale# 更新 C2W 的第4列的前3个元素为新的相机中心坐标C2W[:3, 3] = cam_center# 计算从世界坐标系到相机坐标系的变换矩阵 RtRt = np.linalg.inv(C2W)# 返回浮点类型的变换矩阵 Rtreturn np.float32(Rt)

其中的投影矩阵

def getProjectionMatrix(znear, zfar, fovX, fovY):'''投影矩阵用于将三维坐标变换为二维屏幕坐标。这通常涉及将相机坐标系中的点投影到一个称为规范化设备坐标(NDC)系的空间中,然后再映射到屏幕坐标系。'''# 计算垂直视场角的一半的切线值tanHalfFovY = math.tan((fovY / 2))# 计算水平视场角的一半的切线值tanHalfFovX = math.tan((fovX / 2))# 计算近平面的上边界位置top = tanHalfFovY * znear# 计算近平面的下边界位置bottom = -top# 计算近平面的右边界位置right = tanHalfFovX * znear# 计算近平面的左边界位置left = -right# 初始化一个 4x4 的投影矩阵 P,所有元素初始为零P = torch.zeros(4, 4)# 控制 z 方向的符号z_sign = 1.0# 设置投影矩阵中的元素P[0, 0] = 2.0 * znear / (right - left)  # x 方向上的缩放因子P[1, 1] = 2.0 * znear / (top - bottom)  # y 方向上的缩放因子P[0, 2] = (right + left) / (right - left)  # x 方向上的偏移量P[1, 2] = (top + bottom) / (top - bottom)  # y 方向上的偏移量P[3, 2] = z_sign  # 齐次坐标的 w 分量P[2, 2] = z_sign * zfar / (zfar - znear)  # z 方向上的缩放因子P[2, 3] = -(zfar * znear) / (zfar - znear)  # z 方向上的偏移量# 返回计算好的投影矩阵return P

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

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

相关文章

反激开关电源反馈电路相关参数选型

Vb的电压正常变化范围是&#xff1a;0-1V&#xff08;最低0V&#xff0c;由于有稳压管&#xff0c;最高不会超过1V&#xff09; Vb的电压越高&#xff0c;则输出占空比越大&#xff0c;Vb电压越低&#xff0c;则输出占空比越小 那么Va的正常变化范围应该是&#xff1a;1.4-4.…

云卓SKYDROID-H30——科技改变未来

云卓H30采用高通处理器、搭载安卓嵌入式系统&#xff0c;拥有三个工作频率&#xff0c;让图像更清晰、延迟更低、距离远、抗干扰性强&#xff0c;支持多种接口&#xff0c;更有10.1寸高清工业级阳光可视屏&#xff0c;防尘耐磨&#xff0c;结构强度高&#xff0c;适用于各种严苛…

Python和tkinter单词游戏

Python和tkinter单词游戏 数据字典文本文件&#xff0c;文件名为Dictionary.txt&#xff0c;保存编码格式为&#xff1a;utf-8。文本内容&#xff1a;每行一个 单词 &#xff0c;单词和解释用空格分隔&#xff0c;如 a art.一(个)&#xff1b;每一(个) ability n.能力&#…

springboot实验报告管理系统-计算机毕业设计源码10596

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 修改信息流程 2.2.6 删除信息流程 2.3 系统功能分析 …

HarmonyOS Next开发学习手册——单选框 (Radio)

Radio是单选框组件&#xff0c;通常用于提供相应的用户交互选择项&#xff0c;同一组的Radio中只有一个可以被选中。具体用法请参考 Radio 。 创建单选框 Radio通过调用接口来创建&#xff0c;接口调用形式如下&#xff1a; Radio(options: {value: string, group: string})…

高考不是终点:如何利用教育资源实现人生跃迁?普鲁士教育的利弊,你了解吗?从科举到高考,中国教育的变迁!链接上层,获取核心资源的途径

高考已经结束&#xff0c;这两天分数将会陆续出来&#xff0c;无论结果好坏&#xff0c;我都希望你明白一些道理。这些道理在学校老师不会教你&#xff0c;但是非常重要。 一、中国的科举制度 科举制度是为王朝服务的。 科举制度是中国古代通过考试选拔官员的制度&#xff0c…

C++类型转换可调用对象

目录 C的四种可视性类型转换 1.static_cast 2.reinterpret_cast 3.const_cast 4.dynamic_cast C中的可调用对象 普通函数 函数指针 仿函数 Lambda表达式 包装器function bind C的四种可视性类型转换 C语言中的类型转换是不安全、不明确的&#xff0c;于是C就出了更…

MQTT QoS 0, 1, 2

目录 # 开篇 1. 精细MQS TT QoS的行为 1.1 QoS 0: 最多交付一次&#xff08;At Most Once&#xff09; 1.2 QoS 1: 至少交付一次&#xff08;At Least Once&#xff09; 1.3 QoS 2: 只交付一次&#xff08;Exactly Once&#xff09; 1.4 传输过程图示 1.5 总结 2. MQTT…

7-1作业

1.实验目的&#xff1a;完成字符收发 led.h #ifndef __GPIO_H__ #define __GPIO_H__#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_uart.h"//RCC,GPIO,UART初始化 void init();//字符数据发送 void set_tt…

Web端登录页和注册页源码

前言&#xff1a;登录页面是前端开发中最常见的页面&#xff0c;下面是登录页面效果图和源代码&#xff0c;CV大法直接拿走。 1、登录页面 源代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>登录</ti…

每日复盘-20240701

今日关注&#xff1a; 20240701 六日涨幅最大: ------1--------301182--------- 凯旺科技 五日涨幅最大: ------1--------300977--------- 深圳瑞捷 四日涨幅最大: ------1--------300977--------- 深圳瑞捷 三日涨幅最大: ------1--------300461--------- 田中精机 二日涨幅最…

快钱支付股东全部股权已被质押!

根据近期工商信息&#xff0c;第三方支付机构快钱支付清算信息有限公司&#xff08;简称“快钱支付”&#xff09;实际控股方快钱金融服务&#xff08;上海&#xff09;有限公司&#xff08;简称“快钱金融”&#xff09;&#xff0c;作为出质股权标的企业&#xff0c;被出质给…

【SGX系列教程】(三)Intel-SGX 官方示例分析(SampleCode)——SampleEnclave

文章目录 一. 引言二. README2.1 项目目的2.2 构建和执行示例代码的步骤2.3 配置参数解释2.4 配置文件分析2.5 启动令牌初始化 三. 重点代码分析3.1 App文件夹3.1.1 App/App.cpp3.1.2 App/Edger8rSyntax文件夹3.1.2.1 App/Edger8rSyntax/Arrays.cpp3.1.2.2 App/Edger8rSyntax/F…

一文全概括,建议收藏,那些你不可错过的IC设计书籍合集(可下载)

集成电路设计工程师的角色不仅是推动技术创新的中坚力量&#xff0c;更是实现产品从概念到现实的关键桥梁。随着对高性能、低功耗芯片的需求不断增长&#xff0c;IC设计工程师的专业技能和知识深度成为了衡量其职业价值的重要标准。无论是在数字逻辑设计、功能验证、可测试性设…

JMeter--定时执行的方法

原文网址&#xff1a;JMeter--定时执行的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍JMeter如何使用定时器定时执行测试任务。 Java技术星球&#xff1a;way2j.com 方法 第一步&#xff1a;新建定时器 右键测试任务> Add > Timer> Constant Timer 如下图所示…

qt中数据库和excel互导数据————附带详细步骤和代码

文章目录 0 背景1 准备QXlsx环境1.1 cmake安装使用1.2 qmake使用 2 把excel数据导出到mysql数据库3 把mysql数据库的数据写入到excel4 完整代码5 项目代码仓库 0 背景 因为需要批量导入和导出数据&#xff0c;所以需要用到excel。实现把数据库的数据导入到excel中&#xff0c;…

解析QAnything启动命令过程

一.启动命令过程日志 启动命令bash ./run.sh -c local -i 0 -b hf -m Qwen-1_8B-Chat -t qwen-7b-chat。输入日志如下所示&#xff1a; rootMM-202203161213:/mnt/l/20230918_RAG方向/QAnything# bash ./run.sh -c local -i 0 -b hf -m Qwen-1_8B-Chat -t qwen-7b-chat From …

理性决策的艺术:从购房到择偶的数学智慧;37% 规则,做出最佳决策的秘诀;用数学模型解决人生难题

在面对人生重大决策时&#xff0c;如购房或择偶&#xff0c;我们常常感到迷茫和困惑。然而&#xff0c;如果我们能够将这些看似复杂的问题简化为数学模型&#xff0c;我们就能以更加理性和系统的方式做出决策。 37%规则 1950年代&#xff0c;当时几位数学家开始研究这样一个问…

值得收藏!盘点那些适合普通人方便又好用的AIGC工具!(下)

【导读】接上一篇文章&#xff0c;盘点国内外适合普通人能够轻松上手的AIGC工具&#xff08;上&#xff09;。今天又为大家整理了一些好用又方便的AI设计工具、AI办公工具、AI编程工具、AI指令工具和AI检测工具&#xff0c;如果有没更新到的工具也欢迎大家评论区交流。 一 、A…

C#/WPF 自制截图工具

在日常使用电脑办公时&#xff0c;我们经常遇到需要截图然后保存图片&#xff0c;我们往往需要借助安装截图工具才能实现&#xff0c;现在我们通过C#自制截图工具&#xff0c;也能够轻松进行截图。 我们可以通过C#调用WindousAPI来实现截图&#xff0c;实例代码如下&#xff1a…