Tohru127 commited on
Commit
67615f4
·
verified ·
1 Parent(s): 93426d1

Create utils.py

Browse files
Files changed (1) hide show
  1. utils.py +265 -0
utils.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utility functions for point cloud processing and visualization
3
+ """
4
+
5
+ import numpy as np
6
+ import open3d as o3d
7
+ from pathlib import Path
8
+ import cv2
9
+
10
+ def visualize_point_cloud(ply_file):
11
+ """
12
+ Visualize a single point cloud using Open3D
13
+
14
+ Usage:
15
+ visualize_point_cloud("pointcloud_000000.ply")
16
+ """
17
+ pcd = o3d.io.read_point_cloud(ply_file)
18
+
19
+ # Statistical outlier removal
20
+ pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
21
+
22
+ # Estimate normals
23
+ pcd.estimate_normals(
24
+ search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)
25
+ )
26
+
27
+ o3d.visualization.draw_geometries(
28
+ [pcd],
29
+ window_name="Point Cloud Viewer",
30
+ width=1024,
31
+ height=768,
32
+ point_show_normal=False
33
+ )
34
+
35
+ def merge_point_clouds(ply_files, output_file="merged_pointcloud.ply", subsample_rate=0.3):
36
+ """
37
+ Merge multiple point clouds into one
38
+
39
+ Args:
40
+ ply_files: List of PLY file paths
41
+ output_file: Output merged PLY file
42
+ subsample_rate: Keep only this fraction of points (0.3 = 30%)
43
+
44
+ Usage:
45
+ merge_point_clouds(["pc1.ply", "pc2.ply"], "merged.ply")
46
+ """
47
+ merged_pcd = o3d.geometry.PointCloud()
48
+
49
+ for ply_file in ply_files:
50
+ pcd = o3d.io.read_point_cloud(str(ply_file))
51
+
52
+ # Subsample to reduce size
53
+ if subsample_rate < 1.0:
54
+ pcd = pcd.random_down_sample(subsample_rate)
55
+
56
+ merged_pcd += pcd
57
+
58
+ # Remove duplicates and outliers
59
+ merged_pcd = merged_pcd.remove_duplicated_points()
60
+ merged_pcd, _ = merged_pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
61
+
62
+ # Save
63
+ o3d.io.write_point_cloud(output_file, merged_pcd)
64
+ print(f"Merged point cloud saved: {output_file}")
65
+ print(f"Total points: {len(merged_pcd.points)}")
66
+
67
+ return output_file
68
+
69
+ def create_mesh_from_pointcloud(ply_file, output_file="mesh.ply", depth=9):
70
+ """
71
+ Create mesh from point cloud using Poisson surface reconstruction
72
+
73
+ Args:
74
+ ply_file: Input PLY file
75
+ output_file: Output mesh file
76
+ depth: Poisson depth (higher = more detail, slower)
77
+
78
+ Usage:
79
+ create_mesh_from_pointcloud("pointcloud.ply", "mesh.ply")
80
+ """
81
+ pcd = o3d.io.read_point_cloud(ply_file)
82
+
83
+ # Estimate normals if not present
84
+ if not pcd.has_normals():
85
+ pcd.estimate_normals(
86
+ search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)
87
+ )
88
+ pcd.orient_normals_consistent_tangent_plane(k=15)
89
+
90
+ # Poisson reconstruction
91
+ print("Running Poisson reconstruction...")
92
+ mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
93
+ pcd, depth=depth
94
+ )
95
+
96
+ # Remove low density vertices
97
+ vertices_to_remove = densities < np.quantile(densities, 0.01)
98
+ mesh.remove_vertices_by_mask(vertices_to_remove)
99
+
100
+ # Save
101
+ o3d.io.write_triangle_mesh(output_file, mesh)
102
+ print(f"Mesh saved: {output_file}")
103
+ print(f"Vertices: {len(mesh.vertices)}, Triangles: {len(mesh.triangles)}")
104
+
105
+ return output_file
106
+
107
+ def animate_reconstruction(ply_directory, output_video="reconstruction.mp4", fps=10):
108
+ """
109
+ Create a video animating through the point clouds
110
+
111
+ Args:
112
+ ply_directory: Directory containing PLY files
113
+ output_video: Output video file
114
+ fps: Frames per second
115
+
116
+ Usage:
117
+ animate_reconstruction("./point_clouds", "animation.mp4")
118
+ """
119
+ ply_files = sorted(Path(ply_directory).glob("*.ply"))
120
+
121
+ if not ply_files:
122
+ print("No PLY files found!")
123
+ return
124
+
125
+ # Setup visualizer
126
+ vis = o3d.visualization.Visualizer()
127
+ vis.create_window(visible=False, width=1920, height=1080)
128
+
129
+ # Get first point cloud for camera setup
130
+ pcd = o3d.io.read_point_cloud(str(ply_files[0]))
131
+ vis.add_geometry(pcd)
132
+
133
+ # Setup camera
134
+ ctr = vis.get_view_control()
135
+ ctr.set_zoom(0.8)
136
+
137
+ # Render options
138
+ opt = vis.get_render_option()
139
+ opt.point_size = 2.0
140
+ opt.background_color = np.asarray([0, 0, 0])
141
+
142
+ # Capture frames
143
+ frames = []
144
+ for i, ply_file in enumerate(ply_files):
145
+ print(f"Rendering frame {i+1}/{len(ply_files)}")
146
+
147
+ # Update point cloud
148
+ pcd = o3d.io.read_point_cloud(str(ply_file))
149
+ vis.clear_geometries()
150
+ vis.add_geometry(pcd)
151
+
152
+ # Rotate camera slightly
153
+ ctr.rotate(10.0, 0.0)
154
+
155
+ # Capture
156
+ vis.poll_events()
157
+ vis.update_renderer()
158
+ img = np.asarray(vis.capture_screen_float_buffer(do_render=True))
159
+ img = (img * 255).astype(np.uint8)
160
+ frames.append(cv2.cvtColor(img, cv2.COLOR_RGB2BGR))
161
+
162
+ vis.destroy_window()
163
+
164
+ # Write video
165
+ height, width = frames[0].shape[:2]
166
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
167
+ out = cv2.VideoWriter(output_video, fourcc, fps, (width, height))
168
+
169
+ for frame in frames:
170
+ out.write(frame)
171
+
172
+ out.release()
173
+ print(f"Video saved: {output_video}")
174
+
175
+ def depth_map_to_pointcloud(rgb_image_path, depth_npy_path, output_ply, subsample=0.5):
176
+ """
177
+ Convert single RGB image and depth map to point cloud
178
+
179
+ Args:
180
+ rgb_image_path: Path to RGB image
181
+ depth_npy_path: Path to depth numpy array
182
+ output_ply: Output PLY file
183
+ subsample: Subsampling rate (0.5 = keep 50% of points)
184
+
185
+ Usage:
186
+ depth_map_to_pointcloud("frame.jpg", "depth.npy", "output.ply")
187
+ """
188
+ # Load data
189
+ rgb = cv2.imread(rgb_image_path)
190
+ rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
191
+ depth = np.load(depth_npy_path)
192
+
193
+ # Normalize depth to reasonable scale
194
+ depth = depth.astype(np.float32)
195
+ depth = (depth - depth.min()) / (depth.max() - depth.min())
196
+ depth = depth * 10.0 # Scale to 0-10 units
197
+
198
+ h, w = depth.shape
199
+
200
+ # Camera intrinsics (adjust for your camera)
201
+ fx = fy = w * 0.7
202
+ cx, cy = w / 2, h / 2
203
+
204
+ # Create 3D points
205
+ u, v = np.meshgrid(np.arange(w), np.arange(h))
206
+ z = depth
207
+ x = (u - cx) * z / fx
208
+ y = (v - cy) * z / fy
209
+
210
+ points = np.stack([x, y, z], axis=-1).reshape(-1, 3)
211
+ colors = rgb.reshape(-1, 3) / 255.0
212
+
213
+ # Subsample
214
+ if subsample < 1.0:
215
+ n_points = int(len(points) * subsample)
216
+ indices = np.random.choice(len(points), n_points, replace=False)
217
+ points = points[indices]
218
+ colors = colors[indices]
219
+
220
+ # Create point cloud
221
+ pcd = o3d.geometry.PointCloud()
222
+ pcd.points = o3d.utility.Vector3dVector(points)
223
+ pcd.colors = o3d.utility.Vector3dVector(colors)
224
+
225
+ # Clean up
226
+ pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
227
+
228
+ # Save
229
+ o3d.io.write_point_cloud(output_ply, pcd)
230
+ print(f"Point cloud saved: {output_ply} ({len(pcd.points)} points)")
231
+
232
+ return output_ply
233
+
234
+
235
+ if __name__ == "__main__":
236
+ import sys
237
+
238
+ if len(sys.argv) > 1:
239
+ command = sys.argv[1]
240
+
241
+ if command == "visualize" and len(sys.argv) > 2:
242
+ visualize_point_cloud(sys.argv[2])
243
+
244
+ elif command == "merge" and len(sys.argv) > 3:
245
+ ply_files = sys.argv[2:-1]
246
+ output = sys.argv[-1]
247
+ merge_point_clouds(ply_files, output)
248
+
249
+ elif command == "mesh" and len(sys.argv) > 2:
250
+ output = sys.argv[3] if len(sys.argv) > 3 else "mesh.ply"
251
+ create_mesh_from_pointcloud(sys.argv[2], output)
252
+
253
+ elif command == "animate" and len(sys.argv) > 2:
254
+ output = sys.argv[3] if len(sys.argv) > 3 else "reconstruction.mp4"
255
+ animate_reconstruction(sys.argv[2], output)
256
+
257
+ else:
258
+ print("Usage:")
259
+ print(" python utils.py visualize <ply_file>")
260
+ print(" python utils.py merge <ply1> <ply2> ... <output>")
261
+ print(" python utils.py mesh <input_ply> [output_mesh]")
262
+ print(" python utils.py animate <ply_directory> [output_video]")
263
+ else:
264
+ print("Utility functions loaded. Import in Python:")
265
+ print(" from utils import visualize_point_cloud, merge_point_clouds")