feat: implement ICP registration for ground plane refinement and add tests

This commit is contained in:
2026-02-10 03:04:43 +00:00
parent 2d42e2cdfa
commit 206c6e58ee
7 changed files with 1068 additions and 15 deletions
+29 -15
View File
@@ -245,11 +245,10 @@ def build_pose_graph(
extrinsics: Dict[str, Mat44],
pair_results: Dict[Tuple[str, str], ICPResult],
reference_serial: str,
) -> Tuple[o3d.pipelines.registration.PoseGraph, List[str]]:
) -> o3d.pipelines.registration.PoseGraph:
"""
Build a PoseGraph from pairwise results.
Only includes cameras reachable from the reference camera.
Returns (pose_graph, optimized_serials).
"""
# 1. Detect connected component from reference
connected = {reference_serial}
@@ -273,6 +272,13 @@ def build_pose_graph(
)
serial_to_idx = {s: i for i, s in enumerate(optimized_serials)}
# Log disconnected cameras
disconnected = set(serials) - connected
if disconnected:
logger.warning(
f"Cameras disconnected from reference {reference_serial}: {disconnected}"
)
pose_graph = o3d.pipelines.registration.PoseGraph()
for serial in optimized_serials:
T_wc = extrinsics[serial]
@@ -295,7 +301,7 @@ def build_pose_graph(
)
pose_graph.edges.append(edge)
return pose_graph, optimized_serials
return pose_graph
def optimize_pose_graph(
@@ -415,7 +421,7 @@ def refine_with_icp(
return extrinsics, metrics
# 3. Pose Graph
pose_graph, optimized_serials = build_pose_graph(
pose_graph = build_pose_graph(
valid_serials, extrinsics, pair_results, metrics.reference_camera
)
@@ -425,13 +431,26 @@ def refine_with_icp(
# 5. Extract and Validate
new_extrinsics = extrinsics.copy()
metrics.num_disconnected = len(valid_serials) - len(optimized_serials)
if metrics.num_disconnected > 0:
disconnected_serials = set(valid_serials) - set(optimized_serials)
logger.warning(
f"Cameras disconnected from reference {metrics.reference_camera}: {disconnected_serials}"
)
# Re-derive optimized_serials to match build_pose_graph logic for node-to-serial mapping
connected = {metrics.reference_camera}
queue = [metrics.reference_camera]
while queue:
curr = queue.pop(0)
for (s1, s2), result in pair_results.items():
if not result.converged:
continue
if s1 == curr and s2 not in connected:
connected.add(s2)
queue.append(s2)
elif s2 == curr and s1 not in connected:
connected.add(s1)
queue.append(s1)
optimized_serials = [metrics.reference_camera] + sorted(
list(connected - {metrics.reference_camera})
)
metrics.num_disconnected = len(valid_serials) - len(optimized_serials)
metrics.num_cameras_optimized = 0
for i, serial in enumerate(optimized_serials):
@@ -457,8 +476,3 @@ def refine_with_icp(
metrics.message = f"Optimized {metrics.num_cameras_optimized} cameras"
return new_extrinsics, metrics
metrics.success = metrics.num_cameras_optimized > 1
metrics.message = f"Optimized {metrics.num_cameras_optimized} cameras"
return new_extrinsics, metrics