[Mesh] SuGaR o3d TriangleMesh
Open3D์์ TriangleMesh ์ง์ ์์ฑํ๊ธฐ
TriangleMesh
๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํ์ฌ 3D ๋ฐ์ดํฐ๋ฅผ ์ ์ํ ์ ์์ต๋๋ค. Open3D์์ TriangleMesh
๋ฅผ ์ง์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋จ๊ณ๋ณ๋ก ์ค๋ช
ํ๊ฒ ์ต๋๋ค. ์ด ๊ณผ์ ์์๋ ์ ์ (vertices)๊ณผ ์ผ๊ฐํ(triangles)์ ๋ช
์์ ์ผ๋ก ์ ์ํ์ฌ ๋ฉ์ฌ๋ฅผ ์์ฑํฉ๋๋ค.
Open3D ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
๋จผ์ Open3D ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ค์น๋์ด ์์ด์ผ ํฉ๋๋ค. ์ค์นํ์ง ์์๋ค๋ฉด ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ์ค์นํ ์ ์์ต๋๋ค:
pip install open3d
TriangleMesh ๊ฐ์ฒด ์์ฑ
๋ค์์ ์ ์ ๊ณผ ์ผ๊ฐํ์ ์ง์ ์ ์ํ์ฌ TriangleMesh ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์๊ฐํํ๋ ์์์ ๋๋ค.
import open3d as o3d
import numpy as np
# ์ ์ (vertices) ์ ์
vertices = np.array([
[0, 0, 0], # Vertex 0
[1, 0, 0], # Vertex 1
[0, 1, 0], # Vertex 2
[0, 0, 1] # Vertex 3
])
# ์ผ๊ฐํ (triangles) ์ ์
triangles = np.array([
[0, 1, 2], # Triangle 0
[0, 1, 3], # Triangle 1
[0, 2, 3], # Triangle 2
[1, 2, 3] # Triangle 3
])
# TriangleMesh ๊ฐ์ฒด ์์ฑ
mesh = o3d.geometry.TriangleMesh()
mesh.vertices = o3d.utility.Vector3dVector(vertices)
mesh.triangles = o3d.utility.Vector3iVector(triangles)
# ๋ฉ์ฌ์ ์์ ์ถ๊ฐ (์ ํ ์ฌํญ)
mesh.vertex_colors = o3d.utility.Vector3dVector(np.array([
[1, 0, 0], # Color for Vertex 0
[0, 1, 0], # Color for Vertex 1
[0, 0, 1], # Color for Vertex 2
[1, 1, 0] # Color for Vertex 3
]))
# ๋ฉ์ฌ ์๊ฐํ
o3d.visualization.draw_geometries([mesh])
- ์ ์ ์ ์:์ ์ ์ 3์ฐจ์ ๊ณต๊ฐ์ ์ขํ๋ก ์ ์๋ฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋ค ๊ฐ์ ์ ์ ์ ์ ์ํ์ต๋๋ค.
- ์ผ๊ฐํ ์ ์:์ผ๊ฐํ์ ์ ์ ์ ์ธ๋ฑ์ค๋ก ์ ์๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, [0, 1, 2]๋ ์ฒซ ๋ฒ์งธ, ๋ ๋ฒ์งธ, ์ธ ๋ฒ์งธ ์ ์ ์ ์ฐ๊ฒฐํ๋ ์ผ๊ฐํ์ ์๋ฏธํฉ๋๋ค.
- TriangleMesh ๊ฐ์ฒด ์์ฑ:TriangleMesh ๊ฐ์ฒด๋ฅผ ์์ฑํ ํ, ์ ์ ๊ณผ ์ผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ Vector3dVector์ Vector3iVector๋ฅผ ์ฌ์ฉํ์ฌ ์ค์ ํฉ๋๋ค.
- ์์ ์ถ๊ฐ (์ ํ ์ฌํญ):๊ฐ ์ ์ ์ ์์์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์ด๋ ์๊ฐํํ ๋ ์ ์ฉํฉ๋๋ค.
- ์๊ฐํ:draw_geometries ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑํ ๋ฉ์ฌ๋ฅผ ์๊ฐํํฉ๋๋ค.
์๋์ open3d official document ์์ ์ ๊ทธ๋ฆผ์ ๋ณด๋ฉด vertices์ triangle์ด ์ด๋ป๊ฒ ์ ์๋์ด TriangleMesh ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํ๊ฒ ๋๋์ง ์ ์ ์์ต๋๋ค.
https://www.open3d.org/docs/release/python_api/open3d.utility.Vector3iVector.html
์ด์ SuGaR์์ TriangleMesh๋ฅผ ์ด๋ป๊ฒ ๋ถ๋ฌ์ค๋์ง์ ์ด๋ป๊ฒ ์๋กญ๊ฒ ์ ์ํด์ ๋๊ธฐ๋์ง ๋ด ์๋ค.
- ์ ์ฅ๋ TriangleMesh ๊ฐ์ฒด๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฒ์
o3d.io.read_triangle_mesh
๋ก ํฉ๋๋ค.
# sugar_extractors/refined_mesh.py
# --- Loading coarse mesh ---
o3d_mesh = o3d.io.read_triangle_mesh(sugar_mesh_path)
# --- Loading refined SuGaR model ---
checkpoint = torch.load(refined_model_path, map_location=nerfmodel.device)
refined_sugar = SuGaR(
nerfmodel=nerfmodel,
points=checkpoint['state_dict']['_points'],
colors=SH2RGB(checkpoint['state_dict']['_sh_coordinates_dc'][:, 0, :]),
initialize=False,
sh_levels=nerfmodel.gaussians.active_sh_degree+1,
keep_track_of_knn=False,
knn_to_track=0,
beta_mode='average',
surface_mesh_to_bind=o3d_mesh,
n_gaussians_per_surface_triangle=n_gaussians_per_surface_triangle,
)
refined_sugar.load_state_dict(checkpoint['state_dict'])
refined_sugar.eval()
- ์๋ก์ด TriangleMesh ๊ฐ์ฒด๋ฅผ ๋ง๋ค๋๋,
o3d.geometry.TriangleMesh()
๋ก ์ ์ํ๊ณ , ์ด์vertices
,triangles
,vertex_normals
,vertex_colors
๋ฅผ ๋๊ฒจ์ค๋๋ค.
# sugar_extractors/refined_mesh.py
new_o3d_mesh = o3d.geometry.TriangleMesh()
new_o3d_mesh.vertices = o3d.utility.Vector3dVector(new_verts.cpu().numpy())
new_o3d_mesh.triangles = o3d.utility.Vector3iVector(new_faces.cpu().numpy())
new_o3d_mesh.vertex_normals = o3d.utility.Vector3dVector(new_normals.cpu().numpy())
new_o3d_mesh.vertex_colors = o3d.utility.Vector3dVector(torch.ones_like(new_verts).cpu().numpy())
refined_sugar = SuGaR(
nerfmodel=nerfmodel,
points=None,
colors=None,
initialize=False,
sh_levels=nerfmodel.gaussians.active_sh_degree+1,
keep_track_of_knn=False,
knn_to_track=0,
beta_mode='average',
surface_mesh_to_bind=new_o3d_mesh,
n_gaussians_per_surface_triangle=refined_sugar.n_gaussians_per_surface_triangle,
)
- ์ด๋, new TriangleMesh์ธ new_o3d_mesh์ ์ ์ฅํ๋
vertices
,triangles
,vertex_normals
,vertex_colors
๋ ์์์ ๋ฏธ๋ฆฌ mesh์์verts_list()
,face_list()
,faces_normals_list()
๋ก ๋ถ๋ฌ์์ postprocess๋ฅผ ํ ์ ๋ณด์ ๋๋ค.
- ์๋์ ๊ฐ์ด
0๋ฒ์งธ mesh
๋งverts_list()[0]
,faces_list()[0]
,faces_normals_list()
์์ ์ธ๋ฑ์ฑํ์ฌ ์ฌ์ฉํฉ๋๋ค. new_verts = refined_sugar.surface_mesh.verts_list()[0].detach().clone()
new_faces = refined_sugar.surface_mesh.faces_list()[0].detach().clone()
new_normals = refined_sugar.surface_mesh.faces_normals_list()[0].detach().clone()
-
postprocess๋ฅผ ํ๋ฉด ์ต์ข ์ ์ผ๋ก
face_mask
๋ ๋ด๋ถ ์ผ๊ฐํ๊ณผ ๊ฒฝ๊ณ ์ผ๊ฐํ์ ๊ตฌ๋ถํ๋ฉฐ, ๊ฒฝ๊ณ ์ผ๊ฐํ ์ค์์ ๋ฐ๋๊ฐ ๋์ ์ผ๊ฐํ์ ๋ค์ ํฌํจ์ํต๋๋ค. ์ด๋ฅผ ํตํด ํ์ฒ๋ฆฌ๋ ๋ฉ์ฌ๋ ๋ถํ์ํ ๊ฒฝ๊ณ ์ผ๊ฐํ์ด ์ ๊ฑฐ๋๊ณ , ์ค์ํ ๊ฒฝ๊ณ ์ผ๊ฐํ์ ๋ณต๊ตฌ๋ ํํ๋ก ์ ์ง๋ฉ๋๋ค.if postprocess_mesh: CONSOLE.print("Postprocessing mesh by removing border triangles with low-opacity gaussians...") with torch.no_grad(): new_verts = refined_sugar.surface_mesh.verts_list()[0].detach().clone() new_faces = refined_sugar.surface_mesh.faces_list()[0].detach().clone() new_normals = refined_sugar.surface_mesh.faces_normals_list()[0].detach().clone() # For each face, get the 3 edges edges0 = new_faces[..., None, (0,1)].sort(dim=-1)[0] edges1 = new_faces[..., None, (1,2)].sort(dim=-1)[0] edges2 = new_faces[..., None, (2,0)].sort(dim=-1)[0] all_edges = torch.cat([edges0, edges1, edges2], dim=-2) # We start by identifying the inside faces and border faces face_mask = refined_sugar.strengths[..., 0] > -1. for i in range(postprocess_iterations): CONSOLE.print("\nStarting postprocessing iteration", i) # We look for edges that appear in the list at least twice (their NN is themselves) edges_neighbors = knn_points(all_edges[face_mask].view(1, -1, 2).float(), all_edges[face_mask].view(1, -1, 2).float(), K=2) # If all edges of a face appear in the list at least twice, then the face is inside the mesh is_inside = (edges_neighbors.dists[0][..., 1].view(-1, 3) < 0.01).all(-1) # We update the mask by removing border faces face_mask[face_mask.clone()] = is_inside # We then add back border faces with high-density face_centers = new_verts[new_faces].mean(-2) face_densities = refined_sugar.compute_density(face_centers[~face_mask]) face_mask[~face_mask.clone()] = face_densities > postprocess_density_threshold # And we create the new mesh and SuGaR model new_faces = new_faces[face_mask] new_normals = new_normals[face_mask] new_scales = refined_sugar._scales.reshape(len(face_mask), -1, 2)[face_mask].view(-1, 2) new_quaternions = refined_sugar._quaternions.reshape(len(face_mask), -1, 2)[face_mask].view(-1, 2) new_densities = refined_sugar.all_densities.reshape(len(face_mask), -1, 1)[face_mask].view(-1, 1) new_sh_coordinates_dc = refined_sugar._sh_coordinates_dc.reshape(len(face_mask), -1, 1, 3)[face_mask].view(-1, 1, 3) new_sh_coordinates_rest = refined_sugar._sh_coordinates_rest.reshape(len(face_mask), -1, 15, 3)[face_mask].view(-1, 15, 3)
- density๊ฐ ๋์ faces(์ผ๊ฐํ)์ ์ด๋ป๊ฒ ์ฐพ์๊น์?
sugar_scene/sugar_model.py
์์compute_density
๋ฅผ ๋ณด๋ฉด ์ ์ ์์ต๋๋ค.
# sugar_scene/sugar_model.py
class SuGaR(nn.Module):
...
def get_gaussians_closest_to_samples(self, x, n_closest_gaussian=None):
if n_closest_gaussian is None:
if not hasattr(self, 'knn_to_track'):
print("Variable knn_to_track not found. Setting it to 16.")
self.knn_to_track = 16
n_closest_gaussian = self.knn_to_track
closest_gaussians_idx = knn_points(x[None], self.points[None], K=n_closest_gaussian).idx[0]
return closest_gaussians_idx
def compute_density(self, x, closest_gaussians_idx=None, density_factor=1.,
return_closest_gaussian_opacities=False):
if closest_gaussians_idx is None:
closest_gaussians_idx = self.get_gaussians_closest_to_samples(x)
# Gather gaussian parameters
close_gaussian_centers = self.points[closest_gaussians_idx]
close_gaussian_inv_scaled_rotation = self.get_covariance(
return_full_matrix=True, return_sqrt=True, inverse_scales=True
)[closest_gaussians_idx]
close_gaussian_strengths = self.strengths[closest_gaussians_idx]
# Compute the density field as a sum of local gaussian opacities
shift = (x[:, None] - close_gaussian_centers)
warped_shift = close_gaussian_inv_scaled_rotation.transpose(-1, -2) @ shift[..., None]
neighbor_opacities = (warped_shift[..., 0] * warped_shift[..., 0]).sum(dim=-1).clamp(min=0., max=1e8)
neighbor_opacities = density_factor * close_gaussian_strengths[..., 0] * torch.exp(-1. / 2 * neighbor_opacities)
densities = neighbor_opacities.sum(dim=-1)
if return_closest_gaussian_opacities:
return densities, neighbor_opacities
else:
return densities # Shape is (n_points, )
- ์ต์ข
์ ์ผ๋ก ์ ์ฅํ mesh์๋
verts_list()
,face_list()
,,faces_normals_list()
textures.verts_uvs_list()
,textures.faces_uvs_list()
,textures.maps_padded()
๋ฅผ ํฌํจ์์ผ ์ ์ฅํด์ค๋๋ค.
# Compute texture
with torch.no_grad():
verts_uv, faces_uv, texture_img = extract_texture_image_and_uv_from_gaussians(
refined_sugar, square_size=square_size, n_sh=1, texture_with_gaussian_renders=True)
textures_uv = TexturesUV(
maps=texture_img[None], #texture_img[None]),
verts_uvs=verts_uv[None],
faces_uvs=faces_uv[None],
sampling_mode='nearest',
)
textured_mesh = Meshes(
verts=[refined_sugar.surface_mesh.verts_list()[0]],
faces=[refined_sugar.surface_mesh.faces_list()[0]],
textures=textures_uv,
)
CONSOLE.print("Texture extracted.")
CONSOLE.print("Texture shape:", texture_img.shape)
CONSOLE.print("Saving textured mesh...")
with torch.no_grad():
save_obj(
mesh_save_path,
verts=textured_mesh.verts_list()[0],
faces=textured_mesh.faces_list()[0],
verts_uvs=textured_mesh.textures.verts_uvs_list()[0],
faces_uvs=textured_mesh.textures.faces_uvs_list()[0],
texture_map=textured_mesh.textures.maps_padded()[0].clamp(0., 1.),
)
CONSOLE.print("Texture saved at:", mesh_save_path)
return mesh_save_path
Leave a comment