bdck commited on
Commit
808837f
·
verified ·
1 Parent(s): 180521e

Upload scripts/image_to_pointcloud.py

Browse files
Files changed (1) hide show
  1. scripts/image_to_pointcloud.py +119 -0
scripts/image_to_pointcloud.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI script: image → metric depth → 3D point cloud using DepthPro.
4
+
5
+ Usage
6
+ -----
7
+ python image_to_pointcloud.py photo.jpg output.ply --device cuda:0 --sample-step 2
8
+ python image_to_pointcloud.py photo.jpg output.ply --colored --save-depth depth.png
9
+ """
10
+
11
+ import argparse
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ import numpy as np
16
+
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ from depthpro_wrapper import (
20
+ DepthProEstimator,
21
+ depth_to_point_cloud,
22
+ rgbd_to_point_cloud,
23
+ normals_from_depth,
24
+ load_image,
25
+ save_point_cloud,
26
+ )
27
+
28
+
29
+ def main() -> None:
30
+ parser = argparse.ArgumentParser(
31
+ description="Apple DepthPro: image → metric depth → 3D point cloud"
32
+ )
33
+ parser.add_argument("image", type=Path, help="Input RGB image")
34
+ parser.add_argument("output", type=Path, help="Output point cloud (.ply)")
35
+ parser.add_argument("--device", default="cuda:0", help="PyTorch device")
36
+ parser.add_argument("--dtype", choices=["float16", "float32"], default="float16", help="Inference dtype")
37
+ parser.add_argument("--colored", action="store_true", help="Include per-point RGB colours")
38
+ parser.add_argument("--normals", action="store_true", help="Include per-point normals")
39
+ parser.add_argument("--sample-step", type=int, default=1, help="Spatial sub-sampling (1 = full res, 2 = 1/4 points)")
40
+ parser.add_argument("--save-depth", type=Path, default=None, help="Also save depth map as .npy")
41
+ parser.add_argument("--save-confidence", type=Path, default=None, help="Also save confidence map as .npy")
42
+ args = parser.parse_args()
43
+
44
+ if not args.image.exists():
45
+ parser.error(f"Input image not found: {args.image}")
46
+
47
+ # ---- depth estimation -----------------------------------------------
48
+ print(f"Loading DepthPro on {args.device} (dtype={args.dtype}) ...")
49
+ dtype = {"float16": "float16", "float32": "float32"}[args.dtype]
50
+ import torch
51
+ torch_dtype = torch.float16 if dtype == "float16" else torch.float32
52
+
53
+ estimator = DepthProEstimator(device=args.device, dtype=torch_dtype)
54
+
55
+ print(f"Estimating depth for {args.image} ...")
56
+ result = estimator.estimate(
57
+ args.image,
58
+ return_confidence=args.save_confidence is not None,
59
+ )
60
+
61
+ print(f" Image size: {result.width}×{result.height}")
62
+ print(f" Estimated focal length: {result.focal_length:.1f} px")
63
+ print(f" Estimated FOV: {result.field_of_view:.1f}°")
64
+ print(f" Depth range: {result.depth.min():.2f} m – {result.depth.max():.2f} m")
65
+
66
+ # ---- optional saves -------------------------------------------------
67
+ if args.save_depth:
68
+ np.save(args.save_depth, result.depth)
69
+ print(f" Saved depth map → {args.save_depth}")
70
+
71
+ if args.save_confidence and result.confidence is not None:
72
+ np.save(args.save_confidence, result.confidence)
73
+ print(f" Saved confidence map → {args.save_confidence}")
74
+
75
+ # ---- back-projection ------------------------------------------------
76
+ print("\nBack-projecting to 3D point cloud ...")
77
+
78
+ normals = None
79
+ if args.normals:
80
+ normals = normals_from_depth(result.depth, result.focal_length)
81
+
82
+ if args.colored:
83
+ points, colors = rgbd_to_point_cloud(
84
+ result.depth,
85
+ result.image,
86
+ result.focal_length,
87
+ sample_step=args.sample_step,
88
+ )
89
+ if args.normals:
90
+ # Sample normals at same grid
91
+ H, W = result.depth.shape
92
+ v_idx = np.arange(0, H, args.sample_step)
93
+ u_idx = np.arange(0, W, args.sample_step)
94
+ valid = result.depth[v_idx[:, None], u_idx[None, :]] > 0
95
+ normals = normals[v_idx[:, None], u_idx[None, :]]
96
+ normals = normals[valid]
97
+ print(f" Colored point cloud: {len(points):,} points")
98
+ save_point_cloud(args.output, points, colors=colors, normals=normals)
99
+ else:
100
+ points = depth_to_point_cloud(
101
+ result.depth,
102
+ result.focal_length,
103
+ sample_step=args.sample_step,
104
+ )
105
+ if args.normals:
106
+ H, W = result.depth.shape
107
+ v_idx = np.arange(0, H, args.sample_step)
108
+ u_idx = np.arange(0, W, args.sample_step)
109
+ valid = result.depth[v_idx[:, None], u_idx[None, :]] > 0
110
+ normals = normals[v_idx[:, None], u_idx[None, :]]
111
+ normals = normals[valid]
112
+ print(f" Point cloud: {len(points):,} points")
113
+ save_point_cloud(args.output, points, normals=normals)
114
+
115
+ print(f"\nDone — saved to {args.output}")
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()