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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -68
app.py CHANGED
@@ -1,110 +1,110 @@
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()
 
1
  import tempfile
2
  import os
3
 
4
+ # --- DA3 SETTINGS ---
5
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
6
+ CHECKPOINT = "depth-anything/da3-small"
7
+
8
 
9
  processor = AutoImageProcessor.from_pretrained(CHECKPOINT)
10
  model = AutoModelForDepthEstimation.from_pretrained(CHECKPOINT).to(DEVICE)
11
 
12
+ def process_textured_mesh(input_image):
13
  if input_image is None:
14
  return None, None
15
 
16
+ # 1. GENERATE DEPTH
17
+
18
+
19
+
20
  inputs = processor(images=input_image, return_tensors="pt").to(DEVICE)
21
  with torch.no_grad():
22
  outputs = model(**inputs)
23
+
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
+ # 2. CREATE TEXTURED GRID
31
+ # We use a step of 2 to keep the mesh lightweight for the browser
32
  width, height = input_image.size
33
+ step = 2
34
+ x, y = np.meshgrid(np.arange(0, width, step), np.arange(0, height, step))
 
 
 
 
 
35
 
36
+ # Normalize Z (depth) and center X, Y in a unit-10 space
37
+ z = (depth[::step, ::step] / (depth.max() + 1e-5)) * 3.0
38
+ x_centered = ((x / width) - 0.5) * 10.0 * (width / height)
39
+ y_centered = (0.5 - (y / height)) * 10.0
 
 
 
 
 
40
 
 
 
 
41
 
42
 
 
 
 
 
 
 
 
 
43
 
44
+ points = np.stack((x_centered, y_centered, z), axis=-1)
45
+ rows, cols, _ = points.shape
46
 
47
 
 
 
48
 
49
 
50
 
51
+ # 3. VERTICES & UV MAPPING
52
+ vertices = points.reshape(-1, 3)
53
+ # UVs map the image (0-1 range) to the vertices
54
+ uvs = np.stack((x / width, 1.0 - (y / height)), axis=-1).reshape(-1, 2)
55
+
56
+ # Build Triangles
57
+ faces = []
58
+ for i in range(rows - 1):
59
+ for j in range(cols - 1):
60
+ v0 = i * cols + j
61
+ v1 = v0 + 1
62
+ v2 = (i + 1) * cols + j
63
+ v3 = v2 + 1
64
+ faces.append([v0, v2, v1])
65
+ faces.append([v1, v2, v3])
66
+
67
+ # 4. CONSTRUCT MESH
68
+ mesh = o3d.geometry.TriangleMesh()
69
+ mesh.vertices = o3d.utility.Vector3dVector(vertices)
70
+ mesh.triangles = o3d.utility.Vector3iVector(np.array(faces))
71
+
72
+ # Assign UVs (Open3D expects UVs per triangle vertex, so we tile them)
73
+ mesh.triangle_uvs = o3d.utility.Vector2dVector(np.tile(uvs, (3, 1)))
74
 
75
+ # 5. EXPORT
 
 
76
  temp_dir = tempfile.gettempdir()
77
+ mesh_path = os.path.join(temp_dir, "model.obj")
78
+ texture_path = os.path.join(temp_dir, "texture.png")
 
 
 
 
 
 
79
 
80
+ # Save image as texture
81
+ input_image.save(texture_path)
82
+
83
+ # Save OBJ
84
+ o3d.io.write_triangle_mesh(mesh_path, mesh)
85
+
86
+ # To see textures in some viewers, we return the OBJ.
87
+ # In Blender, you'll simply load this texture.png onto the model.
88
+ return mesh_path, mesh_path
89
 
90
+ # --- UI ---
91
+ with gr.Blocks() as demo:
92
+ gr.Markdown("# 🎭 DA3 Textured 3D Mesh")
93
 
 
 
 
 
94
 
95
  with gr.Row():
96
  with gr.Column():
97
+ img_in = gr.Image(type="pil", label="Input")
98
+ btn = gr.Button("🔨 Generate Mesh", variant="primary")
99
+
100
  with gr.Column():
101
+ # Gradio 5.0+ focuses on the center (0,0,0) automatically
102
+ v3d = gr.Model3D(label="3D Preview", camera_position=(0, 90, 15))
103
+ dl = gr.DownloadButton("💾 Download OBJ + PNG")
104
+
105
+
106
+
107
+
108
+ btn.click(fn=process_textured_mesh, inputs=[img_in], outputs=[v3d, dl])
109
 
110
  demo.launch()