Spaces:
Running on Zero
Running on Zero
Rawal Khirodkar commited on
Commit ·
056d8b0
1
Parent(s): 0b515f7
Pointmap mesh polish: UV-textured PBR (image as albedo) + vertex normals + tighter max_edge=0.02m
Browse files
app.py
CHANGED
|
@@ -211,68 +211,74 @@ def _floor_ring(radius: float = 1.5, n_points: int = 360, y: float = 0.0,
|
|
| 211 |
return verts, cols
|
| 212 |
|
| 213 |
|
| 214 |
-
def _triangulate_grid(pointmap_hwc: np.ndarray,
|
| 215 |
-
|
| 216 |
"""Build a triangulated mesh from the (H, W) pointmap grid.
|
| 217 |
|
| 218 |
-
Each valid pixel becomes a vertex; adjacent
|
| 219 |
-
(
|
| 220 |
-
|
|
|
|
| 221 |
"""
|
| 222 |
H, W = pointmap_hwc.shape[:2]
|
| 223 |
z = pointmap_hwc[:, :, 2]
|
| 224 |
valid = mask_hw & np.isfinite(pointmap_hwc).all(axis=2) & (z > 0.05) & (z < 25.0)
|
| 225 |
|
| 226 |
-
# Vertex index per pixel; -1 if invalid.
|
| 227 |
idx_map = np.full((H, W), -1, dtype=np.int64)
|
| 228 |
yy, xx = np.where(valid)
|
| 229 |
idx_map[yy, xx] = np.arange(len(yy))
|
| 230 |
|
| 231 |
-
verts = pointmap_hwc[yy, xx]
|
| 232 |
-
|
|
|
|
| 233 |
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
b = idx_map[:-1, 1:] # top-right
|
| 237 |
-
c = idx_map[1:, :-1] # bottom-left
|
| 238 |
-
d = idx_map[1:, 1:] # bottom-right
|
| 239 |
quad_valid = (a != -1) & (b != -1) & (c != -1) & (d != -1)
|
| 240 |
-
|
| 241 |
a_v, b_v, c_v, d_v = a[quad_valid], b[quad_valid], c[quad_valid], d[quad_valid]
|
| 242 |
-
tri1 = np.stack([a_v, c_v, b_v], axis=1)
|
| 243 |
-
tri2 = np.stack([b_v, c_v, d_v], axis=1)
|
| 244 |
-
faces = np.concatenate([tri1, tri2], axis=0)
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
p0 = verts[faces[:, 0]]
|
| 248 |
-
p1 = verts[faces[:, 1]]
|
| 249 |
-
p2 = verts[faces[:, 2]]
|
| 250 |
e01 = np.linalg.norm(p1 - p0, axis=1)
|
| 251 |
e12 = np.linalg.norm(p2 - p1, axis=1)
|
| 252 |
e20 = np.linalg.norm(p0 - p2, axis=1)
|
| 253 |
keep = (e01 < max_edge) & (e12 < max_edge) & (e20 < max_edge)
|
| 254 |
-
faces = faces[keep]
|
| 255 |
-
|
| 256 |
-
return verts.astype(np.float32), cols.astype(np.uint8), faces.astype(np.int64)
|
| 257 |
|
| 258 |
|
| 259 |
def _make_glb(image_pil_native: Image.Image, pointmap_hwc: np.ndarray,
|
| 260 |
-
mask_hw: np.ndarray) -> str:
|
| 261 |
h, w = pointmap_hwc.shape[:2]
|
| 262 |
-
|
| 263 |
|
| 264 |
-
verts,
|
| 265 |
|
| 266 |
# Y-up flip so the viewer's default orientation matches photographic intuition.
|
| 267 |
flip = np.array([1.0, -1.0, -1.0], dtype=np.float32)
|
| 268 |
verts = verts * flip
|
| 269 |
|
| 270 |
-
|
| 271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
)
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
# Scene aids as
|
| 276 |
aids_v, aids_c = [], []
|
| 277 |
for fn in (_camera_marker, _xyz_axes, _floor_ring):
|
| 278 |
v, c = fn()
|
|
|
|
| 211 |
return verts, cols
|
| 212 |
|
| 213 |
|
| 214 |
+
def _triangulate_grid(pointmap_hwc: np.ndarray, mask_hw: np.ndarray,
|
| 215 |
+
max_edge: float = 0.02):
|
| 216 |
"""Build a triangulated mesh from the (H, W) pointmap grid.
|
| 217 |
|
| 218 |
+
Returns (verts, uvs, faces). Each valid pixel becomes a vertex; adjacent
|
| 219 |
+
valid pixels form quads (2 tris). Triangles with any edge longer than
|
| 220 |
+
`max_edge` (meters) are dropped to kill stretched skin at depth jumps.
|
| 221 |
+
UVs are pixel-aligned for direct texturing with the input image.
|
| 222 |
"""
|
| 223 |
H, W = pointmap_hwc.shape[:2]
|
| 224 |
z = pointmap_hwc[:, :, 2]
|
| 225 |
valid = mask_hw & np.isfinite(pointmap_hwc).all(axis=2) & (z > 0.05) & (z < 25.0)
|
| 226 |
|
|
|
|
| 227 |
idx_map = np.full((H, W), -1, dtype=np.int64)
|
| 228 |
yy, xx = np.where(valid)
|
| 229 |
idx_map[yy, xx] = np.arange(len(yy))
|
| 230 |
|
| 231 |
+
verts = pointmap_hwc[yy, xx].astype(np.float32) # (N, 3)
|
| 232 |
+
uvs = np.stack([xx / max(W - 1, 1), yy / max(H - 1, 1)],
|
| 233 |
+
axis=1).astype(np.float32) # (N, 2)
|
| 234 |
|
| 235 |
+
a = idx_map[:-1, :-1]; b = idx_map[:-1, 1:]
|
| 236 |
+
c = idx_map[1:, :-1]; d = idx_map[1:, 1:]
|
|
|
|
|
|
|
|
|
|
| 237 |
quad_valid = (a != -1) & (b != -1) & (c != -1) & (d != -1)
|
|
|
|
| 238 |
a_v, b_v, c_v, d_v = a[quad_valid], b[quad_valid], c[quad_valid], d[quad_valid]
|
| 239 |
+
tri1 = np.stack([a_v, c_v, b_v], axis=1)
|
| 240 |
+
tri2 = np.stack([b_v, c_v, d_v], axis=1)
|
| 241 |
+
faces = np.concatenate([tri1, tri2], axis=0)
|
| 242 |
+
|
| 243 |
+
p0 = verts[faces[:, 0]]; p1 = verts[faces[:, 1]]; p2 = verts[faces[:, 2]]
|
|
|
|
|
|
|
|
|
|
| 244 |
e01 = np.linalg.norm(p1 - p0, axis=1)
|
| 245 |
e12 = np.linalg.norm(p2 - p1, axis=1)
|
| 246 |
e20 = np.linalg.norm(p0 - p2, axis=1)
|
| 247 |
keep = (e01 < max_edge) & (e12 < max_edge) & (e20 < max_edge)
|
| 248 |
+
faces = faces[keep].astype(np.int64)
|
| 249 |
+
return verts, uvs, faces
|
|
|
|
| 250 |
|
| 251 |
|
| 252 |
def _make_glb(image_pil_native: Image.Image, pointmap_hwc: np.ndarray,
|
| 253 |
+
mask_hw: np.ndarray, max_edge: float = 0.02) -> str:
|
| 254 |
h, w = pointmap_hwc.shape[:2]
|
| 255 |
+
image_native = image_pil_native.resize((w, h), Image.LANCZOS)
|
| 256 |
|
| 257 |
+
verts, uvs, faces = _triangulate_grid(pointmap_hwc, mask_hw, max_edge=max_edge)
|
| 258 |
|
| 259 |
# Y-up flip so the viewer's default orientation matches photographic intuition.
|
| 260 |
flip = np.array([1.0, -1.0, -1.0], dtype=np.float32)
|
| 261 |
verts = verts * flip
|
| 262 |
|
| 263 |
+
# MoGe-style: V flipped (image origin is top-left, GL UVs origin bottom-left).
|
| 264 |
+
uvs = uvs * np.array([1.0, -1.0], dtype=np.float32) + np.array([0.0, 1.0], dtype=np.float32)
|
| 265 |
+
|
| 266 |
+
# PBR material with the input image as the albedo texture — much sharper than
|
| 267 |
+
# vertex colors because each triangle interior is sampled from the image at
|
| 268 |
+
# the correct pixel, not bilinearly between 3 corner colors.
|
| 269 |
+
material = trimesh.visual.material.PBRMaterial(
|
| 270 |
+
baseColorTexture=image_native,
|
| 271 |
+
metallicFactor=0.0,
|
| 272 |
+
roughnessFactor=1.0,
|
| 273 |
+
doubleSided=True,
|
| 274 |
)
|
| 275 |
+
visual = trimesh.visual.texture.TextureVisuals(uv=uvs, material=material)
|
| 276 |
+
|
| 277 |
+
mesh = trimesh.Trimesh(vertices=verts, faces=faces, visual=visual, process=False)
|
| 278 |
+
# Compute and attach per-vertex normals → enables shading in Three.js viewer.
|
| 279 |
+
_ = mesh.vertex_normals # triggers lazy compute; trimesh exports them in glb
|
| 280 |
|
| 281 |
+
# Scene aids (camera marker, XYZ axes, floor ring) as a single point primitive.
|
| 282 |
aids_v, aids_c = [], []
|
| 283 |
for fn in (_camera_marker, _xyz_axes, _floor_ring):
|
| 284 |
v, c = fn()
|