Extrinsics Math
Notation
Section titled “Notation”| Symbol | Meaning |
|---|---|
| 3D point in the LiDAR / vehicle frame (metres): forward, left, up | |
| Same point in the OpenCV camera frame (metres): right, down, forward | |
| Rigid LiDAR→camera map: . , | |
| camera matrix for the pixels in the pose images (undistorted; right cam already ) | |
projection matrix written to params.yaml: | |
-th checkerboard corner in the board frame (), spaced by square_m | |
| That corner expressed in the camera frame (metres) | |
| The same corner recovered from the LiDAR board rectangle (metres) | |
| Detected corner in image pixels (runtime frame) | |
| Plane with unit normal | |
| Board plane in the camera frame | |
| (axes) | Orthonormal in-plane axes of the fitted board rectangle (metres); not image pixels |
| Pose | One synced capture: runtime image + cloud.npy |
Homogeneous projection into pixels is written with . More on and : projection math.
Overview
Section titled “Overview”Estimate from multi-pose board observations, then write .
Correspondences are not clicked. OpenCV recovers ordered chessboard corners on the camera; LiDAR recovers the same corners from a board-sized plane fit. Multi-pose consensus resolves planar labeling ambiguity that a single pose cannot.
Camera side (per pose)
Section titled “Camera side (per pose)”Images are already in SeeCam runtime space. Detection:
findChessboardCorners+cornerSubPixon a internal-corner grid (cols rows).- Object points lie in the board frame (), spaced by
square_m. solvePnPwith (distortion coeffs unused; image already undistorted) yields the board pose in the camera frame: rotation and translation , so a board point maps as .
Camera-frame pattern corners and the board plane:
So is the board normal in the camera frame (third column of ), and the plane through the board is . pose_to_plane is that conversion. If the board is not found, the pose is skipped.
LiDAR side (per pose)
Section titled “LiDAR side (per pose)”Input is a dense cloud (cloud.npy from /lidar_points), not /cones.
- Soft radial gate (script defaults about to from the LiDAR origin) and voxel downsample for discovery.
- Iterative RANSAC peels dominant planes (inlier distance about ), then refines each plane with SVD on its inliers.
- For each plane, project inliers into an orthonormal basis on that plane and fit a rectangle of known outer size (
board_width_mboard_height_m).
Rectangle fit sweeps the in-plane angle (about steps). For each angle it measures percentile extents of the projected points, checks size within tolerance (about ), and scores fill fraction: fraction of inliers inside the known-size box centered on the observed extent. Candidates need enough inliers (about ≥40) and fill ≥ about . Rank by fill; later consensus uses only the top-ranked footprint.
From the fitted rectangle center and in-plane axes (unit vectors in metres, lying in the LiDAR plane), rebuild ordered pattern corners (pattern_corners_from_board_rectangle). The printed checker is assumed centered on the board (plus optional offsets in BoardSpec), with the same row-major OpenCV ordering as board_object_points / the above.
Orientation ambiguity
Section titled “Orientation ambiguity”A rectangle has eight discrete corner labelings: 4 cyclic rotations × reflection (rectangle_corner_permutations). Each labeling produces a different ordered set .
For a single pose, Kabsch aligning to often has near-zero RMSE even for a wrong flip of the board in its plane. Local 3D error cannot break that symmetry. The code therefore keeps all size-consistent orientations with low local RMSE (about mean error on pattern corners), not only the single best.
initial_rt is a nominal seed: fixed LiDAR→OpenCV axis map, about pitch, and a hand-set camera position in the LiDAR frame. The multi-pose solve does not depend on its accuracy.
Consensus across poses
Section titled “Consensus across poses”Need ≥3 valid poses. Build a pool of candidate global transforms from the per-pose orientation hypotheses. For each candidate, and for each pose , pick the orientation of that pose’s LiDAR pattern that best matches under the same global map:
where is the number of internal corners (54 for a grid), are that orientation’s LiDAR corners for pose , and are the camera-frame corners from solvePnP on pose .
Accept a hypothesis only if every pose’s best stays below a few centimetres (script headroom about ). Among survivors, pick the lowest . That locks one oriented LiDAR pattern per pose.
Scoring applies the candidate global to each pose’s corners. It does not ask whether that pose’s own one-shot Kabsch rotation equals . A wrong flip can look great locally and still fail this global check.
Stacked Kabsch refine
Section titled “Stacked Kabsch refine”With consensus labels fixed, stack every pose’s LiDAR corners into one source cloud and every matching camera corner into one destination cloud. Re-estimate a single rigid map (orthogonal Procrustes / Kabsch). Below, the sums run over all stacked correspondences (all poses, all corners):
Here is the cross-covariance used by Kabsch, not a homogeneous transform. The resulting is what enters .
Observability
Section titled “Observability”Even a clean Kabsch fit is under-constrained if every board pose is nearly the same orientation and position. assess_observability looks at the LiDAR planes across poses:
- Max pairwise angle between normals (using so sign flips do not matter) ≥ about .
- Spread of plane points (a point on each plane along its normal): Euclidean std of those 3D points ≥ about .
Poses with parallel normals at similar range fail observability.
Acceptance (pixel)
Section titled “Acceptance (pixel)”After is fixed, project every consensus LiDAR pattern corner and compare 1:1 to the detected image corner (structured_board_reprojection_error):
with and the corresponding OpenCV corner in pixels.
Report mean and p95 of over all corners and poses. Script write gate: mean ≤ 2 px, p95 ≤ 4 px.
This is stricter than nearest-neighbor matching between projected cloud points and chessboard edges. Wrong orientation consensus can pass a soft nearest-neighbor check and still fail the structured gate.
LiDAR input
Section titled “LiDAR input”Plane peel and rectangle fill need many returns on the board face. The live cone stack publishes sparse centroids (interfaces/msg/Points on /cones); that representation cannot support this solve.
Procedure and sync gates: calibration procedure. Runtime and right-cam : projection math. Prod click path: legacy DLT.
