Integrated Triplane Gaussian Transformers & Better 3DGS Import & Export
This commit is contained in:
@@ -29,6 +29,7 @@ class DiffTextureBaker:
|
||||
self.batch_size = batch_size
|
||||
|
||||
def prepare_img(self, img):
|
||||
# (H, W, C) -> (C, H, W)
|
||||
img_new = img.permute(2, 0, 1).unsqueeze(0).to(self.device)
|
||||
img_new = F.interpolate(img_new, (self.ref_size_H, self.ref_size_W), mode="bilinear", align_corners=False).contiguous()
|
||||
return img_new
|
||||
|
||||
@@ -31,7 +31,7 @@ from simple_knn._C import distCUDA2
|
||||
|
||||
from ..shared_utils.sh_utils import eval_sh, SH2RGB, RGB2SH
|
||||
from ..mesh_processer.mesh import Mesh
|
||||
from ..mesh_processer.mesh_utils import decimate_mesh, clean_mesh
|
||||
from ..mesh_processer.mesh_utils import read_gs_ply
|
||||
|
||||
def inverse_sigmoid(x):
|
||||
return torch.log(x/(1-x))
|
||||
@@ -500,6 +500,11 @@ class GaussianModel:
|
||||
lr = self.xyz_scheduler_args(iteration)
|
||||
param_group['lr'] = lr
|
||||
return lr
|
||||
|
||||
def reset_opacity(self):
|
||||
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 construct_list_of_attributes(self):
|
||||
l = ['x', 'y', 'z', 'nx', 'ny', 'nz']
|
||||
@@ -525,9 +530,7 @@ class GaussianModel:
|
||||
def get_mesh(self):
|
||||
xyz = self._xyz.detach().cpu().numpy()
|
||||
|
||||
def save_ply(self, path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
|
||||
def to_ply(self):
|
||||
xyz = self._xyz.detach().cpu().numpy()
|
||||
normals = np.zeros_like(xyz)
|
||||
f_dc = self._features_dc.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()
|
||||
@@ -542,45 +545,10 @@ class GaussianModel:
|
||||
attributes = np.concatenate((xyz, normals, f_dc, f_rest, opacities, scale, rotation), axis=1)
|
||||
elements[:] = list(map(tuple, attributes))
|
||||
el = PlyElement.describe(elements, 'vertex')
|
||||
PlyData([el]).write(path)
|
||||
return PlyData([el])
|
||||
|
||||
def reset_opacity(self):
|
||||
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):
|
||||
plydata = PlyData.read(path)
|
||||
|
||||
xyz = np.stack((np.asarray(plydata.elements[0]["x"]),
|
||||
np.asarray(plydata.elements[0]["y"]),
|
||||
np.asarray(plydata.elements[0]["z"])), axis=1)
|
||||
opacities = np.asarray(plydata.elements[0]["opacity"])[..., np.newaxis]
|
||||
|
||||
print("Number of points at loading : ", xyz.shape[0])
|
||||
|
||||
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"])
|
||||
|
||||
extra_f_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("f_rest_")]
|
||||
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])
|
||||
# Reshape (P,F*SH_coeffs) to (P, F, SH_coeffs except DC)
|
||||
features_extra = features_extra.reshape((features_extra.shape[0], 3, (self.max_sh_degree + 1) ** 2 - 1))
|
||||
|
||||
scale_names = [p.name for p in plydata.elements[0].properties if p.name.startswith("scale_")]
|
||||
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")]
|
||||
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])
|
||||
def create_from_ply(self, plydata):
|
||||
xyz, features_dc, features_extra, opacities, scales, rots = read_gs_ply(plydata)
|
||||
|
||||
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))
|
||||
@@ -894,6 +862,11 @@ class Renderer:
|
||||
if isinstance(input, Mesh):
|
||||
# load from 3D mesh
|
||||
self.gaussians.create_from_mesh(input, num_pts)
|
||||
elif isinstance(input, PlyData):
|
||||
self.gaussians.create_from_ply(input)
|
||||
elif isinstance(input, PointCloud):
|
||||
# load from a provided pcd
|
||||
self.gaussians.create_from_pcd(input, 1)
|
||||
elif input is None:
|
||||
# init from random point cloud
|
||||
|
||||
@@ -913,9 +886,6 @@ class Renderer:
|
||||
points=xyz, colors=SH2RGB(shs), normals=np.zeros((num_pts, 3))
|
||||
)
|
||||
self.gaussians.create_from_pcd(pcd, 10)
|
||||
elif isinstance(input, PointCloud):
|
||||
# load from a provided pcd
|
||||
self.gaussians.create_from_pcd(input, 1)
|
||||
else:
|
||||
self.gaussians.create_from_uv_data(input)
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import torch
|
||||
from dataclasses import dataclass, field
|
||||
from einops import rearrange
|
||||
|
||||
from ..tgs import find
|
||||
from ..tgs.models.image_feature import ImageFeature
|
||||
from ..tgs.utils.saving import SaverMixin
|
||||
from ..tgs.utils.config import parse_structured
|
||||
from ..tgs.utils.ops import points_projection
|
||||
from ..tgs.utils.misc import load_module_weights
|
||||
from ..tgs.utils.typing import *
|
||||
|
||||
class TGS(torch.nn.Module, SaverMixin):
|
||||
@dataclass
|
||||
class Config:
|
||||
weights: Optional[str] = None
|
||||
weights_ignore_modules: Optional[List[str]] = None
|
||||
|
||||
camera_embedder_cls: str = ""
|
||||
camera_embedder: dict = field(default_factory=dict)
|
||||
|
||||
image_feature: dict = field(default_factory=dict)
|
||||
|
||||
image_tokenizer_cls: str = ""
|
||||
image_tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
tokenizer_cls: str = ""
|
||||
tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
backbone_cls: str = ""
|
||||
backbone: dict = field(default_factory=dict)
|
||||
|
||||
post_processor_cls: str = ""
|
||||
post_processor: dict = field(default_factory=dict)
|
||||
|
||||
renderer_cls: str = ""
|
||||
renderer: dict = field(default_factory=dict)
|
||||
|
||||
pointcloud_generator_cls: str = ""
|
||||
pointcloud_generator: dict = field(default_factory=dict)
|
||||
|
||||
pointcloud_encoder_cls: str = ""
|
||||
pointcloud_encoder: dict = field(default_factory=dict)
|
||||
|
||||
cfg: Config
|
||||
|
||||
def load_weights(self, weights: str, ignore_modules: Optional[List[str]] = None):
|
||||
state_dict = load_module_weights(
|
||||
weights, ignore_modules=ignore_modules, map_location="cpu"
|
||||
)
|
||||
self.load_state_dict(state_dict, strict=False)
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
|
||||
self.image_tokenizer = find(self.cfg.image_tokenizer_cls)(
|
||||
self.cfg.image_tokenizer
|
||||
)
|
||||
|
||||
assert self.cfg.camera_embedder_cls == 'tgs.models.networks.MLP'
|
||||
weights = self.cfg.camera_embedder.pop("weights") if "weights" in self.cfg.camera_embedder else None
|
||||
self.camera_embedder = find(self.cfg.camera_embedder_cls)(**self.cfg.camera_embedder)
|
||||
if weights:
|
||||
from ..tgs.utils.misc import load_module_weights
|
||||
weights_path, module_name = weights.split(":")
|
||||
state_dict = load_module_weights(
|
||||
weights_path, module_name=module_name, map_location="cpu"
|
||||
)
|
||||
self.camera_embedder.load_state_dict(state_dict)
|
||||
|
||||
self.image_feature = ImageFeature(self.cfg.image_feature)
|
||||
|
||||
self.tokenizer = find(self.cfg.tokenizer_cls)(self.cfg.tokenizer)
|
||||
|
||||
self.backbone = find(self.cfg.backbone_cls)(self.cfg.backbone)
|
||||
|
||||
self.post_processor = find(self.cfg.post_processor_cls)(
|
||||
self.cfg.post_processor
|
||||
)
|
||||
|
||||
self.renderer = find(self.cfg.renderer_cls)(self.cfg.renderer)
|
||||
|
||||
# pointcloud generator
|
||||
self.pointcloud_generator = find(self.cfg.pointcloud_generator_cls)(self.cfg.pointcloud_generator)
|
||||
|
||||
self.point_encoder = find(self.cfg.pointcloud_encoder_cls)(self.cfg.pointcloud_encoder)
|
||||
|
||||
# load checkpoint
|
||||
if self.cfg.weights is not None:
|
||||
self.load_weights(self.cfg.weights, self.cfg.weights_ignore_modules)
|
||||
|
||||
def _forward(self, batch: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# generate point cloud
|
||||
out = self.pointcloud_generator(batch)
|
||||
pointclouds = out["points"]
|
||||
|
||||
batch_size, n_input_views = batch["rgb_cond"].shape[:2]
|
||||
|
||||
# Camera modulation
|
||||
camera_extri = batch["c2w_cond"].view(*batch["c2w_cond"].shape[:-2], -1)
|
||||
camera_intri = batch["intrinsic_normed_cond"].view(*batch["intrinsic_normed_cond"].shape[:-2], -1)
|
||||
camera_feats = torch.cat([camera_intri, camera_extri], dim=-1)
|
||||
|
||||
camera_feats = self.camera_embedder(camera_feats)
|
||||
|
||||
input_image_tokens: Float[Tensor, "B Cit Nit"] = self.image_tokenizer(
|
||||
rearrange(batch["rgb_cond"], 'B Nv H W C -> B Nv C H W'),
|
||||
modulation_cond=camera_feats,
|
||||
)
|
||||
input_image_tokens = rearrange(input_image_tokens, 'B Nv C Nt -> B (Nv Nt) C', Nv=n_input_views)
|
||||
|
||||
# get image features for projection
|
||||
image_features = self.image_feature(
|
||||
rgb = batch["rgb_cond"],
|
||||
mask = batch.get("mask_cond", None),
|
||||
feature = input_image_tokens
|
||||
)
|
||||
|
||||
# only support number of input view is one
|
||||
c2w_cond = batch["c2w_cond"].squeeze(1)
|
||||
intrinsic_cond = batch["intrinsic_cond"].squeeze(1)
|
||||
proj_feats = points_projection(pointclouds, c2w_cond, intrinsic_cond, image_features)
|
||||
|
||||
point_cond_embeddings = self.point_encoder(torch.cat([pointclouds, proj_feats], dim=-1))
|
||||
tokens: Float[Tensor, "B Ct Nt"] = self.tokenizer(batch_size, cond_embeddings=point_cond_embeddings)
|
||||
|
||||
tokens = self.backbone(
|
||||
tokens,
|
||||
encoder_hidden_states=input_image_tokens,
|
||||
modulation_cond=None,
|
||||
)
|
||||
|
||||
scene_codes = self.post_processor(self.tokenizer.detokenize(tokens))
|
||||
rend_out = self.renderer(scene_codes,
|
||||
query_points=pointclouds,
|
||||
additional_features=proj_feats,
|
||||
**batch)
|
||||
|
||||
return {**out, **rend_out}
|
||||
|
||||
def forward(self, batch):
|
||||
plys = []
|
||||
|
||||
out = self._forward(batch)
|
||||
batch_size = batch["index"].shape[0]
|
||||
for b in range(batch_size):
|
||||
if batch["view_index"][b, 0] == 0:
|
||||
plys.append(out["3dgs"][b].to_ply())
|
||||
|
||||
#self.render_test_video(out, batch, b)
|
||||
|
||||
return plys
|
||||
|
||||
def render_test_video(self, out, batch, b):
|
||||
for index, render_image in enumerate(out["comp_rgb"][b]):
|
||||
view_index = batch["view_index"][b, index]
|
||||
self.save_image_grid(
|
||||
f"video/{batch['instance_id'][b]}/{view_index}.png",
|
||||
[
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": render_image,
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
}
|
||||
]
|
||||
)
|
||||
Reference in New Issue
Block a user