Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -1,51 +1,73 @@
|
|
1 |
import torch
|
2 |
import gradio as gr
|
3 |
-
from PIL import Image
|
4 |
import numpy as np
|
5 |
import open3d as o3d
|
|
|
6 |
import cv2
|
7 |
|
8 |
-
class
|
9 |
def __init__(self):
|
10 |
-
#
|
11 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
-
def estimate_depth(self,
|
14 |
"""
|
15 |
-
|
16 |
"""
|
17 |
-
# Convert PIL Image to
|
18 |
-
|
19 |
|
20 |
-
#
|
21 |
-
|
|
|
22 |
|
23 |
-
#
|
24 |
-
|
25 |
-
depth_map = cv2.distanceTransform(255 - edges, cv2.DIST_L2, 5)
|
26 |
|
27 |
-
#
|
28 |
-
|
|
|
|
|
29 |
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
def create_point_cloud(self, image, depth_map):
|
33 |
"""
|
34 |
-
|
35 |
"""
|
36 |
img_array = np.array(image)
|
37 |
height, width = img_array.shape[:2]
|
38 |
|
39 |
-
#
|
40 |
-
step =
|
41 |
points = []
|
42 |
colors = []
|
43 |
|
44 |
for y in range(0, height, step):
|
45 |
for x in range(0, width, step):
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
pcd = o3d.geometry.PointCloud()
|
51 |
pcd.points = o3d.utility.Vector3dVector(points)
|
@@ -55,42 +77,48 @@ class RobustImageTo3DConverter:
|
|
55 |
|
56 |
def convert_to_mesh(self, point_cloud):
|
57 |
"""
|
58 |
-
|
59 |
"""
|
|
|
60 |
point_cloud.estimate_normals()
|
61 |
point_cloud.orient_normals_consistent_tangent_plane(100)
|
62 |
|
63 |
-
#
|
64 |
mesh, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
|
65 |
-
point_cloud, depth=
|
66 |
)
|
67 |
|
68 |
-
#
|
69 |
-
mesh.
|
70 |
|
71 |
return mesh
|
72 |
|
73 |
def process_image(self, input_image):
|
74 |
"""
|
75 |
-
CPU-friendly full pipeline
|
76 |
"""
|
77 |
-
#
|
|
|
|
|
|
|
|
|
78 |
depth_map = self.estimate_depth(input_image)
|
79 |
|
80 |
# Create point cloud
|
81 |
-
point_cloud = self.create_point_cloud(input_image, depth_map)
|
82 |
|
83 |
# Convert to mesh
|
84 |
mesh = self.convert_to_mesh(point_cloud)
|
85 |
|
86 |
# Save mesh
|
87 |
-
output_path = "/tmp/
|
88 |
o3d.io.write_triangle_mesh(output_path, mesh)
|
89 |
|
90 |
return output_path
|
91 |
|
92 |
def create_huggingface_space():
|
93 |
-
|
|
|
94 |
|
95 |
def convert_image(input_image):
|
96 |
try:
|
@@ -98,10 +126,6 @@ def create_huggingface_space():
|
|
98 |
if not isinstance(input_image, Image.Image):
|
99 |
input_image = Image.fromarray(input_image)
|
100 |
|
101 |
-
# Resize image if too large
|
102 |
-
max_size = (800, 800)
|
103 |
-
input_image.thumbnail(max_size, Image.LANCZOS)
|
104 |
-
|
105 |
# Process image
|
106 |
output_model = converter.process_image(input_image)
|
107 |
return output_model
|
@@ -112,12 +136,9 @@ def create_huggingface_space():
|
|
112 |
iface = gr.Interface(
|
113 |
fn=convert_image,
|
114 |
inputs=gr.Image(type="pil", label="Input Image"),
|
115 |
-
outputs=
|
116 |
-
|
117 |
-
|
118 |
-
],
|
119 |
-
title="Robust Image to 3D Model Converter",
|
120 |
-
description="Convert images to 3D models using OpenCV depth estimation and point cloud reconstruction."
|
121 |
)
|
122 |
|
123 |
return iface
|
|
|
1 |
import torch
|
2 |
import gradio as gr
|
|
|
3 |
import numpy as np
|
4 |
import open3d as o3d
|
5 |
+
from PIL import Image
|
6 |
import cv2
|
7 |
|
8 |
+
class CPUFriendlyAIDepthTo3DConverter:
|
9 |
def __init__(self):
|
10 |
+
# Load MiDaS depth estimation model with explicit CPU configuration
|
11 |
+
self.model = torch.hub.load('intel-isl/MiDaS', 'MiDaS_small', force_reload=False)
|
12 |
+
self.model.to('cpu') # Ensure model runs on CPU
|
13 |
+
self.model.eval()
|
14 |
+
|
15 |
+
# Preprocessing transforms
|
16 |
+
self.transform = torch.hub.load('intel-isl/MiDaS', 'transforms').small_transform
|
17 |
|
18 |
+
def estimate_depth(self, input_image):
|
19 |
"""
|
20 |
+
CPU-optimized depth estimation
|
21 |
"""
|
22 |
+
# Convert PIL Image to numpy
|
23 |
+
img = np.array(input_image)
|
24 |
|
25 |
+
# Ensure image is in RGB
|
26 |
+
if img.shape[-1] == 4: # If RGBA, convert to RGB
|
27 |
+
img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
|
28 |
|
29 |
+
# Preprocess image
|
30 |
+
input_batch = self.transform(img).unsqueeze(0).to('cpu')
|
|
|
31 |
|
32 |
+
# Estimate depth with minimal memory usage
|
33 |
+
with torch.no_grad():
|
34 |
+
prediction = self.model(input_batch)
|
35 |
+
depth = prediction.squeeze().cpu().numpy()
|
36 |
|
37 |
+
# Free up memory
|
38 |
+
torch.cuda.empty_cache()
|
39 |
+
|
40 |
+
# Normalize depth
|
41 |
+
depth_normalized = cv2.normalize(depth, None, 0, 255,
|
42 |
+
norm_type=cv2.NORM_MINMAX,
|
43 |
+
dtype=cv2.CV_8U)
|
44 |
+
|
45 |
+
return depth_normalized
|
46 |
|
47 |
def create_point_cloud(self, image, depth_map):
|
48 |
"""
|
49 |
+
Efficient point cloud creation
|
50 |
"""
|
51 |
img_array = np.array(image)
|
52 |
height, width = img_array.shape[:2]
|
53 |
|
54 |
+
# More aggressive downsampling for memory efficiency
|
55 |
+
step = 4
|
56 |
points = []
|
57 |
colors = []
|
58 |
|
59 |
for y in range(0, height, step):
|
60 |
for x in range(0, width, step):
|
61 |
+
# Use depth as Z coordinate
|
62 |
+
z = depth_map[y, x] / 255.0 * 5 # Scaled depth
|
63 |
+
points.append([x, y, z])
|
64 |
+
|
65 |
+
# Safely get color
|
66 |
+
try:
|
67 |
+
color = img_array[y, x] / 255.0
|
68 |
+
colors.append(color)
|
69 |
+
except IndexError:
|
70 |
+
colors.append([0.5, 0.5, 0.5]) # Default color if out of bounds
|
71 |
|
72 |
pcd = o3d.geometry.PointCloud()
|
73 |
pcd.points = o3d.utility.Vector3dVector(points)
|
|
|
77 |
|
78 |
def convert_to_mesh(self, point_cloud):
|
79 |
"""
|
80 |
+
Memory-efficient mesh conversion
|
81 |
"""
|
82 |
+
# Estimate and orient normals
|
83 |
point_cloud.estimate_normals()
|
84 |
point_cloud.orient_normals_consistent_tangent_plane(100)
|
85 |
|
86 |
+
# Lower depth for less memory consumption
|
87 |
mesh, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
|
88 |
+
point_cloud, depth=7 # Reduced from previous version
|
89 |
)
|
90 |
|
91 |
+
# Simplified color handling
|
92 |
+
mesh.vertex_colors = point_cloud.colors
|
93 |
|
94 |
return mesh
|
95 |
|
96 |
def process_image(self, input_image):
|
97 |
"""
|
98 |
+
CPU-friendly full pipeline
|
99 |
"""
|
100 |
+
# Resize image to reduce memory usage
|
101 |
+
max_size = (800, 800)
|
102 |
+
input_image.thumbnail(max_size, Image.LANCZOS)
|
103 |
+
|
104 |
+
# Estimate depth using AI model
|
105 |
depth_map = self.estimate_depth(input_image)
|
106 |
|
107 |
# Create point cloud
|
108 |
+
point_cloud = self.create_point_cloud(np.array(input_image), depth_map)
|
109 |
|
110 |
# Convert to mesh
|
111 |
mesh = self.convert_to_mesh(point_cloud)
|
112 |
|
113 |
# Save mesh
|
114 |
+
output_path = "/tmp/cpu_optimized_3d_model.obj"
|
115 |
o3d.io.write_triangle_mesh(output_path, mesh)
|
116 |
|
117 |
return output_path
|
118 |
|
119 |
def create_huggingface_space():
|
120 |
+
# Initialize converter
|
121 |
+
converter = CPUFriendlyAIDepthTo3DConverter()
|
122 |
|
123 |
def convert_image(input_image):
|
124 |
try:
|
|
|
126 |
if not isinstance(input_image, Image.Image):
|
127 |
input_image = Image.fromarray(input_image)
|
128 |
|
|
|
|
|
|
|
|
|
129 |
# Process image
|
130 |
output_model = converter.process_image(input_image)
|
131 |
return output_model
|
|
|
136 |
iface = gr.Interface(
|
137 |
fn=convert_image,
|
138 |
inputs=gr.Image(type="pil", label="Input Image"),
|
139 |
+
outputs=gr.File(label="3D Model (OBJ)"),
|
140 |
+
title="CPU-Friendly AI Image to 3D Converter",
|
141 |
+
description="Convert images to 3D models using lightweight AI depth estimation."
|
|
|
|
|
|
|
142 |
)
|
143 |
|
144 |
return iface
|