Commit
·
c6a8a22
1
Parent(s):
1fa688f
Add vertex color to UV-textured GLB conversion in inference.py
Browse filesImplemented 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.
- 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 |
-
|
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
|