Point Cloud Alignment¶
Point Cloud Alignment estimates the rigid transform between overlapping lidar
frames or point clouds. It can also align a multi-sensor FrameSet to
sensor 0, producing an extrinsic matrix for each sensor.
Users can calibrate the extrinsics of a multi-sensor setup, register two lidar
frames, or align two point clouds before combining or comparing them. Use the
align CLI command for multi-sensor sources, or call
align_clouds
from Python or C++ for frame-set, pairwise-frame, or point-cloud alignment.
The SDK uses a coarse-to-fine pipeline: it first searches over yaw and translation using FFT and histogram correlation, then refines the result with multi-pass ICP. This larger capture range makes it more robust than ICP-only alignment when the input point clouds start farther apart or have a larger initial yaw offset.
Using the CLI¶
Use a multi-sensor source with the align command. It matches the point
clouds from all available sensors when they have sufficient overlapping
coverage, estimates the transformations that align them to sensor 0, and
applies those transformations as the updated sensor extrinsics.
Chain viz to inspect the aligned point clouds. The command also prints the
resulting extrinsic matrices:
ouster-cli source {MULTI_SENSOR_OSF_PATH} align viz
For the best results, align expects the input sensor extrinsics to be
aligned with gravity. If the source still contains IMU packets from sensors
running FW 3.2 or later, run plumb before align to calculate the
gravity-aligned extrinsics:
ouster-cli source {SENSOR_HOSTNAME},{SENSOR_HOSTNAME2} plumb align viz
Chain save to write the frames and updated extrinsics to a new OSF file:
ouster-cli source {MULTI_SENSOR_OSF_PATH} align save aligned.osf
Using the API¶
Use align_clouds for pairwise frame alignment, point-cloud alignment, and multi-sensor extrinsic calibration.
Frame inputs must have gravity-aligned sensor_info.sensor_to_body rotations.
The standard frame and frame-set alignment paths use the full existing
sensor_info.sensor_to_body matrix, including translation, as the initial
extrinsic prior and estimate a correction from there. Point-cloud inputs must
already be in a gravity-aligned common frame.
Both overloads return rigid 4 x 4 transformation matrices, but the result depends on the input:
align_clouds(source_frame, target_frame)returnssource_to_target_transform, which aligns the source frame to the target frame. The target frame is the fixed reference. To calculate the aligned source extrinsic, left-multiply the source frame’s existing extrinsic by the returned transform.align_clouds(frameset)returns one aligned extrinsic matrix for each frame in a multi-sensorFrameSet. Frame 0 is the reference and must be valid. Every other valid frame is aligned to frame 0, and the returned transforms can be used as updated sensor extrinsics.
Each returned multi-sensor transform is written back to the same index as its
input frame, so aligned_extrinsics[i] corresponds to frames[i]. In
Python, the result has shape (N, 4, 4); in C++, it is a vector of 4 x 4
matrices.
Use align_clouds(source_frame, target_frame) to align two LidarFrame
objects:
source = open_source(source_file)
frames: core.FrameSet = next(iter(source))
target_frame = frames[0]
source_frame = frames[1]
source_to_target_transform = align_clouds(source_frame, target_frame)
aligned_source_extrinsic = source_to_target_transform @ (
source_frame.sensor_info.sensor_to_body)
auto source = open_source(source_file);
core::FrameSet frames = *source.begin();
const auto& target_frame = frames[0];
const auto& source_frame = frames[1];
auto source_to_target_transform = align_clouds(*source_frame, *target_frame);
auto aligned_source_extrinsic =
source_to_target_transform * source_frame->sensor_info->sensor_to_body;
Use align_clouds(frameset) to align every valid frame in a FrameSet to
frame 0, for example when calibrating sensor extrinsics:
source = open_source(source_file)
frames: core.FrameSet = next(iter(source))
aligned_extrinsics = align_clouds(frames)
for i, extrinsic in enumerate(aligned_extrinsics):
source.sensor_info[i].sensor_to_body = extrinsic
auto source = open_source(source_file);
core::FrameSet frames = *source.begin();
auto aligned_extrinsics = align_clouds(frames);
for (std::size_t i = 0; i < aligned_extrinsics.size(); ++i) {
source.sensor_info()[i]->sensor_to_body = aligned_extrinsics[i];
}