LiDAR Writeup A crash course in LiDAR 2 of 11
Coordinate frames, rigid transforms and calibration
Sequence: A crash course in LiDAR 2 of 11
Contents
A point measured by a LiDAR is a position relative to the sensor, and almost nothing useful can be done with it until it is expressed relative to something else: the vehicle carrying the sensor, a camera beside it, a map of the world, or the same sensor a tenth of a second earlier. This writeup develops the geometry that does this. It introduces coordinate frames and the rigid transforms between them, the ways rotations are represented and why each is used, the small-angle calculus on which every pose optimiser in later writeups depends, and then the operations performed on every scan in practice: extrinsic calibration, projection into a camera image, projection into a range image or a bird’s-eye grid, and the correction of the distortion a moving sensor puts into its own scan.
This writeup builds on How LiDAR works, which describes the points themselves, how they are computed from range and angle in the sensor’s frame (4), and why each carries a timestamp. A thorough treatment of the material here is [Barf17]; [Sol,18] is a short and practical one.
Coordinate frames
A coordinate frame is an origin and three orthonormal axes attached to something. A typical vehicle carries many: one for each LiDAR, camera, radar and inertial measurement unit (IMU), and a body frame for the vehicle itself, by ROS convention called base_link, usually on the ground beneath the centre of the rear axle. Beyond the vehicle there are frames fixed in the world: an odom frame, in which the vehicle’s motion is smooth but drifts over time, and a map frame, in which its position is globally consistent but may jump when the localisation is corrected.
Conventions for the axes matter, because mixing them is the most common source of bugs in this kind of code. Vehicle and LiDAR frames almost always follow the right-handed convention forward, left, up (ROS’s REP 103). Camera frames almost always follow the optical convention forward along the optical axis, right and down, matching image rows and columns. Some datasets and engines use up or a left-handed system. Before trusting any transform, it is worth checking which convention each frame uses.
Rotations
A rotation of three-dimensional space is a linear map that preserves lengths, angles and handedness. As a matrix, it is an with
and the set of all of them, closed under multiplication and inversion, is the special orthogonal group . Its inverse is its transpose, its columns are the axes of the rotated frame expressed in the original one, and composing two rotations is multiplying their matrices. A rotation has three degrees of freedom, though a matrix has nine entries, and the various representations of rotations are different ways of handling that redundancy.
Euler angles
The most intuitive representation is three successive rotations about coordinate axes. The usual one for vehicles is yaw about , then pitch about the new , then roll about the newest :
with and defined likewise. Roll, pitch and yaw are excellent for reading and for writing configuration files, since a mounting angle “pitched down ” is immediately clear. They are poor for computation. There are twelve different axis orders in use, and two programs that disagree about the order produce different rotations from the same three numbers. Interpolating the angles does not interpolate the rotations smoothly. And at a pitch of yaw and roll rotate about the same axis, so one degree of freedom is lost and nearby rotations require large changes of angle, a singularity called gimbal lock.
Axis and angle
Every rotation is a rotation by some angle about some unit axis , a fact known as Euler’s rotation theorem. Packing the two into one vector , the rotation vector, gives a minimal three-number representation without the arbitrary choice of axis order. The matrix is given by Rodrigues’ formula,
where is the skew-symmetric matrix that computes cross products, :
Unit quaternions
A quaternion is a four-component number with the multiplication rules . The rotation by about the unit axis is represented by the unit quaternion
which rotates a vector , written as the pure quaternion , by , where negates the three imaginary parts. Composing rotations is multiplying quaternions, and the corresponding matrix is
Quaternions are the representation of choice for storing and transmitting rotations: four numbers, no singularities, cheap to compose, and easily renormalised when rounding errors accumulate (whereas a matrix drifts away from orthogonality and must be re-orthogonalised). Two caveats are worth knowing. First, and represent the same rotation, since the half-angle makes a full turn of correspond to two turns of space. Second, libraries disagree on the order of the components: ROS and SciPy store , while Eigen’s constructor and many papers write .
The quaternion representation also gives the right way to interpolate. Spherical linear interpolation between unit quaternions and ,
moves at constant angular speed along the shortest rotation from one to the other as runs from to , provided the sign of is first flipped if , so that the shorter of the two paths is taken.
Rigid transforms
A rigid transform is a rotation followed by a translation, , and describes how one frame sits relative to another. The set of them is the special Euclidean group . Writing points in homogeneous coordinates, with a fourth coordinate equal to , turns the transform into a single matrix multiplication:
This course writes for the transform that takes the coordinates of a point in frame to its coordinates in frame , so that . Its translation is the origin of expressed in , and its rotation’s columns are the axes of expressed in , which is why is also called the pose of in . With this notation, chains read naturally, since adjacent subscripts cancel:
For example, a LiDAR point is placed on a map by : the first transform is the vehicle’s pose, which changes every instant, and the second is the sensor’s mounting, its extrinsic calibration, which is fixed. ROS’s tf2 library maintains the tree of all such transforms, with timestamps, so that any frame can be expressed in any other at any time.
Applying a transform to a whole point cloud is one matrix product. With the points as the rows of an array,
import numpy as np
def transform(T, points):
"""Apply a 4x4 rigid transform to an (N, 3) array of points."""
return points @ T[:3, :3].T + T[:3, 3] which costs and, for a scan of a hundred thousand points, well under a millisecond.
As a worked example, suppose a LiDAR is mounted m ahead of base_link and m above it, yawed to the left so that its axis points along the vehicle’s axis. Then
and a point m straight ahead of the sensor, , is at : ten metres to the vehicle’s left, as it should be.
Small motions and the tangent space
Every pose estimator in the course, from ICP to a SLAM back end, finds a transform by iteratively improving a guess. That requires a way to nudge a rotation by a small amount, and the rotation matrix itself cannot be nudged directly: adding a small matrix to does not in general leave it in . The remedy is to nudge in the space of rotation vectors and map the result back.
The map from rotation vectors to rotations is the exponential map. For a rotation vector it is
the matrix exponential of the skew-symmetric matrix, and summing the series gives exactly Rodrigues’ formula (1) with and . Its inverse, , recovers the rotation vector from a matrix. (The skew-symmetric matrices form the Lie algebra of the group, and the exponential map of a Lie group generalises the same idea; [Sol,18] develops it at the level needed here.) For a small rotation vector, the series gives the first-order approximation
and so perturbing a rotation by a small gives , which is linear in . An optimiser therefore works with the six numbers of a small rigid motion, linearises its cost through (2), solves a linear least-squares problem for them, applies them to the current estimate through the exponential map, and repeats. This pattern, sometimes called optimisation on the manifold, recurs in the writeups on registration and SLAM.
The same exponential map, extended to , describes a motion at constant linear and angular velocity, a twist : the pose after a fraction of a motion is . This is the basis of the motion correction described below.
Extrinsic calibration
The extrinsic calibration of a sensor is its fixed transform to the body frame or to another sensor. Errors in it are unforgiving: a yaw error of half a degree displaces a point at m by cm, enough to put a pedestrian on the wrong side of a kerb, or to make two LiDARs on the same vehicle disagree about where a wall is.
Calibration methods fall into three groups.
- Target-based methods observe a known object from both sensors. To calibrate a LiDAR against a camera, a planar board, often a checkerboard, is shown in several poses: the camera finds the board’s plane from the pattern and the LiDAR from the points that fall on it, and the transform is found that makes the planes agree. Boards with holes, spheres or retroreflective markers provide points and edges as well as planes.
- Motion-based methods solve the hand–eye problem. If two rigidly connected sensors each estimate their own motion between two moments, as and , the unknown transform between them satisfies ; several motions with differing axes of rotation determine . LiDAR odometry gives , and visual or wheel odometry gives .
- Targetless, appearance-based methods refine a calibration during normal driving by maximising the agreement between the sensors, for instance between LiDAR intensity and image brightness, or between depth discontinuities and image edges.
Two related calibrations are easy to forget. The intrinsic calibration of the LiDAR itself, the per-channel elevations and offsets of How LiDAR works, is supplied by the manufacturer but can be refined. And temporal calibration matters as much as spatial: if a LiDAR’s clock is ms behind a camera’s on a vehicle moving at m/s, the two disagree by cm. Sensors are therefore synchronised to a common clock, usually by the Precision Time Protocol (PTP) or by a GPS pulse-per-second signal.
Projection into a camera
Fusing a LiDAR with a camera, whether to colour the points, to give image pixels a depth, or to train a network on both, starts by projecting points into the image. A LiDAR point is first moved into the camera’s optical frame, , and then, under the pinhole model, onto the image at the pixel
where the focal lengths and the principal point , in pixels, are the camera’s intrinsics, usually gathered into a matrix . Real lenses also distort the image, and the distortion model found during camera calibration is applied to before .
def project(points_lidar, T_cam_lidar, K, width, height):
"""Pixel coordinates and depths of the LiDAR points that land in the image."""
p = transform(T_cam_lidar, points_lidar)
p = p[p[:, 2] > 0.1] # only points in front of the camera
uv = (p[:, :2] / p[:, 2:3]) @ K[:2, :2].T + K[:2, 2]
inside = (uv[:, 0] >= 0) & (uv[:, 0] < width) & (uv[:, 1] >= 0) & (uv[:, 1] < height)
return uv[inside], p[inside, 2] Two details trip up first implementations. Points behind the camera have and project, through the division, to apparently valid pixels, so they must be discarded first, as above. And because the camera and the LiDAR sit in different places, they see the scene from slightly different viewpoints. A point on a wall that the LiDAR sees just past the edge of a pole may be hidden from the camera behind that pole, and projecting it gives it the pole’s colour. Keeping, for each pixel, only the nearest projected point (a z-buffer) and discarding points much farther than their neighbours removes most of these errors. The resulting sparse depth image, with a LiDAR depth at a few percent of the pixels, is the input to depth completion and the training target for monocular depth networks.
Projection into images and grids
Many algorithms and most networks want an image rather than a set of points, and there are two standard ways of turning a cloud into one.
The spherical projection recovers a range image from any cloud, including one that has lost its sensor ordering. A point at range has azimuth and elevation , and is placed at
for an image spanning elevations from to . When several points fall in one pixel, the nearest is usually kept. When the cloud still carries each point’s ring index, that index should be used for the row instead, because the channels of a real sensor are not evenly spaced in elevation and do not share an origin, so the computed elevation places some points in the wrong row.
The bird’s-eye view (BEV) drops the vertical dimension: the ground plane around the vehicle is divided into square cells of side , a point falls in the cell , and each cell records statistics of its points, such as their number, maximum height and mean intensity. The BEV is the natural frame for driving, since objects on a road do not overlap from above and keep their real size at every range. It is the representation behind occupancy grids and most LiDAR object detectors, and it is the frame in which their outputs are evaluated.
Motion distortion and deskewing
How LiDAR works explained that a spinning sensor takes a tenth of a second to collect a scan, and that a moving sensor smears its own scan over that time. Correcting this, deskewing, re-expresses every point in the sensor’s frame at one reference time, usually the end of the sweep.
Let the sweep run from to and let point be measured at time , at the fraction of the sweep, with coordinates in the sensor’s frame at that moment. Writing for the sensor’s pose in a world frame at time , the point in the world is , and in the sensor frame at it is
The sensor’s motion over the sweep must come from somewhere: an IMU, wheel odometry, or, in LiDAR-only systems, the assumption that the motion during this sweep equals the motion estimated for the previous one, the constant-velocity model. If the motion over the sweep is and is taken to be at constant velocity, then and the correction collapses to
so each point is moved backwards along the motion by the fraction of the sweep still to come. In practice it is common to approximate the exponential by interpolating the rotation by slerp and the translation linearly, which differs from it negligibly over a single sweep:
from scipy.spatial.transform import Rotation, Slerp
def deskew(points, s, R_sweep, t_sweep):
"""Express each point in the sensor frame at the end of the sweep.
points: (N, 3) in the sensor frame at their own capture times.
s: (N,) fraction of the sweep elapsed at each capture, in [0, 1].
R_sweep, t_sweep: the sensor's motion over the sweep, as the pose of its
frame at the end of the sweep in its frame at the start.
"""
slerp = Slerp([0, 1], Rotation.concatenate([Rotation.identity(), R_sweep]))
R_k = slerp(s) # pose at capture, in start frame
p_start = R_k.apply(points) + s[:, None] * t_sweep
return R_sweep.inv().apply(p_start - t_sweep) # into the end frame Deskewing matters more than it seems. On a vehicle turning at per second, the start and end of a Hz sweep are rotated apart, which at m is an error of m; without correction, a registration algorithm aligns smeared scans and its estimate of the motion is biased. Objects that are themselves moving, such as other cars, remain distorted by their own motion even after the sensor’s motion is removed, which is one of the difficulties that object trackers face.
Summary
Points are useful only once placed in a common frame. Rotations live in and are best stored as unit quaternions, read as roll, pitch and yaw, and perturbed through the exponential map; rigid transforms live in , compose by multiplication, and are written with cancelling subscripts. Extrinsic, intrinsic and temporal calibration fix the relations between sensors, the pinhole model (3) carries points into camera images, the spherical projection and the bird’s-eye view carry them into images and grids, and deskewing removes the sensor’s own motion from each scan. The next writeup turns from where points are to which points are near which: the spatial data structures that make neighbourhood queries fast.
References
- [Barf17] Timothy D. Barfoot. State Estimation for Robotics. Cambridge University Press, 2017.
- [Sol,18] Joan Solà, Jeremie Deray and Dinesh Atchuthan. A micro Lie theory for state estimation in robotics. 2018.