fix: implement per-camera ground plane correction
This commit is contained in:
@@ -51,7 +51,10 @@ class GroundPlaneMetrics:
|
||||
correction_applied: bool = False
|
||||
num_cameras_total: int = 0
|
||||
num_cameras_valid: int = 0
|
||||
correction_transform: Mat44 = field(default_factory=lambda: np.eye(4))
|
||||
# Per-camera corrections
|
||||
camera_corrections: Dict[str, Mat44] = field(default_factory=dict)
|
||||
skipped_cameras: List[str] = field(default_factory=list)
|
||||
# Summary stats (optional, maybe average of corrections?)
|
||||
rotation_deg: float = 0.0
|
||||
translation_m: float = 0.0
|
||||
camera_planes: Dict[str, FloorPlane] = field(default_factory=dict)
|
||||
@@ -354,7 +357,7 @@ def refine_ground_from_depth(
|
||||
metrics.message = f"Found {len(valid_planes)} valid planes, required {config.min_valid_cameras}"
|
||||
return extrinsics, metrics
|
||||
|
||||
# 3. Compute consensus
|
||||
# 3. Compute consensus (for reporting/metrics only)
|
||||
try:
|
||||
consensus = compute_consensus_plane(valid_planes)
|
||||
metrics.consensus_plane = consensus
|
||||
@@ -362,42 +365,55 @@ def refine_ground_from_depth(
|
||||
metrics.message = f"Consensus computation failed: {e}"
|
||||
return extrinsics, metrics
|
||||
|
||||
# 4. Compute correction
|
||||
correction = compute_floor_correction(
|
||||
consensus,
|
||||
target_floor_y=config.target_y,
|
||||
max_rotation_deg=config.max_rotation_deg,
|
||||
max_translation_m=config.max_translation_m,
|
||||
)
|
||||
# 4. Compute and apply per-camera correction
|
||||
new_extrinsics = extrinsics.copy()
|
||||
|
||||
metrics.correction_transform = correction.transform
|
||||
|
||||
if not correction.valid:
|
||||
metrics.message = f"Correction invalid: {correction.reason}"
|
||||
return extrinsics, metrics
|
||||
|
||||
# 5. Apply correction
|
||||
# T_corr is the transform that moves the world frame.
|
||||
# New world points P' = T_corr * P
|
||||
# We want new extrinsics T'_world_cam such that P' = T'_world_cam * P_cam
|
||||
# T'_world_cam * P_cam = T_corr * (T_world_cam * P_cam)
|
||||
# So T'_world_cam = T_corr * T_world_cam
|
||||
|
||||
new_extrinsics = {}
|
||||
T_corr = correction.transform
|
||||
# Track max rotation/translation for summary metrics
|
||||
max_rot = 0.0
|
||||
max_trans = 0.0
|
||||
corrections_count = 0
|
||||
|
||||
for serial, T_old in extrinsics.items():
|
||||
# If we didn't find a plane for this camera, skip it
|
||||
if serial not in metrics.camera_planes:
|
||||
metrics.skipped_cameras.append(serial)
|
||||
continue
|
||||
|
||||
plane = metrics.camera_planes[serial]
|
||||
|
||||
correction = compute_floor_correction(
|
||||
plane,
|
||||
target_floor_y=config.target_y,
|
||||
max_rotation_deg=config.max_rotation_deg,
|
||||
max_translation_m=config.max_translation_m,
|
||||
)
|
||||
|
||||
if not correction.valid:
|
||||
metrics.skipped_cameras.append(serial)
|
||||
continue
|
||||
|
||||
T_corr = correction.transform
|
||||
metrics.camera_corrections[serial] = T_corr
|
||||
|
||||
# Apply correction: T_new = T_corr @ T_old
|
||||
new_extrinsics[serial] = T_corr @ T_old
|
||||
|
||||
# Calculate metrics
|
||||
# Rotation angle of T_corr
|
||||
trace = np.trace(T_corr[:3, :3])
|
||||
cos_angle = np.clip((trace - 1) / 2, -1.0, 1.0)
|
||||
metrics.rotation_deg = float(np.rad2deg(np.arccos(cos_angle)))
|
||||
metrics.translation_m = float(np.linalg.norm(T_corr[:3, 3]))
|
||||
# Update summary metrics
|
||||
trace = np.trace(T_corr[:3, :3])
|
||||
cos_angle = np.clip((trace - 1) / 2, -1.0, 1.0)
|
||||
rot_deg = float(np.rad2deg(np.arccos(cos_angle)))
|
||||
trans_m = float(np.linalg.norm(T_corr[:3, 3]))
|
||||
|
||||
max_rot = max(max_rot, rot_deg)
|
||||
max_trans = max(max_trans, trans_m)
|
||||
corrections_count += 1
|
||||
|
||||
metrics.rotation_deg = max_rot
|
||||
metrics.translation_m = max_trans
|
||||
metrics.success = True
|
||||
metrics.correction_applied = True
|
||||
metrics.message = "Success"
|
||||
metrics.correction_applied = corrections_count > 0
|
||||
metrics.message = (
|
||||
f"Corrected {corrections_count} cameras, skipped {len(metrics.skipped_cameras)}"
|
||||
)
|
||||
|
||||
return new_extrinsics, metrics
|
||||
|
||||
Reference in New Issue
Block a user