Upload scripts/image_to_pointcloud.py
Browse files- 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()
|