Engineering Guides · Module 06
Transforming points between camera, LiDAR and base frames
Name every transform as target-from-source, compose by chaining, invert for the other direction, and check with a round trip.
Assumptions: Numbers in the examples are illustrative and the code is executed by the site’s tests. Facts taken from a cited source are marked; other statements are general engineering knowledge and must be confirmed on the datasheet of the part you use.
1. Engineering problem
Sensors are mounted at different places and orientations. To use their data together every point must be expressed in one frame. Most calibration bugs are a transform applied in the wrong direction or composed in the wrong order.
2. When is this needed?
- Fusing LiDAR and camera data.
- Writing a static transform for a new sensor.
- Debugging data that appears mirrored, shifted or rotated.
3. Selection and design decisions
A rigid transform is a 4×4 matrix [R t; 0 1]. Writing TBL for “base from LiDAR” means: multiply a point in LiDAR coordinates to get its coordinates in the base frame. Chains compose by matching inner names: TCL = TCB · TBL = (TBC)⁻¹ · TBL. The ROS frame conventions are in REP 103 (x forward, y left, z up for body frames; camera optical frames use x right, y down, z forward).
| Mistake | Symptom | Check |
|---|---|---|
| Inverse applied instead of forward | Sensor appears mirrored through the origin | Round trip a known point |
| Wrong multiplication order | Offsets rotate with the wrong frame | Names must chain: C←B then B←L |
| Body frame used where optical frame is expected | Image axes swapped | Use the optical frame for projection (x right, y down, z forward) |
| Degrees and radians mixed | Large angular error | Use one unit and name it |
| Parent and child swapped in tf2 | Transform inverted on the tree | Compare with tf2_echo |
4. Advantages and disadvantages
- Store the mounting transforms to a common base (base←sensor) and derive the sensor-to-sensor transforms; this keeps one source of truth per sensor.
- Direct sensor-to-sensor calibration measures the quantity you need most accurately and must be re-chained to the base.
- Quaternions avoid gimbal problems; matrices are easier to read.
5. Requirements and constraints
- Each transform has a documented direction (target from source) and a unit.
- Rotations are orthonormal with determinant +1.
- The same transform chain is used in code, URDF and tf2.
6. Architecture
lidar ──T_BL──▶ base ◀──T_BC── camera T_CL = inv(T_BC) · T_BL7. Implementation
- Measure or calibrate the base←LiDAR and base←camera transforms and record their direction and units.
- Compose the camera←LiDAR transform by chaining (below).
- Transform a known point and check the result by hand.
- Check the round trip (forward then inverse) gives the point back.
- Publish the same values as static transforms.
import numpy as np
def T(R, t):
M = np.eye(4); M[:3, :3] = R; M[:3, 3] = t; return M
def yaw(deg):
a = np.radians(deg); c, s = np.cos(a), np.sin(a)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1.0]])
T_BL = T(yaw(0), [0.30, 0.00, 0.50]) # base <- lidar
T_BC = T(yaw(5), [0.45, 0.05, 0.40]) # base <- camera (small yaw for illustration)
T_CL = np.linalg.inv(T_BC) @ T_BL # camera <- lidar = (base <- camera)^-1 (base <- lidar)
p_L = np.array([10.0, 0.0, 0.0, 1.0])
p_B = T_BL @ p_L
p_C = T_CL @ p_L
print("point in base:", np.round(p_B[:3], 3))
print("point in camera body frame:", np.round(p_C[:3], 3))
print("round trip error:", float(np.abs(np.linalg.inv(T_BL) @ p_B - p_L).max()))Printed (checked by the site’s tests):
point in base: [10.3 0. 0.5]
point in camera body frame: [ 9.808 -0.908 0.1 ]
round trip error: 0.0Reading it: a point 10 m ahead of the LiDAR is 10.3 m ahead and 0.5 m above the base (the LiDAR is mounted 0.30 m forward and 0.50 m up). In the camera body frame it appears at about (9.81, −0.91, 0.10): the 5° camera yaw and 0.05 m offset shift it to the right of the camera axis. The result is in the camera body frame (x forward); converting to the optical frame needs the fixed axis permutation from REP 103. The mounting values are illustrative.
8. Real examples
- AMR: LiDAR 0.30 m ahead of and 0.50 m above base_link, camera 0.45 m ahead and 0.40 m above; the camera←LiDAR transform is derived, not measured twice.
9. Failure scenarios
- The static transform published as child←parent instead of parent←child.
- The optical-frame permutation forgotten, so the image looks rotated 90°.
- Degrees given to a function that expects radians.
10. Troubleshooting
- Put a known object at a known place and compare predicted and observed positions.
- Print T and its inverse and check the translation sign.
- Use tf2_echo between the frames and compare with your matrix.
11. Validation
- The example prints the values shown (executed by the site tests).
- A known object appears at the predicted position within the calibration accuracy.
12. Engineering checklist
13. Further references
- REP 103: rep-0103.rst, read 2026-11-05.
- ROS 2 Jazzy tf2 tutorials: static broadcaster and debugging tf2 problems, read 2026-11-06.