|
import torch |
|
import numpy as np |
|
import pymeshlab as ml |
|
from pytorch3d.renderer import TexturesVertex |
|
from pytorch3d.structures import Meshes |
|
import torch |
|
import torch.nn.functional as F |
|
import trimesh |
|
from pymeshlab import PercentageValue |
|
import open3d as o3d |
|
|
|
|
|
def tensor2variable(tensor, device): |
|
|
|
return torch.tensor(tensor, device=device, requires_grad=True) |
|
|
|
def rot6d_to_rotmat(x): |
|
"""Convert 6D rotation representation to 3x3 rotation matrix. |
|
Based on Zhou et al., "On the Continuity of Rotation Representations in Neural Networks", CVPR 2019 |
|
Input: |
|
(B,6) Batch of 6-D rotation representations |
|
Output: |
|
(B,3,3) Batch of corresponding rotation matrices |
|
""" |
|
x = x.view(-1, 3, 2) |
|
a1 = x[:, :, 0] |
|
a2 = x[:, :, 1] |
|
b1 = F.normalize(a1) |
|
b2 = F.normalize(a2 - torch.einsum("bi,bi->b", b1, a2).unsqueeze(-1) * b1) |
|
b3 = torch.cross(b1, b2) |
|
return torch.stack((b1, b2, b3), dim=-1) |
|
|
|
|
|
def fix_vert_color_glb(mesh_path): |
|
from pygltflib import GLTF2, Material, PbrMetallicRoughness |
|
obj1 = GLTF2().load(mesh_path) |
|
obj1.meshes[0].primitives[0].material = 0 |
|
obj1.materials.append(Material( |
|
pbrMetallicRoughness = PbrMetallicRoughness( |
|
baseColorFactor = [1.0, 1.0, 1.0, 1.0], |
|
metallicFactor = 0., |
|
roughnessFactor = 1.0, |
|
), |
|
emissiveFactor = [0.0, 0.0, 0.0], |
|
doubleSided = True, |
|
)) |
|
obj1.save(mesh_path) |
|
|
|
def srgb_to_linear(c_srgb): |
|
c_linear = np.where(c_srgb <= 0.04045, c_srgb / 12.92, ((c_srgb + 0.055) / 1.055) ** 2.4) |
|
return c_linear.clip(0, 1.) |
|
|
|
def save_py3dmesh_with_trimesh_fast(meshes: Meshes, save_glb_path, apply_sRGB_to_LinearRGB=True): |
|
|
|
vertices = meshes.verts_packed().cpu().float().numpy() |
|
triangles = meshes.faces_packed().cpu().long().numpy() |
|
np_color = meshes.textures.verts_features_packed().cpu().float().numpy() |
|
if save_glb_path.endswith(".glb"): |
|
|
|
vertices[:, [0, 2]] = -vertices[:, [0, 2]] |
|
|
|
if apply_sRGB_to_LinearRGB: |
|
np_color = srgb_to_linear(np_color) |
|
assert vertices.shape[0] == np_color.shape[0] |
|
assert np_color.shape[1] == 3 |
|
assert 0 <= np_color.min() and np_color.max() <= 1, f"min={np_color.min()}, max={np_color.max()}" |
|
mesh = trimesh.Trimesh(vertices=vertices, faces=triangles, vertex_colors=np_color) |
|
mesh.remove_unreferenced_vertices() |
|
|
|
mesh.export(save_glb_path) |
|
if save_glb_path.endswith(".glb"): |
|
fix_vert_color_glb(save_glb_path) |
|
print(f"saving to {save_glb_path}") |
|
|
|
def load_mesh_with_trimesh(file_name, file_type=None): |
|
mesh: trimesh.Trimesh = trimesh.load(file_name, file_type=file_type) |
|
if isinstance(mesh, trimesh.Scene): |
|
assert len(mesh.geometry) > 0 |
|
|
|
from io import BytesIO |
|
with BytesIO() as f: |
|
mesh.export(f, file_type="obj") |
|
f.seek(0) |
|
mesh = trimesh.load(f, file_type="obj") |
|
if isinstance(mesh, trimesh.Scene): |
|
|
|
mesh = trimesh.util.concatenate( |
|
tuple(trimesh.Trimesh(vertices=g.vertices, faces=g.faces) |
|
for g in mesh.geometry.values())) |
|
assert isinstance(mesh, trimesh.Trimesh) |
|
|
|
vertices = torch.from_numpy(mesh.vertices).T |
|
faces = torch.from_numpy(mesh.faces).T |
|
colors = None |
|
if mesh.visual is not None: |
|
if hasattr(mesh.visual, 'vertex_colors'): |
|
colors = torch.from_numpy(mesh.visual.vertex_colors)[..., :3].T / 255. |
|
if colors is None: |
|
|
|
colors = torch.ones_like(vertices) * 0.5 |
|
return vertices, faces, colors |
|
|
|
def meshlab_mesh_to_py3dmesh(mesh: ml.Mesh) -> Meshes: |
|
verts = torch.from_numpy(mesh.vertex_matrix()).float() |
|
faces = torch.from_numpy(mesh.face_matrix()).long() |
|
colors = torch.from_numpy(mesh.vertex_color_matrix()[..., :3]).float() |
|
textures = TexturesVertex(verts_features=[colors]) |
|
return Meshes(verts=[verts], faces=[faces], textures=textures) |
|
|
|
|
|
def py3dmesh_to_meshlab_mesh(meshes: Meshes) -> ml.Mesh: |
|
colors_in = F.pad(meshes.textures.verts_features_packed().cpu().float(), [0,1], value=1).numpy().astype(np.float64) |
|
m1 = ml.Mesh( |
|
vertex_matrix=meshes.verts_packed().cpu().float().numpy().astype(np.float64), |
|
face_matrix=meshes.faces_packed().cpu().long().numpy().astype(np.int32), |
|
v_normals_matrix=meshes.verts_normals_packed().cpu().float().numpy().astype(np.float64), |
|
v_color_matrix=colors_in) |
|
return m1 |
|
|
|
|
|
def to_pyml_mesh(vertices,faces): |
|
m1 = ml.Mesh( |
|
vertex_matrix=vertices.cpu().float().numpy().astype(np.float64), |
|
face_matrix=faces.cpu().long().numpy().astype(np.int32), |
|
) |
|
return m1 |
|
|
|
|
|
def to_py3d_mesh(vertices, faces, normals=None): |
|
from pytorch3d.structures import Meshes |
|
from pytorch3d.renderer.mesh.textures import TexturesVertex |
|
mesh = Meshes(verts=[vertices], faces=[faces], textures=None) |
|
if normals is None: |
|
normals = mesh.verts_normals_packed() |
|
|
|
mesh.textures = TexturesVertex(verts_features=[normals / 2 + 0.5]) |
|
return mesh |
|
|
|
|
|
def from_py3d_mesh(mesh): |
|
return mesh.verts_list()[0], mesh.faces_list()[0], mesh.textures.verts_features_packed() |
|
|
|
|
|
def simple_clean_mesh(pyml_mesh: ml.Mesh, apply_smooth=True, stepsmoothnum=1, apply_sub_divide=False, sub_divide_threshold=0.25): |
|
ms = ml.MeshSet() |
|
ms.add_mesh(pyml_mesh, "cube_mesh") |
|
|
|
if apply_smooth: |
|
ms.apply_filter("apply_coord_laplacian_smoothing", stepsmoothnum=stepsmoothnum, cotangentweight=False) |
|
if apply_sub_divide: |
|
ms.apply_filter("meshing_repair_non_manifold_vertices") |
|
ms.apply_filter("meshing_repair_non_manifold_edges", method='Remove Faces') |
|
ms.apply_filter("meshing_surface_subdivision_loop", iterations=2, threshold=PercentageValue(sub_divide_threshold)) |
|
return meshlab_mesh_to_py3dmesh(ms.current_mesh()) |
|
|
|
|
|
|
|
def post_process_mesh(mesh, cluster_to_keep=1000): |
|
""" |
|
Post-process a mesh to filter out floaters and disconnected parts |
|
""" |
|
import copy |
|
print("post processing the mesh to have {} clusterscluster_to_kep".format(cluster_to_keep)) |
|
mesh_0 = copy.deepcopy(mesh) |
|
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm: |
|
triangle_clusters, cluster_n_triangles, cluster_area = (mesh_0.cluster_connected_triangles()) |
|
|
|
triangle_clusters = np.asarray(triangle_clusters) |
|
cluster_n_triangles = np.asarray(cluster_n_triangles) |
|
cluster_area = np.asarray(cluster_area) |
|
n_cluster = np.sort(cluster_n_triangles.copy())[-cluster_to_keep] |
|
n_cluster = max(n_cluster, 50) |
|
triangles_to_remove = cluster_n_triangles[triangle_clusters] < n_cluster |
|
mesh_0.remove_triangles_by_mask(triangles_to_remove) |
|
mesh_0.remove_unreferenced_vertices() |
|
mesh_0.remove_degenerate_triangles() |
|
print("num vertices raw {}".format(len(mesh.vertices))) |
|
print("num vertices post {}".format(len(mesh_0.vertices))) |
|
return mesh_0 |