mmmno commited on
Commit
5be4ca8
·
verified ·
1 Parent(s): c2dff49

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -63
app.py CHANGED
@@ -1,100 +1,110 @@
1
- import gradio as gr
2
- import torch
3
- import numpy as np
4
- import open3d as o3d
5
- from PIL import Image
6
- from transformers import AutoImageProcessor, AutoModelForDepthEstimation
7
  import tempfile
8
  import os
9
 
10
- # --- DA3 SETTINGS ---
11
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
12
- CHECKPOINT = "depth-anything/da3-small"
 
13
 
14
  processor = AutoImageProcessor.from_pretrained(CHECKPOINT)
15
  model = AutoModelForDepthEstimation.from_pretrained(CHECKPOINT).to(DEVICE)
16
 
17
- def process_textured_mesh(input_image):
18
  if input_image is None:
19
  return None, None
20
 
21
- # 1. GENERATE DEPTH
 
 
 
22
  inputs = processor(images=input_image, return_tensors="pt").to(DEVICE)
23
  with torch.no_grad():
24
  outputs = model(**inputs)
 
25
  depth = torch.nn.functional.interpolate(
26
  outputs.predicted_depth.unsqueeze(1),
27
  size=input_image.size[::-1],
28
  mode="bicubic",
29
  ).squeeze().cpu().numpy()
30
 
31
- # 2. CREATE TEXTURED GRID
32
- # We use a step of 2 to keep the mesh lightweight for the browser
33
  width, height = input_image.size
34
- step = 2
35
- x, y = np.meshgrid(np.arange(0, width, step), np.arange(0, height, step))
36
 
37
- # Normalize Z (depth) and center X, Y in a unit-10 space
38
- z = (depth[::step, ::step] / (depth.max() + 1e-5)) * 3.0
39
- x_centered = ((x / width) - 0.5) * 10.0 * (width / height)
40
- y_centered = (0.5 - (y / height)) * 10.0
41
 
 
 
 
42
  points = np.stack((x_centered, y_centered, z), axis=-1)
43
- rows, cols, _ = points.shape
44
-
45
- # 3. VERTICES & UV MAPPING
46
- vertices = points.reshape(-1, 3)
47
- # UVs map the image (0-1 range) to the vertices
48
- uvs = np.stack((x / width, 1.0 - (y / height)), axis=-1).reshape(-1, 2)
49
-
50
- # Build Triangles
51
- faces = []
52
- for i in range(rows - 1):
53
- for j in range(cols - 1):
54
- v0 = i * cols + j
55
- v1 = v0 + 1
56
- v2 = (i + 1) * cols + j
57
- v3 = v2 + 1
58
- faces.append([v0, v2, v1])
59
- faces.append([v1, v2, v3])
60
-
61
- # 4. CONSTRUCT MESH
62
- mesh = o3d.geometry.TriangleMesh()
63
- mesh.vertices = o3d.utility.Vector3dVector(vertices)
64
- mesh.triangles = o3d.utility.Vector3iVector(np.array(faces))
65
 
66
- # Assign UVs (Open3D expects UVs per triangle vertex, so we tile them)
67
- mesh.triangle_uvs = o3d.utility.Vector2dVector(np.tile(uvs, (3, 1)))
 
68
 
69
- # 5. EXPORT
70
- temp_dir = tempfile.gettempdir()
71
- mesh_path = os.path.join(temp_dir, "model.obj")
72
- texture_path = os.path.join(temp_dir, "texture.png")
73
 
74
- # Save image as texture
75
- input_image.save(texture_path)
 
 
 
 
 
 
 
 
 
76
 
77
- # Save OBJ
78
- o3d.io.write_triangle_mesh(mesh_path, mesh)
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- # To see textures in some viewers, we return the OBJ.
81
- # In Blender, you'll simply load this texture.png onto the model.
82
- return mesh_path, mesh_path
83
 
84
- # --- UI ---
85
- with gr.Blocks() as demo:
86
- gr.Markdown("# 🎭 DA3 Textured 3D Mesh")
 
 
 
87
 
88
  with gr.Row():
89
  with gr.Column():
90
- img_in = gr.Image(type="pil", label="Input")
91
- btn = gr.Button("🔨 Generate Mesh", variant="primary")
92
-
93
  with gr.Column():
94
- # Gradio 5.0+ focuses on the center (0,0,0) automatically
95
- v3d = gr.Model3D(label="3D Preview", camera_position=(0, 90, 15))
96
- dl = gr.DownloadButton("💾 Download OBJ + PNG")
 
 
 
97
 
98
- btn.click(fn=process_textured_mesh, inputs=[img_in], outputs=[v3d, dl])
99
 
100
  demo.launch()
 
 
 
 
 
 
 
1
  import tempfile
2
  import os
3
 
4
+ # --- 1. DA3 MODEL SETUP ---
5
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
6
+ # Depth Anything V3 Small - Higher precision for 2026 workflows
7
+ CHECKPOINT = "depth-anything/DA3-Small"
8
 
9
  processor = AutoImageProcessor.from_pretrained(CHECKPOINT)
10
  model = AutoModelForDepthEstimation.from_pretrained(CHECKPOINT).to(DEVICE)
11
 
12
+ def process_da3_to_mesh(input_image):
13
  if input_image is None:
14
  return None, None
15
 
16
+ # Resize for processing speed
17
+ input_image.thumbnail((1024, 1024))
18
+
19
+ # --- 2. V3 DEPTH INFERENCE ---
20
  inputs = processor(images=input_image, return_tensors="pt").to(DEVICE)
21
  with torch.no_grad():
22
  outputs = model(**inputs)
23
+ # DA3 provides much sharper depth maps
24
  depth = torch.nn.functional.interpolate(
25
  outputs.predicted_depth.unsqueeze(1),
26
  size=input_image.size[::-1],
27
  mode="bicubic",
28
  ).squeeze().cpu().numpy()
29
 
30
+
31
+
32
  width, height = input_image.size
33
+ rgb_colors = np.array(input_image).reshape(-1, 3) / 255.0
34
+
35
 
36
+ # --- 3. NORMALIZED 3D PROJECTION ---
37
+ x, y = np.meshgrid(np.arange(width), np.arange(height))
38
+ # DA3 depth is more linear; we scale it for a natural 3D look
39
+ z = (depth.flatten() / (depth.max() + 1e-5)) * 4.0
40
 
41
+ # Center everything in the 'Unit 10' viewing box
42
+ x_centered = ((x.flatten() / width) - 0.5) * 10.0 * (width / height)
43
+ y_centered = (0.5 - (y.flatten() / height)) * 10.0
44
  points = np.stack((x_centered, y_centered, z), axis=-1)
45
+
46
+ # --- 4. ADVANCED MESHING (POISSON RECONSTRUCTION) ---
47
+ pcd = o3d.geometry.PointCloud()
48
+ pcd.points = o3d.utility.Vector3dVector(points)
49
+ pcd.colors = o3d.utility.Vector3dVector(rgb_colors)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ # Estimate Normals - DA3 needs higher search radius for its high-detail output
52
+ pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.2, max_nn=50))
53
+ pcd.orient_normals_towards_camera_location(camera_location=np.array([0., 0., 15.]))
54
 
55
+
56
+ # Poisson Surface Reconstruction creates a watertight "solid" shell
57
+ # depth=8 or 9 is the sweet spot for detail vs speed
58
+ mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=9)
59
 
60
+ # Clean up the mesh (Poisson creates a 'bubble' we need to trim)
61
+ vertices_to_remove = densities < np.quantile(densities, 0.1)
62
+ mesh.remove_vertices_by_mask(vertices_to_remove)
63
+
64
+
65
+
66
+
67
+ # --- 5. FINALIZE & EXPORT ---
68
+ mesh.translate(-mesh.get_center()) # FORCE CENTER
69
+
70
+
71
 
72
+
73
+
74
+
75
+
76
+ temp_dir = tempfile.gettempdir()
77
+ output_path = os.path.join(temp_dir, "da3_mesh.ply")
78
+ # Binary PLY for Blender Color Compatibility
79
+ o3d.io.write_triangle_mesh(output_path, mesh, write_ascii=False)
80
+
81
+
82
+
83
+
84
+
85
 
86
+ return output_path, output_path
 
 
87
 
88
+
89
+
90
+ # --- 6. UI ---
91
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
92
+ gr.Markdown("# 🧊 Depth Anything V3: Mesh Engine")
93
+ gr.Markdown("Using the 2026 DA3 architecture for high-fidelity 3D reconstruction.")
94
 
95
  with gr.Row():
96
  with gr.Column():
97
+ img_in = gr.Image(type="pil", label="Source Image")
98
+ btn = gr.Button("🔨 Generate High-Res Mesh", variant="primary")
99
+
100
  with gr.Column():
101
+ v3d = gr.Model3D(
102
+ label="3D Mesh Preview",
103
+ display_mode="solid",
104
+ camera_position=(0, 90, 15)
105
+ )
106
+ dl = gr.DownloadButton("💾 Download Colored PLY")
107
 
108
+ btn.click(fn=process_da3_to_mesh, inputs=[img_in], outputs=[v3d, dl])
109
 
110
  demo.launch()