3 minute read

3dgs์—์„œ 1,000 iters๋งˆ๋‹ค SH์˜ degree๋ฅผ ์˜ฌ๋ ค์„œ features_rest์˜ ๊ฐ’์„ ์ถ”๊ฐ€์ ์œผ๋กœ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.

3dgs/train.py์—์„œ iteration 1000๋งˆ๋‹ค oneupSHdegree()๋ฅผ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

# 3dgs/train.py

def training(dataset, opt, pipe, testing_iterations, saving_iterations, checkpoint_iterations, checkpoint, debug_from):

...

        # Every 1000 its we increase the levels of SH up to a maximum degree
        if iteration % 1000 == 0:
            gaussians.oneupSHdegree()

...

        render_pkg = render(viewpoint_cam, gaussians, pipe, bg)

self.max_sh_degree์— ๋”ฐ๋ฅธ self._features_dc, self._features_rest์˜ shape

  • ๋งŒ์•ฝ self.max_sh_degree์ธ self.sh_degree๊ฐ€ default์ธ 3์ด๋ฉด ๊ทธ์— ๋งž๊ฒŒ features๊ฐ€ ์ •์˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • ์ด๋•Œ features์˜ shape์€ (n_points, RGB, (self.max_sh_degree + 1) ** 2) = (n_points, 3, (self.max_sh_degree + 1) ** 2)์ž…๋‹ˆ๋‹ค.
  • features๋Š” self._features_dc์™€ self._features_rest๋กœ ๋ถ„๋ฆฌ๋˜๊ณ , transpose(1, 2)๋˜๋ฉด์„œ shape์ด ๋‹ค์Œ๊ณผ ๊ฐ™์•„์ง‘๋‹ˆ๋‹ค.
    • self._features_dc # (n_points, sh 0 degree, RGB) = (n_points, 1, 3)

      image

    • self._features_rest # (n_points, (sh 0 ~ sh max degree) - sh 0 degree, RGB) = (n_points, (self.max_sh_degree + 1) ** 2 - 1, 3) = (n_points, (3 + 1) ** 2 - 1, 3) = (n_points, 15, 3)

      image

self._features_dc, self._feature_rest์˜ ์ดˆ๊ธฐ๊ฐ’

  • self._features_dc๋Š” pcd.colors์˜ RGB2SHํ•จ์ˆ˜๋กœ ์ดˆ๊ธฐํ™”
  • self._features_rest๋Š” 0๋กœ ์ดˆ๊ธฐํ™”
# 3dgs/scene/gaussian_model.py

class GaussianModel:
...
    def create_from_pcd(self, pcd : BasicPointCloud, spatial_lr_scale : float):
        self.spatial_lr_scale = spatial_lr_scale
        fused_point_cloud = torch.tensor(np.asarray(pcd.points)).float().cuda()
        fused_color = RGB2SH(torch.tensor(np.asarray(pcd.colors)).float().cuda())
        features = torch.zeros((fused_color.shape[0], 3, (self.max_sh_degree + 1) ** 2)).float().cuda()
        features[:, :3, 0 ] = fused_color
        features[:, 3:, 1:] = 0.0

        print("Number of points at initialisation : ", fused_point_cloud.shape[0])

        dist2 = torch.clamp_min(distCUDA2(torch.from_numpy(np.asarray(pcd.points)).float().cuda()), 0.0000001)
        scales = torch.log(torch.sqrt(dist2))[...,None].repeat(1, 3)
        rots = torch.zeros((fused_point_cloud.shape[0], 4), device="cuda")
        rots[:, 0] = 1

        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))

oneupSHdegree()๋Š” self.max_sh_degree๋ณด๋‹ค ์ž‘์„ ๊ฒฝ์šฐ, self.active_sh_degree๋ฅผ 1๋งŒํผ ์˜ฌ๋ ค์ค๋‹ˆ๋‹ค.

self.max_sh_degree = 3์ผ๋•Œ, iterations์ด 30,000์ด๋ฉด, 1,000 iters ๋งˆ๋‹ค์˜ features_rest๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•™์Šต์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

  • 0~999๊นŒ์ง€๋Š” sh 0๋งŒ ํ•™์Šต (features_rest์˜ ๊ฐ’์ด ๋ชจ๋‘ ์ดˆ๊ธฐ๊ฐ’ 0์ž…๋‹ˆ๋‹ค.)

    image

  • 1000~1999๊นŒ์ง€๋Š” self.active_sh_degree = 1์ด๋ฏ€๋กœ sh 1์„ ์ถ”๊ฐ€๋กœ ํ•™์Šต (features_rest์˜ ๊ฐ’์ด ํ•™์Šต๋˜๊ธฐ ์‹œ์ž‘ํ•จ, ์ด ์ค‘์—์„œ๋„ (self.active_sh_degree + 1) ** 2 - 1 = (1 + 1) ** 2 - 1 = 3์ธ 3๊ฐœ์˜ features์— ๋Œ€ํ•ด์„œ๋งŒ ํ•™์Šต์ด ๋˜๊ณ , 3๊ฐœ ์ดํ›„์˜ ๊ฐ’์€ ์—ฌ์ „ํžˆ ์ดˆ๊ธฐ๊ฐ’์ธ 0์ž…๋‹ˆ๋‹ค.)

    • 0~2 features dim์— ๋Œ€ํ•ด์„œ๋Š” ํ•™์Šต๋˜๊ธฐ ์‹œ์ž‘ํ•˜์—ฌ, 0์ด ์•„๋‹™๋‹ˆ๋‹ค.

      image

    • 3 ์ด์ƒ features dim์— ๋Œ€ํ•ด์„œ๋Š” ์•„์ง ํ•™์Šต์ด ์ง„ํ–‰๋˜์ง€ ์•Š์•„, ๋ชจ๋‘ 0์ž…๋‹ˆ๋‹ค.

      image

  • 2000~2999๊นŒ์ง€๋Š” self.active_sh_degree = 2์ด๋ฏ€๋กœ sh 2์„ ์ถ”๊ฐ€๋กœ ํ•™์Šต ((self.active_sh_degree + 1) ** 2 - 1 = (2 + 1) ** 2 - 1 = 8์ธ 8๊ฐœ์˜ features์— ๋Œ€ํ•ด์„œ๋งŒ ํ•™์Šต์ด ๋˜๊ณ , 8๊ฐœ ์ดํ›„์˜ ๊ฐ’์€ ์—ฌ์ „ํžˆ ์ดˆ๊ธฐ๊ฐ’์ธ 0์ž…๋‹ˆ๋‹ค.)

    • 0~7 features dim์— ๋Œ€ํ•ด์„œ๋Š” ํ•™์Šต์— ํฌํ•จ๋˜๋ฏ€๋กœ, 0์ด ์•„๋‹™๋‹ˆ๋‹ค.

      image

    • 8 ์ด์ƒ features dim์— ๋Œ€ํ•ด์„œ๋Š” ์•„์ง ํ•™์Šต์ด ์ง„ํ–‰๋˜์ง€ ์•Š์•„, ๋ชจ๋‘ 0์ž…๋‹ˆ๋‹ค.

      image

  • 3000~3999๊นŒ์ง€๋Š” self.active_sh_degree = 3์ด๋ฏ€๋กœ sh 3์„ ์ถ”๊ฐ€๋กœ ํ•™์Šต ((self.active_sh_degree + 1) ** 2 - 1 = (3 + 1) ** 2 - 1 = 15์ธ 15๊ฐœ์˜ features์— ๋Œ€ํ•ด์„œ ๋ชจ๋‘ ํ•™์Šต์ด ๋ฉ๋‹ˆ๋‹ค.)

    • 0~14 features dim์— ๋Œ€ํ•ด์„œ ๋ชจ๋‘ ํ•™์Šต์— ํฌํ•จ๋˜๋ฏ€๋กœ, 0์ด ์•„๋‹™๋‹ˆ๋‹ค.

      image image

  • 4000~30000์—์„œ๋Š” sh 0, 1, 2, 3์— ๋Œ€ํ•ด ๊ณ„์† ํ•™์Šต (self.active_sh_degree๊ฐ€ self.max_sh_degree = 3์— ๋„๋‹ฌํ•˜์˜€๊ณ , ์•ž์„œ ์ดˆ๊ธฐํ™” ํ•  ๋•Œ, features_rest์—์„œ sh๊ฐ€ 3๋ณด๋‹ค ํฐ features dim์€ ์• ์ดˆ์— ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)

    image

# 3dgs/scene/gaussian_model.py

class GaussianModel:
...
    def oneupSHdegree(self):
        if self.active_sh_degree < self.max_sh_degree:
            self.active_sh_degree += 1
# 3dgs/arguments/__init__.py

class ModelParams(ParamGroup): 
    def __init__(self, parser, sentinel=False):
        self.sh_degree = 3
...
  • override_color = None, pipe.convert_SHs_python = None์ด default์ด๋ฏ€๋กœ, shs = pc.get_features ๋ถ€๋ถ„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (pc๋Š” point cloud์— ํ•ด๋‹น)
  • get_features๋Š” features_dc์™€ features_rest๋ฅผ feature dimension์—์„œ concatํ•ฉ๋‹ˆ๋‹ค.
    • features_dc์˜ shape # (n_points, n_features_dc, RGB)
    • features_rest์˜ shape # (n_points, n_features_rest, RGB)
# 3dgs/gaussian_renderer/__init__.py

def render(viewpoint_camera, pc : GaussianModel, pipe, bg_color : torch.Tensor, scaling_modifier = 1.0, override_color = None):

...

    # If precomputed colors are provided, use them. Otherwise, if it is desired to precompute colors
    # from SHs in Python, do it. If not, then SH -> RGB conversion will be done by rasterizer.
    shs = None
    colors_precomp = None
    if override_color is None:
        if pipe.convert_SHs_python:
            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)
            sh2rgb = eval_sh(pc.active_sh_degree, shs_view, dir_pp_normalized)
            colors_precomp = torch.clamp_min(sh2rgb + 0.5, 0.0)
        else:
            shs = pc.get_features # default์—์„œ ์ด ๋ถ€๋ถ„์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    else:
        colors_precomp = override_color

# 3dgs/scene/gaussian_model.py

class GaussianModel:
...
    @property
    def get_features(self):
        features_dc = self._features_dc
        features_rest = self._features_rest
        return torch.cat((features_dc, features_rest), dim=1)

Leave a comment