mmmno commited on
Commit
c2dff49
·
verified ·
1 Parent(s): 907bcd6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -51
app.py CHANGED
@@ -7,89 +7,94 @@ from transformers import AutoImageProcessor, AutoModelForDepthEstimation
7
  import tempfile
8
  import os
9
 
10
- # --- 1. DA3 MODEL SETUP ---
11
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
12
- # Depth Anything V3 Small - Higher precision for 2026 workflows
13
- CHECKPOINT = "depth-anything/DA3-Small"
14
 
15
  processor = AutoImageProcessor.from_pretrained(CHECKPOINT)
16
  model = AutoModelForDepthEstimation.from_pretrained(CHECKPOINT).to(DEVICE)
17
 
18
- def process_da3_to_mesh(input_image):
19
  if input_image is None:
20
  return None, None
21
 
22
- # Resize for processing speed
23
- input_image.thumbnail((1024, 1024))
24
-
25
- # --- 2. V3 DEPTH INFERENCE ---
26
  inputs = processor(images=input_image, return_tensors="pt").to(DEVICE)
27
  with torch.no_grad():
28
  outputs = model(**inputs)
29
- # DA3 provides much sharper depth maps
30
  depth = torch.nn.functional.interpolate(
31
  outputs.predicted_depth.unsqueeze(1),
32
  size=input_image.size[::-1],
33
  mode="bicubic",
34
  ).squeeze().cpu().numpy()
35
 
 
 
36
  width, height = input_image.size
37
- rgb_colors = np.array(input_image).reshape(-1, 3) / 255.0
 
38
 
39
- # --- 3. NORMALIZED 3D PROJECTION ---
40
- x, y = np.meshgrid(np.arange(width), np.arange(height))
41
- # DA3 depth is more linear; we scale it for a natural 3D look
42
- z = (depth.flatten() / (depth.max() + 1e-5)) * 4.0
43
 
44
- # Center everything in the 'Unit 10' viewing box
45
- x_centered = ((x.flatten() / width) - 0.5) * 10.0 * (width / height)
46
- y_centered = (0.5 - (y.flatten() / height)) * 10.0
47
  points = np.stack((x_centered, y_centered, z), axis=-1)
48
-
49
- # --- 4. ADVANCED MESHING (POISSON RECONSTRUCTION) ---
50
- pcd = o3d.geometry.PointCloud()
51
- pcd.points = o3d.utility.Vector3dVector(points)
52
- pcd.colors = o3d.utility.Vector3dVector(rgb_colors)
53
 
54
- # Estimate Normals - DA3 needs higher search radius for its high-detail output
55
- pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.2, max_nn=50))
56
- pcd.orient_normals_towards_camera_location(camera_location=np.array([0., 0., 15.]))
 
57
 
58
- # Poisson Surface Reconstruction creates a watertight "solid" shell
59
- # depth=8 or 9 is the sweet spot for detail vs speed
60
- mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=9)
61
-
62
- # Clean up the mesh (Poisson creates a 'bubble' we need to trim)
63
- vertices_to_remove = densities < np.quantile(densities, 0.1)
64
- mesh.remove_vertices_by_mask(vertices_to_remove)
 
 
 
65
 
66
- # --- 5. FINALIZE & EXPORT ---
67
- mesh.translate(-mesh.get_center()) # FORCE CENTER
 
 
68
 
 
 
 
 
69
  temp_dir = tempfile.gettempdir()
70
- output_path = os.path.join(temp_dir, "da3_mesh.ply")
71
- # Binary PLY for Blender Color Compatibility
72
- o3d.io.write_triangle_mesh(output_path, mesh, write_ascii=False)
 
 
 
 
 
73
 
74
- return output_path, output_path
 
 
75
 
76
- # --- 6. UI ---
77
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
78
- gr.Markdown("# 🧊 Depth Anything V3: Mesh Engine")
79
- gr.Markdown("Using the 2026 DA3 architecture for high-fidelity 3D reconstruction.")
80
 
81
  with gr.Row():
82
  with gr.Column():
83
- img_in = gr.Image(type="pil", label="Source Image")
84
- btn = gr.Button("🔨 Generate High-Res Mesh", variant="primary")
 
85
  with gr.Column():
86
- v3d = gr.Model3D(
87
- label="3D Mesh Preview",
88
- display_mode="solid",
89
- camera_position=(0, 90, 15)
90
- )
91
- dl = gr.DownloadButton("💾 Download Colored PLY")
92
 
93
- btn.click(fn=process_da3_to_mesh, inputs=[img_in], outputs=[v3d, dl])
94
 
95
  demo.launch()
 
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()