Skip to content

Commit cdb49e4

Browse files
committed
lidar_colorization and mast3r reconstruction codes added
1 parent a841b2b commit cdb49e4

4 files changed

Lines changed: 1044 additions & 0 deletions

File tree

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import os
2+
from pathlib import Path
3+
import numpy as np
4+
import argparse
5+
import open3d as o3d
6+
import cv2
7+
import open3d as o3d
8+
9+
def parse_args():
10+
parser = argparse.ArgumentParser()
11+
parser.add_argument("--folder_path", type=str, required=True)
12+
parser.add_argument("--output_path", type=str, required=True)
13+
parser.add_argument("--camera_types", type=str, required=True)
14+
return parser.parse_args()
15+
16+
17+
def save_ply_with_open3d(points, filename):
18+
pc = o3d.geometry.PointCloud()
19+
pc.points = o3d.utility.Vector3dVector(points[:, :3])
20+
if points.shape[1] == 6:
21+
colors = points[:, 3:6] / 255.0 # normalize RGB to [0, 1]
22+
pc.colors = o3d.utility.Vector3dVector(colors)
23+
o3d.io.write_point_cloud(filename, pc)
24+
25+
def get_camera_extrinsic_matrix(camera_type):
26+
if camera_type == "front_right" or camera_type == "fr":
27+
# From your file
28+
rotation = np.array([
29+
[-0.7168464770690616, -0.10046018208578958, 0.6899557088168523],
30+
[-0.6970911725372957, 0.12308618950445319, -0.7063382243117325],
31+
[-0.01396515249660048, -0.9872981017750231, -0.15826380744561577]
32+
])
33+
34+
translation = np.array([1.8861563355156226, -0.7733611068168774, 1.6793040225335112])
35+
36+
# Build 4x4 homogeneous transformation matrix
37+
fr_to_vehicle = np.eye(4)
38+
fr_to_vehicle[:3, :3] = rotation
39+
fr_to_vehicle[:3, 3] = translation
40+
return fr_to_vehicle
41+
elif camera_type == "rear_right" or camera_type == "rr":
42+
# From your file
43+
rotation = np.array([
44+
[-0.7359657309159472, 0.15986191414426415, -0.6578743127098735],
45+
[0.6768157805459531, 0.14993386619459964, -0.7207220233709469],
46+
[-0.016578363047300385, -0.9756864271752846, -0.21854325362408236]
47+
])
48+
49+
translation = np.array([0.11419591502518789, -0.6896311735924415, 1.711181163333824])
50+
51+
# Build 4x4 homogeneous transformation matrix
52+
rr_to_vehicle = np.eye(4)
53+
rr_to_vehicle[:3, :3] = rotation
54+
rr_to_vehicle[:3, 3] = translation
55+
return rr_to_vehicle
56+
else:
57+
raise ValueError(f"Camera type {camera_type} not supported")
58+
59+
def get_camera_intrinsic_matrix(camera_type):
60+
if camera_type == "front_right" or camera_type == "fr":
61+
# Provided intrinsics
62+
focal = [1176.2554468073797, 1175.1456876174707] # fx, fy
63+
center = [966.4326452411585, 608.5803255934914] # cx, cy
64+
fr_cam_distort = [-0.2701363254469883, 0.16439325523243875, -0.001607207824773341, -7.412467081891699e-05,
65+
-0.06199397580030171]
66+
skew = 0 # assume no skew
67+
68+
fx, fy = focal
69+
cx, cy = center
70+
71+
# Build K matrix
72+
fr_cam_K = np.array([
73+
[fx, skew, cx],
74+
[0, fy, cy],
75+
[0, 0, 1]
76+
])
77+
return fr_cam_K, np.array(fr_cam_distort)
78+
elif camera_type == "rear_right" or camera_type == "rr":
79+
# Provided intrinsics
80+
focal = [1162.3787554048329, 1162.855381183851] # fx, fy
81+
center = [956.2663906909728, 569.2039945552984] # cx, cy
82+
rr_cam_distort = [-0.25040910859151444, 0.1109210921906881, -0.00041247665414900384, 0.0008205455176671751,
83+
-0.026395952816984845]
84+
skew = 0 # assume no skew
85+
86+
fx, fy = focal
87+
cx, cy = center
88+
89+
# Build K matrix
90+
rr_cam_K = np.array([
91+
[fx, skew, cx],
92+
[0, fy, cy],
93+
[0, 0, 1]
94+
])
95+
return rr_cam_K, np.array(rr_cam_distort)
96+
else:
97+
raise ValueError(f"Camera type {camera_type} not supported")
98+
99+
def get_lidar_extrinsic_matrix(lidar_type):
100+
if lidar_type == "ouster":
101+
# From your file
102+
rotation = np.array([
103+
[1,0,0],
104+
[0,1,0],
105+
[0,0,1]
106+
])
107+
108+
translation = np.array([1.10,0,2.03])
109+
110+
# Build 4x4 homogeneous transformation matrix
111+
ouster_to_vehicle = np.eye(4)
112+
ouster_to_vehicle[:3, :3] = rotation
113+
ouster_to_vehicle[:3, 3] = translation
114+
return ouster_to_vehicle
115+
else:
116+
raise ValueError(f"Lidar type {lidar_type} not supported")
117+
118+
def sample_rgb_colors(image_rgb, u_f, v_f):
119+
"""
120+
Sample RGB colors at subpixel positions (u_f, v_f) from image.
121+
Args:
122+
image_rgb: np.ndarray (H, W, 3), float32 in [0,1]
123+
u_f: (N,) float32
124+
v_f: (N,) float32
125+
Returns:
126+
(N, 3) array of RGB colors
127+
"""
128+
assert image_rgb.dtype == np.float32 and image_rgb.max() <= 1.0
129+
colors = []
130+
131+
for x, y in zip(u_f, v_f):
132+
patch = cv2.getRectSubPix(image_rgb, patchSize=(1, 1), center=(x, y))
133+
colors.append(patch[0, 0]) # (1, 1, 3) → get RGB triplet
134+
135+
return np.array(colors)
136+
137+
138+
def main():
139+
args = parse_args()
140+
folder_path = args.folder_path
141+
output_path = args.output_path
142+
camera_types = args.camera_types.split(',')
143+
print('camera_types', camera_types)
144+
145+
folder = Path(folder_path)
146+
files = list(folder.glob("*.b")) # all files
147+
148+
for file in files:
149+
# print(file.name)
150+
data = np.load(file, allow_pickle=True)
151+
# Save
152+
os.makedirs(output_path, exist_ok=True)
153+
save_ply_with_open3d(data, f"{output_path}/pure_pointcloud_{file.name}.ply")
154+
colors_full = np.zeros((data.shape[0], 3), dtype=np.float32) # default black
155+
points_h = np.hstack([data, np.ones((data.shape[0], 1))]) # shape: (N, 4)
156+
157+
for camera_type in camera_types:
158+
159+
# Get camera extrinsic matrix
160+
camera_to_vehicle = get_camera_extrinsic_matrix(camera_type)
161+
162+
# Get lidar extrinsic matrix
163+
lidar_to_vehicle = get_lidar_extrinsic_matrix("ouster")
164+
165+
# Get camera intrinsic matrix
166+
camera_K, camera_distort = get_camera_intrinsic_matrix(camera_type)
167+
168+
# Project points to camera
169+
# Transform lidar points into camera frame
170+
T_vehicle_to_cam = np.linalg.inv(camera_to_vehicle)
171+
T_lidar_to_cam = T_vehicle_to_cam @ lidar_to_vehicle
172+
173+
# Convert points to homogeneous coordinates
174+
175+
# Transform to camera frame
176+
points_cam = (T_lidar_to_cam @ points_h.T).T # shape: (N, 4)
177+
points_cam = points_cam[:, :3] # drop homogeneous component
178+
179+
# Filter out points behind the camera
180+
mask = points_cam[:, 2] > 0
181+
# points_cam = points_cam[mask]
182+
183+
# Load image
184+
point_cloud_number = file.name.split("_")[1].split('.')[0]
185+
# image_path = f"{file.parent.name}/{camera_type}_{point_cloud_number}.png"
186+
image_path = file.parent / f"{camera_type}_{point_cloud_number}.png"
187+
image_path = str(image_path)
188+
if not Path(image_path).exists():
189+
print(f"❌ Image file does not exist: {image_path}")
190+
continue
191+
image_raw = cv2.imread(image_path)
192+
h, w = image_raw.shape[:2]
193+
194+
cam_distort_matrix = camera_distort
195+
cam_k = camera_K
196+
# Get optimal camera matrix (undistorted intrinsics)
197+
cam_K_new, roi = cv2.getOptimalNewCameraMatrix(cam_k, cam_distort_matrix, (w, h), 0)
198+
199+
# Undistort the image
200+
image_undistorted = cv2.undistort(image_raw, cam_k, cam_distort_matrix, None, cam_K_new)
201+
202+
# Project to image
203+
proj = (cam_K_new @ points_cam.T).T # shape: (N, 3)
204+
with np.errstate(divide='ignore', invalid='ignore'):
205+
proj[:, 0] = np.divide(proj[:, 0], proj[:, 2], out=np.zeros_like(proj[:, 0]), where=proj[:, 2] != 0)
206+
proj[:, 1] = np.divide(proj[:, 1], proj[:, 2], out=np.zeros_like(proj[:, 1]), where=proj[:, 2] != 0)
207+
208+
# proj[:, 0] /= proj[:, 2]
209+
# proj[:, 1] /= proj[:, 2]
210+
pixels = proj[:, :2] # shape: (N, 2)
211+
212+
# Step 1: filter front-facing
213+
finite_mask = np.isfinite(proj[:, 0]) & np.isfinite(proj[:, 1])
214+
# mask_front = mask # Z_cam > 0
215+
mask_front = mask & finite_mask
216+
217+
# Projected pixel coords (u, v)
218+
u = pixels[:, 0]
219+
v = pixels[:, 1]
220+
if np.any(np.isnan(u)) or np.any(np.isnan(v)):
221+
print(f"❌ NaN values in u or v for {camera_type}")
222+
continue
223+
if np.any(np.isinf(u)) or np.any(np.isinf(v)):
224+
print(f"❌ Inf values in u or v for {camera_type}")
225+
continue
226+
# Step 2: in image bounds
227+
228+
# Load image and convert to RGB
229+
image_undistorted = cv2.cvtColor(image_undistorted, cv2.COLOR_BGR2RGB)
230+
image_undistorted = image_undistorted.astype(np.float32) / 255.0
231+
h, w, _ = image_undistorted.shape
232+
in_bounds = (u >= 0) & (u < w - 1) & (v >= 0) & (v < h - 1)
233+
# in_bounds = (u >= 0) & (v >= 0)
234+
# Step 3: Combine masks
235+
visible_mask = mask_front & in_bounds
236+
# visible_mask = mask_front
237+
238+
# Step 4: Get visible pixel coords
239+
u_f = u[visible_mask].astype(np.float32)
240+
v_f = v[visible_mask].astype(np.float32)
241+
if u_f.shape[0] == 0 or v_f.shape[0] == 0:
242+
print(f"❌ No visible points for {camera_type}")
243+
continue
244+
# Step 5: Interpolate RGB
245+
colors_interpolated = sample_rgb_colors(image_undistorted, u_f, v_f)
246+
# Step 6: Assign to full color array
247+
# colors_interpolated = np.concatenate(colors_interpolated, axis=1) # shape: (N, 3)
248+
colors_full[visible_mask] = colors_interpolated # only visible points get color
249+
250+
# Step 7: Create point cloud with full color info
251+
252+
colored_pcd = o3d.geometry.PointCloud()
253+
colored_pcd.points = o3d.utility.Vector3dVector(data) # full point set
254+
colored_pcd.colors = o3d.utility.Vector3dVector(colors_full) # partial color
255+
256+
o3d.io.write_point_cloud(f"{output_path}/colored_pointcloud_{file.name}.ply", colored_pcd)
257+
258+
259+
260+
if __name__ == "__main__":
261+
main()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Scaled 3D Scene Reconstruction from Images
2+
3+
This repository provides code for generating a **scaled 3D reconstructed scene** from a set of input images, using [MASt3R](https://github.com/naver/mast3r.git) as the core image matching and reconstruction backend.
4+
5+
## 🧩 Dependency
6+
7+
This project relies on the official [MASt3R repository](https://github.com/naver/mast3r.git) by NAVER Labs Europe.
8+
Please follow their installation instructions to set up the environment and dependencies.
9+
10+
## 🛠️ Installation
11+
12+
1. Clone this repository and the MASt3R submodule:
13+
14+
```bash
15+
git clone https://github.com/your-username/your-project.git
16+
cd your-project
17+
git clone --recursive https://github.com/naver/mast3r.git
18+
```

0 commit comments

Comments
 (0)