Gaussian Splatting中球谐的使用
定义
在class GaussianModel
中,球谐是通过下面的方式定义的:
self._features_dc = torch.empty(0) # 第一个球谐系数,用于表示基础颜色
self._features_rest = torch.empty(0) # 其余的球谐系数,用于表示颜色的细节和变化
_features_dc
和 _features_rest
张量的形状为 (样本数, 3, 1)
和 (样本数, 3, (max_sh_degree + 1) ** 2 - 1)
,分别表示基础颜色的球谐系数和其余球谐系数。
把相关的代码发给ChatGPT,能够帮助分析,参考链接为:https://chatgpt.com/share/35888723-ab2f-4384-ac63-0fcc87aaaa22
初始化
之后,通过如下的方式进行初始化:
fused_color = RGB2SH(torch.tensor(np.asarray(pcd.colors)).float().cuda())
# 初始化存储球谐系数的张量,每个颜色通道有(max_sh_degree + 1) ** 2个球谐系数
features = torch.zeros((fused_color.shape[0], 3, (self.max_sh_degree + 1) ** 2)).float().cuda()
# 将RGB转换后的球谐系数C0项的系数存入
features[:, :3, 0] = fused_color
# 其余球谐系数初始化为0
features[:, 3:, 1:] = 0.0
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))
相关的函数为:
def RGB2SH(rgb):
"""
将RGB颜色值转换为球谐系数C0项的系数。
:param rgb: RGB颜色值。
:return: 转换后的球谐系数C0项的系数。
"""
return (rgb - 0.5) / C0
保存
在class GaussianModel
中,球谐通过如下方式保存到ply文件中:
# 从 self._features_dc 张量中分离数据,转置第二维和第三维,扁平化,变为连续存储,并复制到 CPU,最后转换为 numpy 数组
f_dc = self._features_dc.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()
# 从 self._features_rest 张量中分离数据,转置第二维和第三维,扁平化,变为连续存储,并复制到 CPU,最后转换为 numpy 数组
f_rest = self._features_rest.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()
使用
通过如下的方式,将球谐转换为RGB:
if pipe.convert_SHs_python:
# ************ 重点:球谐函数如何实现对颜色的掌控 **********
# 将SH特征的形状调整为(batch_size * num_points,3,(max_sh_degree+1)**2)
shs_view = pc.get_features.transpose(1, 2).view(-1, 3, (pc.max_sh_degree + 1) ** 2)
# 计算相机中心到每个点的方向向量,并归一化
dir_pp = (pc.get_xyz - viewpoint_camera.camera_center.repeat(pc.get_features.shape[0], 1))
# 计算相机中心到每个点的方向向量,并归一化
dir_pp_normalized = dir_pp / dir_pp.norm(dim=1, keepdim=True)
# 使用SH特征将方向向量转换为RGB颜色
sh2rgb = eval_sh(pc.active_sh_degree, shs_view, dir_pp_normalized)
# 将RGB颜色的范围限制在0到1之间
colors_precomp = torch.clamp_min(sh2rgb + 0.5, 0.0)
相关的几个函数为:
def get_features(self):
features_dc = self._features_dc
features_rest = self._features_rest
return torch.cat((features_dc, features_rest), dim=1)
def eval_sh(deg, sh, dirs):
"""
Evaluate spherical harmonics at unit directions
using hardcoded SH polynomials.
Works with torch/np/jnp.
... Can be 0 or more batch dimensions.
Args:
deg: int SH deg. Currently, 0-3 supported
sh: jnp.ndarray SH coeffs [..., C, (deg + 1) ** 2]
dirs: jnp.ndarray unit directions [..., 3]
Returns:
[..., C]
"""
assert deg <= 4 and deg >= 0
coeff = (deg + 1) ** 2
assert sh.shape[-1] >= coeff
result = C0 * sh[..., 0]
if deg > 0:
x, y, z = dirs[..., 0:1], dirs[..., 1:2], dirs[..., 2:3]
result = (result -
C1 * y * sh[..., 1] +
C1 * z * sh[..., 2] -
C1 * x * sh[..., 3])
if deg > 1:
xx, yy, zz = x * x, y * y, z * z
xy, yz, xz = x * y, y * z, x * z
result = (result +
C2[0] * xy * sh[..., 4] +
C2[1] * yz * sh[..., 5] +
C2[2] * (2.0 * zz - xx - yy) * sh[..., 6] +
C2[3] * xz * sh[..., 7] +
C2[4] * (xx - yy) * sh[..., 8])
if deg > 2:
result = (result +
C3[0] * y * (3 * xx - yy) * sh[..., 9] +
C3[1] * xy * z * sh[..., 10] +
C3[2] * y * (4 * zz - xx - yy) * sh[..., 11] +
C3[3] * z * (2 * zz - 3 * xx - 3 * yy) * sh[..., 12] +
C3[4] * x * (4 * zz - xx - yy) * sh[..., 13] +
C3[5] * z * (xx - yy) * sh[..., 14] +
C3[6] * x * (xx - 3 * yy) * sh[..., 15])
if deg > 3:
result = (result + C4[0] * xy * (xx - yy) * sh[..., 16] +
C4[1] * yz * (3 * xx - yy) * sh[..., 17] +
C4[2] * xy * (7 * zz - 1) * sh[..., 18] +
C4[3] * yz * (7 * zz - 3) * sh[..., 19] +
C4[4] * (zz * (35 * zz - 30) + 3) * sh[..., 20] +
C4[5] * xz * (7 * zz - 3) * sh[..., 21] +
C4[6] * (xx - yy) * (7 * zz - 1) * sh[..., 22] +
C4[7] * xz * (xx - 3 * yy) * sh[..., 23] +
C4[8] * (xx * (xx - 3 * yy) - yy * (3 * xx - yy)) * sh[..., 24])
return result