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

news2024/12/23 11:09:38

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 @ L
    return 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 symm
        
        self.scaling_activation = torch.exp
        self.scaling_inverse_activation = torch.log

        self.covariance_activation = build_covariance_from_scaling_rotation

        self.opacity_activation = torch.sigmoid
        self.inverse_opacity_activation = inverse_sigmoid

        self.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 = 0
    self.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为 None
    self.optimizer = None
    
    # 初始化密度百分比和空间学习率缩放因子(用于处理不同参数对学习率的要求)
    self.percent_dense = 0
    self.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'] = lr
            return 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 - 3
    features_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.denom
    grads[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_size
        big_points_ws = self.get_scaling.max(dim=1).values > 0.1 * extent
        prune_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_id
        self.R = R
        self.T = T
        self.FoVx = FoVx
        self.FoVy = FoVy
        self.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 mask
        if 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.0
        self.znear = 0.01

        # 设置平移和缩放参数
        self.trans = trans
        self.scale = scale

        # 计算世界坐标到视图坐标的变换矩阵,并将其移动到 GPU
        self.world_view_transform = torch.tensor(getWorld2View2(R, T, trans, scale)).transpose(0, 1).cuda()

        # 计算投影矩阵,并将其移动到 GPU
        self.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 = width
        self.image_height = height
        self.FoVy = fovy
        self.FoVx = fovx
        self.znear = znear
        self.zfar = zfar
        self.world_view_transform = world_view_transform
        self.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.0
    Rt[3, 3] = 1.0

    # 计算相机从相机坐标系到世界坐标系的变换矩阵 C2W
    C2W = np.linalg.inv(Rt)
    
    # 提取相机中心坐标
    cam_center = C2W[:3, 3]
    
    # 对相机中心进行平移和缩放
    cam_center = (cam_center + translate) * scale
    
    # 更新 C2W 的第4列的前3个元素为新的相机中心坐标
    C2W[:3, 3] = cam_center
    
    # 计算从世界坐标系到相机坐标系的变换矩阵 Rt
    Rt = np.linalg.inv(C2W)
    
    # 返回浮点类型的变换矩阵 Rt
    return 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.coloradmin.cn/o/1883708.html

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

相关文章

探索工业AI智能摄像机的高端科技

在当今快速发展的工业智能化领域&#xff0c;工业AI智能摄像机系列以其卓越的性能和多功能性在国内外备受关注&#xff08;文末有国外工程师的评测链接&#xff09;。搭载Raspberry Pi CM4支持的旨在广泛应用&#xff0c;涵盖从简单的条形码扫描到基于人工智能的工业环境中的缺…

数学知识——欧拉函数

数学知识&#xff08;二&#xff09; 20240628 求和N互质的个数公式 先分解N&#xff0c;再求个数fai n欧拉函数的证明&#xff1a;用容斥原理 不考 求质因子 p1, … , pk 1-N中与N互质的个数&#xff0c; 去掉质因子倍数 是pi的倍数的有N/pi个&#xff0c;但是会有既是p1也是…

【UE5.1】Chaos物理系统基础——01 创建可被破坏的物体

目录 步骤 一、通过笔刷创建静态网格体 二、破裂静态网格体 三、“统一” 多层级破裂 四、“簇” 群集化的破裂 五、几何体集的材质 六、防止几何体集自动破碎 步骤 一、通过笔刷创建静态网格体 1. 可以在Quixel Bridge中下载两个纹理&#xff0c;用于表示石块的内外纹…

微信小程序毕业设计-英语互助系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

微信小程序-插槽slot

一.插槽slot 在页面使用自定义组件的时候&#xff0c;如果在自定义组件里面写子组件&#xff0c;子组件的内容无法显示。 <custom01> <text slotslot-top>你好&#xff0c;上方组件</text> 你好&#xff0c;组件 <text slotslot-bottom>你好&#xf…

小型语言模型的兴起

过去几年&#xff0c;我们看到人工智能能力呈爆炸式增长&#xff0c;其中很大一部分是由大型语言模型 (LLM) 的进步推动的。GPT-3 等模型包含 1750 亿个参数&#xff0c;已经展示了生成类似人类的文本、回答问题、总结文档等能力。然而&#xff0c;虽然 LLM 的能力令人印象深刻…

文件操作~

目录 1.为什么使用文件&#xff1f; 2.什么是文件&#xff1f; 2.1 程序文件 2.2 数据文件 2.3 文件名 3.⼆进制文件和文本文件&#xff1f; 4.文件的打开和关闭 4.1 流和标准流 4.1.1 流 4.1.2 标准流 4.2 文件指针 4.3 ⽂件的打开和关闭 5.文件的顺序读写 5.1 …

志愿者管理系统带讲解,保运行

技术栈 后端: SpringBoot Mysql MybatisPlus 前端: Vue Element 分为 管理员端 用户端 功能描述 用户端 管理员端 观看地址&#xff1a; B站 &#xff1a; 【毕设者】志愿者管理系统(安装讲解源码)

C++算法学习心得八.动态规划算法(6)

1.最长递增子序列&#xff08;300题&#xff09; 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&…

超详细的 C++中的封装继承和多态的知识总结<2.多态>

引言 小伙伴们我们都知道了&#xff0c;什么是封装和继承&#xff0c;在有了这个的基础上我们接着来看什么是多态。多态从字面上意思我们就可以知道&#xff0c;大概就是一个函数的不同形态&#xff0c;而且&#xff0c;前边我们在学习函数重载的时候我们已经简单的了解了如何用…

如何优化前端性能:提高网页加载速度的实用技巧

我们在前端开发中&#xff0c;性能优化是提高用户体验的关键因素。网页加载速度直接影响用户的满意度和留存率。本文将介绍几种优化前端性能的实用方法&#xff0c;帮助你提高网页加载速度。 问题描述 &#xff1a; 首先前端性能优化涉及多个方面&#xff0c;包括减少HTTP请…

【单片机与嵌入式】stm32串口通信入门

一、串口通信/协议 &#xff08;一&#xff09;串口通信简介 串口通信是一种通过串行传输方式在电子设备之间进行数据交换的通信方式。它通常涉及两条线&#xff08;一条用于发送数据&#xff0c;一条用于接收数据&#xff09;&#xff0c;适用于各种设备&#xff0c;从微控制…

万字长文|下一代系统内存数据加速接口SDXI解读

本文内容分为5章节&#xff0c;总计10535字&#xff0c;内容较多&#xff0c;建议先收藏&#xff01; 1.SDXI技术产生的背景 2.SDXI相比DMA的优势 3.SDXI实现原理与架构 3.1 描述符环原理解读 3.2 上下文管理介绍 3.3 AKey与RKey解读 3.4 错误日志和状态管理 3.5 跨Function访…

javascript 常见设计模式

什么是设计模式? 在软件开发中&#xff0c;设计模式是解决特定问题的经验总结和可复用的解决方案。设计模式可以提高代码的复用性、可维护性和可读性&#xff0c;是提高开发效率的重要手段。 单例模式 1.概念 单例模式 &#xff08;Singleton Pattern&#xff09;&#xf…

c++ 智能指针实战分析

一.智能指针的设计思路 智能指针是类模板&#xff0c;再栈上创建智能指针对象。把普通指针交给智能指针对象。智能指针对象过期时&#xff0c;调用析构函数释放普通指针的内存。 智能指针的类型 auto_ptr是C98的标准&#xff0c;c17已经弃用。unique_ptr、shared_ptr和weak_…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-41目标检测数据集

41目标检测数据集 import os import pandas as pd import torch import torchvision import matplotlib.pylab as plt from d2l import torch as d2l# 数据集下载链接 # http://d2l-data.s3-accelerate.amazonaws.com/banana-detection.zip# 读取数据集 #save def read_data_b…

Mustango——音乐领域知识生成模型探索

Mustango&#xff1a;利用领域知识的音乐生成模型 论文地址&#xff1a;https://arxiv.org/pdf/2311.08355.pdf 源码地址&#xff1a;https://github.com/amaai-lab/mustango 论文题为**“**利用音乐领域知识开发文本到音乐模型’Mustango’”。它利用音乐领域的知识从文本指…

计算机毕业设计Python深度学习美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js

Python美食推荐系统开题报告 一、项目背景与意义 随着互联网和移动技术的飞速发展&#xff0c;人们的生活方式发生了巨大变化&#xff0c;尤其是餐饮行业。在线美食平台如雨后春笋般涌现&#xff0c;为用户提供了丰富的美食选择。然而&#xff0c;如何在海量的餐饮信息中快速…

python 笔试面试八股(自用版~)

1 解释型和编译型语言的区别 解释是翻译一句执行一句&#xff0c;更灵活&#xff0c;eg&#xff1a;python; 解释成机器能理解的指令&#xff0c;而不是二进制码 编译是整个源程序编译成机器可以直接执行的二进制可运行的程序&#xff0c;再运行这个程序 比如c 2 简述下 Pyth…

2.2章节python的变量和常量

在Python中&#xff0c;变量和常量有一些基本的概念和用法&#xff0c;但需要注意的是&#xff0c;Python本身并没有内置的“常量”类型。然而&#xff0c;程序员通常会遵循一种约定&#xff0c;即使用全部大写的变量名来表示常量。 一、变量 在Python中&#xff0c;变量是一…