Spaces:
mashroo
/
Running on Zero

YoussefAnso commited on
Commit
c6a8a22
·
1 Parent(s): 1fa688f

Add vertex color to UV-textured GLB conversion in inference.py

Browse files

Implemented a new function to convert .obj files with vertex colors to UV-mapped textured .glb files. This includes UV parameterization using xatlas, texture baking with inpainting and filtering, and assigning the generated texture to the mesh. Updated the generate3d function to utilize this new conversion method, enhancing texture quality in mesh exports.

Files changed (1) hide show
  1. inference.py +84 -3
inference.py CHANGED
@@ -8,6 +8,88 @@ from mesh import Mesh
8
  import zipfile
9
  from util.renderer import Renderer
10
  import trimesh
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  def generate3d(model, rgb, ccm, device):
13
 
@@ -82,9 +164,8 @@ def generate3d(model, rgb, ccm, device):
82
  elapsed_time = end_time - start_time
83
  print(f"uv takes {elapsed_time}s")
84
 
85
- # Convert .obj (with vertex colors) to .glb
86
  obj_path = mesh_path_glb + ".obj"
87
  glb_path = mesh_path_glb + ".glb"
88
- mesh = trimesh.load(obj_path, process=False)
89
- mesh.export(glb_path)
90
  return glb_path
 
8
  import zipfile
9
  from util.renderer import Renderer
10
  import trimesh
11
+ import xatlas
12
+ import cv2
13
+ from PIL import Image, ImageFilter
14
+
15
+ def vertex_color_to_uv_textured_glb(obj_path, glb_path, texture_size=1024):
16
+ mesh = trimesh.load(obj_path, process=False)
17
+ vertex_colors = mesh.visual.vertex_colors[:, :3] # (N, 3), uint8
18
+ # Generate UVs
19
+ vmapping, indices, uvs = xatlas.parametrize(mesh.vertices, mesh.faces)
20
+ vertices = mesh.vertices[vmapping]
21
+ vertex_colors = vertex_colors[vmapping]
22
+ mesh.vertices = vertices
23
+ mesh.faces = indices
24
+ # Bake texture
25
+ buffer_size = texture_size * 2
26
+ texture_buffer = np.zeros((buffer_size, buffer_size, 4), dtype=np.uint8)
27
+ def barycentric_interpolate(v0, v1, v2, c0, c1, c2, p):
28
+ v0v1 = v1 - v0
29
+ v0v2 = v2 - v0
30
+ v0p = p - v0
31
+ d00 = np.dot(v0v1, v0v1)
32
+ d01 = np.dot(v0v1, v0v2)
33
+ d11 = np.dot(v0v2, v0v2)
34
+ d20 = np.dot(v0p, v0v1)
35
+ d21 = np.dot(v0p, v0v2)
36
+ denom = d00 * d11 - d01 * d01
37
+ if abs(denom) < 1e-8:
38
+ return (c0 + c1 + c2) / 3
39
+ v = (d11 * d20 - d01 * d21) / denom
40
+ w = (d00 * d21 - d01 * d20) / denom
41
+ u = 1.0 - v - w
42
+ u = np.clip(u, 0, 1)
43
+ v = np.clip(v, 0, 1)
44
+ w = np.clip(w, 0, 1)
45
+ return u * c0 + v * c1 + w * c2
46
+ def is_point_in_triangle(p, v0, v1, v2):
47
+ def sign(p1, p2, p3):
48
+ return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])
49
+ d1 = sign(p, v0, v1)
50
+ d2 = sign(p, v1, v2)
51
+ d3 = sign(p, v2, v0)
52
+ has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
53
+ has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
54
+ return not (has_neg and has_pos)
55
+ for face in mesh.faces:
56
+ uv0, uv1, uv2 = uvs[face]
57
+ c0, c1, c2 = vertex_colors[face]
58
+ uv0 = (uv0 * (buffer_size - 1)).astype(int)
59
+ uv1 = (uv1 * (buffer_size - 1)).astype(int)
60
+ uv2 = (uv2 * (buffer_size - 1)).astype(int)
61
+ min_x = max(int(np.floor(min(uv0[0], uv1[0], uv2[0]))), 0)
62
+ max_x = min(int(np.ceil(max(uv0[0], uv1[0], uv2[0]))), buffer_size - 1)
63
+ min_y = max(int(np.floor(min(uv0[1], uv1[1], uv2[1]))), 0)
64
+ max_y = min(int(np.ceil(max(uv0[1], uv1[1], uv2[1]))), buffer_size - 1)
65
+ for y in range(min_y, max_y + 1):
66
+ for x in range(min_x, max_x + 1):
67
+ p = np.array([x + 0.5, y + 0.5])
68
+ if is_point_in_triangle(p, uv0, uv1, uv2):
69
+ color = barycentric_interpolate(uv0, uv1, uv2, c0, c1, c2, p)
70
+ texture_buffer[y, x, :3] = np.clip(color, 0, 255).astype(np.uint8)
71
+ texture_buffer[y, x, 3] = 255
72
+ # Inpainting, filtering, and downsampling
73
+ image_bgra = texture_buffer.copy()
74
+ mask = (image_bgra[:, :, 3] == 0).astype(np.uint8) * 255
75
+ image_bgr = cv2.cvtColor(image_bgra, cv2.COLOR_BGRA2BGR)
76
+ inpainted_bgr = cv2.inpaint(image_bgr, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)
77
+ inpainted_bgra = cv2.cvtColor(inpainted_bgr, cv2.COLOR_BGR2BGRA)
78
+ texture_buffer = inpainted_bgra[::-1]
79
+ image_texture = Image.fromarray(texture_buffer)
80
+ image_texture = image_texture.filter(ImageFilter.MedianFilter(size=3))
81
+ image_texture = image_texture.filter(ImageFilter.GaussianBlur(radius=1))
82
+ image_texture = image_texture.resize((texture_size, texture_size), Image.LANCZOS)
83
+ # Assign UVs and texture to mesh
84
+ material = trimesh.visual.material.PBRMaterial(
85
+ baseColorFactor=[1.0, 1.0, 1.0, 1.0],
86
+ baseColorTexture=image_texture,
87
+ metallicFactor=0.0,
88
+ roughnessFactor=1.0,
89
+ )
90
+ visuals = trimesh.visual.TextureVisuals(uv=uvs, material=material)
91
+ mesh.visual = visuals
92
+ mesh.export(glb_path)
93
 
94
  def generate3d(model, rgb, ccm, device):
95
 
 
164
  elapsed_time = end_time - start_time
165
  print(f"uv takes {elapsed_time}s")
166
 
167
+ # Convert .obj (with vertex colors) to UV-mapped textured .glb
168
  obj_path = mesh_path_glb + ".obj"
169
  glb_path = mesh_path_glb + ".glb"
170
+ vertex_color_to_uv_textured_glb(obj_path, glb_path)
 
171
  return glb_path