codic commited on
Commit
de7156a
·
verified ·
1 Parent(s): 8cd6e80

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -47
app.py CHANGED
@@ -6,39 +6,49 @@ from PIL import Image
6
  import open3d as o3d
7
  from pathlib import Path
8
  import os
 
 
9
 
 
10
  feature_extractor = DPTFeatureExtractor.from_pretrained("Intel/dpt-large")
11
  model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large")
 
12
  def process_image(image_path, depth_map_path=None):
13
  image_path = Path(image_path)
14
- image_raw = Image.open(image_path)
15
  image = image_raw.resize(
16
  (1600, int(1600 * image_raw.size[1] / image_raw.size[0])),
17
  Image.Resampling.LANCZOS,
18
  )
19
 
20
- if depth_map_path:
21
- # Load and resize the user-provided depth map to match the RGB image size
22
- depth_image_raw = Image.open(depth_map_path)
23
- depth_image = depth_image_raw.resize(image.size, Image.Resampling.NEAREST)
24
- depth_image = np.array(depth_image)
25
-
26
- # Adjust the depth map based on the Depth-Anything-V2 format
27
- # Normalize depth if needed (assuming values are not scaled to 0-255)
28
- depth_image = (depth_image - np.min(depth_image)) / (np.max(depth_image) - np.min(depth_image))
29
- depth_image = (depth_image * 255).astype('uint8')
30
 
31
- # Optionally invert the depth map if it's inverse (like MiDaS)
32
- # depth_image = np.max(depth_image) - depth_image
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  else:
35
- # Automatically generate the depth map using the model (if no depth map provided)
36
- encoding = feature_extractor(image, return_tensors="pt")
37
  with torch.no_grad():
38
  outputs = model(**encoding)
39
  predicted_depth = outputs.predicted_depth
40
 
41
- # Interpolate to original size
42
  prediction = torch.nn.functional.interpolate(
43
  predicted_depth.unsqueeze(1),
44
  size=image.size[::-1],
@@ -47,13 +57,17 @@ def process_image(image_path, depth_map_path=None):
47
  ).squeeze()
48
  depth_image = (prediction.cpu().numpy() * 255 / np.max(prediction.cpu().numpy())).astype("uint8")
49
 
 
 
 
50
  try:
51
- gltf_path = create_3d_obj(np.array(image), depth_image, image_path)
52
- img = Image.fromarray(depth_image)
53
  return [img, gltf_path, gltf_path]
54
  except Exception as e:
55
- gltf_path = create_3d_obj(np.array(image), depth_image, image_path, depth=8)
56
- img = Image.fromarray(depth_image)
 
57
  return [img, gltf_path, gltf_path]
58
  except:
59
  print("Error reconstructing 3D model")
@@ -65,60 +79,51 @@ def create_3d_obj(rgb_image, depth_image, image_path, depth=12):
65
  rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(
66
  image_o3d, depth_o3d, convert_rgb_to_intensity=False
67
  )
68
- w = int(depth_image.shape[1])
69
- h = int(depth_image.shape[0])
70
 
 
71
  camera_intrinsic = o3d.camera.PinholeCameraIntrinsic()
72
  camera_intrinsic.set_intrinsics(w, h, 1000, 1000, w / 2, h / 2)
73
 
 
74
  pcd = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, camera_intrinsic)
75
-
76
- print("normals")
77
- pcd.normals = o3d.utility.Vector3dVector(
78
- np.zeros((1, 3))
79
- ) # invalidate existing normals
80
- pcd.estimate_normals(
81
- search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.05, max_nn=30)
82
- )
83
- pcd.orient_normals_towards_camera_location(
84
- camera_location=np.array([0.0, 0.0, 1000.0])
85
- )
86
  pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
87
  pcd.transform([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
88
 
89
- print("run Poisson surface reconstruction")
90
- with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm:
91
- mesh_raw, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
92
- pcd, depth=depth, width=0, scale=1.1, linear_fit=True
93
- )
94
 
 
95
  voxel_size = max(mesh_raw.get_max_bound() - mesh_raw.get_min_bound()) / 512
96
- print(f"voxel_size = {voxel_size:e}")
97
  mesh = mesh_raw.simplify_vertex_clustering(
98
  voxel_size=voxel_size,
99
  contraction=o3d.geometry.SimplificationContraction.Average,
100
  )
101
-
102
- # Apply smoothing
103
  mesh = mesh.filter_smooth_simple(number_of_iterations=3)
104
-
105
  bbox = pcd.get_axis_aligned_bounding_box()
106
  mesh_crop = mesh.crop(bbox)
 
 
107
  gltf_path = f"./{image_path.stem}.gltf"
108
  o3d.io.write_triangle_mesh(gltf_path, mesh_crop, write_triangle_uvs=True)
109
  return gltf_path
110
 
 
 
 
111
 
112
-
113
- title = "Demo: Depth Estimation & 3D Reconstruction with DPT + Point Cloud"
114
- description = "This demo allows users to provide an RGB image and optionally a depth map to create a 3D object. If no depth map is provided, the DPT model will generate it."
115
  examples = [["examples/" + img] for img in os.listdir("examples/")]
116
 
117
  iface = gr.Interface(
118
  fn=process_image,
119
  inputs=[
120
  gr.Image(type="filepath", label="Input Image"),
121
- gr.Image(type="filepath", label="Input Depth Map (optional)"),
122
  ],
123
  outputs=[
124
  gr.Image(label="Predicted Depth", type="pil"),
@@ -131,4 +136,4 @@ iface = gr.Interface(
131
  allow_flagging="never",
132
  cache_examples=False,
133
  )
134
- iface.launch(debug=True, show_api=True, share=True)
 
6
  import open3d as o3d
7
  from pathlib import Path
8
  import os
9
+ import cv2
10
+ from rembg import remove # Import the rembg library for background removal
11
 
12
+ # Initialize model and feature extractor for depth estimation
13
  feature_extractor = DPTFeatureExtractor.from_pretrained("Intel/dpt-large")
14
  model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large")
15
+
16
  def process_image(image_path, depth_map_path=None):
17
  image_path = Path(image_path)
18
+ image_raw = Image.open(image_path).convert("RGB") # Ensure the image is in RGB format
19
  image = image_raw.resize(
20
  (1600, int(1600 * image_raw.size[1] / image_raw.size[0])),
21
  Image.Resampling.LANCZOS,
22
  )
23
 
24
+ # Remove background using rembg
25
+ foreground = remove(image_raw) # Remove background
26
+ foreground = Image.fromarray(np.array(foreground)) # Convert back to PIL Image
27
+ foreground = foreground.convert("RGB") # Ensure the foreground is in RGB format
 
 
 
 
 
 
28
 
29
+ # Check if user-provided depth map is available
30
+ if depth_map_path:
31
+ depth_map_path = Path(depth_map_path)
32
+ if depth_map_path.suffix == '.npy':
33
+ # Load depth map from .npy file
34
+ depth_image = np.load(depth_map_path)
35
+ else:
36
+ # Load depth map from image file
37
+ depth_image_raw = Image.open(depth_map_path).convert("L") # Convert to grayscale
38
+ depth_image = depth_image_raw.resize(image.size, Image.Resampling.NEAREST)
39
+ depth_image = np.array(depth_image)
40
+
41
+ # Normalize depth image to match expected format
42
+ depth_image = (depth_image - np.min(depth_image)) / (np.max(depth_image) - np.min(depth_image))
43
+ depth_image = (depth_image * 255).astype('uint8')
44
 
45
  else:
46
+ # Generate depth map using DPT model
47
+ encoding = feature_extractor(foreground, return_tensors="pt")
48
  with torch.no_grad():
49
  outputs = model(**encoding)
50
  predicted_depth = outputs.predicted_depth
51
 
 
52
  prediction = torch.nn.functional.interpolate(
53
  predicted_depth.unsqueeze(1),
54
  size=image.size[::-1],
 
57
  ).squeeze()
58
  depth_image = (prediction.cpu().numpy() * 255 / np.max(prediction.cpu().numpy())).astype("uint8")
59
 
60
+ # Step 1: Apply Gaussian smoothing on the depth map
61
+ smoothed_depth_map = cv2.GaussianBlur(depth_image, (5, 5), 0)
62
+
63
  try:
64
+ gltf_path = create_3d_obj(np.array(image), smoothed_depth_map, image_path)
65
+ img = Image.fromarray(smoothed_depth_map)
66
  return [img, gltf_path, gltf_path]
67
  except Exception as e:
68
+ print("Error with default depth. Retrying with a shallower depth.")
69
+ gltf_path = create_3d_obj(np.array(image), smoothed_depth_map, image_path, depth=8)
70
+ img = Image.fromarray(smoothed_depth_map)
71
  return [img, gltf_path, gltf_path]
72
  except:
73
  print("Error reconstructing 3D model")
 
79
  rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(
80
  image_o3d, depth_o3d, convert_rgb_to_intensity=False
81
  )
82
+ w, h = depth_image.shape[1], depth_image.shape[0]
 
83
 
84
+ # Camera intrinsic setup for 3D point cloud
85
  camera_intrinsic = o3d.camera.PinholeCameraIntrinsic()
86
  camera_intrinsic.set_intrinsics(w, h, 1000, 1000, w / 2, h / 2)
87
 
88
+ # Point Cloud and Normals
89
  pcd = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, camera_intrinsic)
90
+ pcd.normals = o3d.utility.Vector3dVector(np.zeros((1, 3)))
91
+ pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.05, max_nn=30))
92
+ pcd.orient_normals_towards_camera_location(camera_location=np.array([0.0, 0.0, 1000.0]))
 
 
 
 
 
 
 
 
93
  pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
94
  pcd.transform([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
95
 
96
+ # Poisson Surface Reconstruction
97
+ mesh_raw, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
98
+ pcd, depth=depth, width=0, scale=1.1, linear_fit=True
99
+ )
 
100
 
101
+ # Step 3: Mesh simplification and smoothing
102
  voxel_size = max(mesh_raw.get_max_bound() - mesh_raw.get_min_bound()) / 512
 
103
  mesh = mesh_raw.simplify_vertex_clustering(
104
  voxel_size=voxel_size,
105
  contraction=o3d.geometry.SimplificationContraction.Average,
106
  )
 
 
107
  mesh = mesh.filter_smooth_simple(number_of_iterations=3)
 
108
  bbox = pcd.get_axis_aligned_bounding_box()
109
  mesh_crop = mesh.crop(bbox)
110
+
111
+ # Save GLTF
112
  gltf_path = f"./{image_path.stem}.gltf"
113
  o3d.io.write_triangle_mesh(gltf_path, mesh_crop, write_triangle_uvs=True)
114
  return gltf_path
115
 
116
+ # Gradio Interface
117
+ title = "Depth Estimation & 3D Reconstruction Demo"
118
+ description = "Upload an image and optionally a depth map (in .npy or image format) to generate a 3D model. If no depth map is provided, the DPT model will generate it."
119
 
 
 
 
120
  examples = [["examples/" + img] for img in os.listdir("examples/")]
121
 
122
  iface = gr.Interface(
123
  fn=process_image,
124
  inputs=[
125
  gr.Image(type="filepath", label="Input Image"),
126
+ gr.File(type="filepath", label="Input Depth Map (optional)"), # Changed input type to allow .npy
127
  ],
128
  outputs=[
129
  gr.Image(label="Predicted Depth", type="pil"),
 
136
  allow_flagging="never",
137
  cache_examples=False,
138
  )
139
+ iface.launch(debug=True, show_api=True, share=True)