Skip to content

Projection Math

At runtime, point-to-pixel maps a LiDAR point X=(x,y,z,1)X = (x,y,z,1)^\top into image pixels with a single 3×4 matrix PP:

p=PXu=px/pzv=py/pz\begin{aligned} p &= P X \\ u &= p_x / p_z \\ v &= p_y / p_z \end{aligned}

On the feature-branch calibrator, PP is not a free 12-DoF DLT matrix. It is composed from a known camera matrix and a rigid LiDAR→camera transform:

P=Kruntime[Rt]P = K_{\text{runtime}}\,[R \mid t]

where [Rt][R \mid t] sends a point from the LiDAR frame into the OpenCV camera frame, and KruntimeK_{\text{runtime}} maps that 3D camera point into the runtime image that classifiers actually see.

Code: compose_projection in calib_geometry.py; application in state_manager.cpp::transform_points.

LiDAR points are stored as xx forward, yy left, zz up. OpenCV camera coordinates are xx right, yy down, zz forward. The extrinsic solve works in OpenCV axes; lidar_to_opencv_axes() is the fixed axis change used when seeding an initial (R,t)(R,t).

Intrinsics YAML stores the physical (pre-remap) camera matrix and distortion. SeeCam capture undistorts with getOptimalNewCameraMatrix(..., alpha=0), then the right camera rotates the image 180°:

(u,v)(W1u,  H1v)(u,v) \mapsto (W-1-u,\; H-1-v)

The extrinsic solver therefore builds

Kruntime={KnewleftAKnewrightA=[10W101H1001]K_{\text{runtime}} = \begin{cases} K_{\text{new}} & \text{left} \\ A\,K_{\text{new}} & \text{right} \end{cases} \quad A = \begin{bmatrix} -1 & 0 & W-1 \\ 0 & -1 & H-1 \\ 0 & 0 & 1 \end{bmatrix}

and writes P=Kruntime[Rt]P = K_{\text{runtime}}[R \mid t] so that projecting into the already-rotated right image needs no extra pixel remap at solve time. apply_runtime_pixel_transform encodes the same 180° map if you start from a physical-pixel PP.

If you calibrate against undistorted-but-not-rotated right frames, or feed distorted frames into the extrinsic scripts, the resulting PP will look systematically wrong even when (R,t)(R,t) is fine.

For a LiDAR point on the board (or a cone), (u,v)(u,v) should match the corresponding image feature. The feature-branch write gate uses mean and p95 error between projected LiDAR pattern corners and detected chessboard corners (extrinsics math).

Prod click-DLT estimates PP directly from clicked pairs without forcing the K[Rt]K[R|t] factorization; see legacy DLT.