[3D CV ์ฐ๊ตฌ] 3DGS Spherical Harmonics oneupSHdegree
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)
-
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)
-
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
์ ๋๋ค.) -
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
์ด ์๋๋๋ค. -
3 ์ด์ features dim์ ๋ํด์๋ ์์ง ํ์ต์ด ์งํ๋์ง ์์, ๋ชจ๋
0
์ ๋๋ค.
-
-
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
์ด ์๋๋๋ค. -
8 ์ด์ features dim์ ๋ํด์๋ ์์ง ํ์ต์ด ์งํ๋์ง ์์, ๋ชจ๋
0
์ ๋๋ค.
-
-
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
์ด ์๋๋๋ค.
-
-
4000~30000์์๋ sh 0, 1, 2, 3์ ๋ํด ๊ณ์ ํ์ต (
self.active_sh_degree
๊ฐself.max_sh_degree = 3
์ ๋๋ฌํ์๊ณ , ์์ ์ด๊ธฐํ ํ ๋,features_rest
์์ sh๊ฐ 3๋ณด๋ค ํฐ features dim์ ์ ์ด์ ์กด์ฌํ์ง ์์ต๋๋ค.)
# 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