Files
rtabmap_ros/rtabmap_util/doc/lidar_deskewing.md
T
matlabbe 82f0754bf7 rtabmap_demos tests and docs (#1462)
* rtabmap_demos tests and docs

* added bag testing

* added rtabmap_examples launch tests

* updating demo bag download paths

* added netherdrone demo

* Fixed rgb-only callback with lidar rejected.  Updated lidar params

* Added back OrbitOriented rviz view to ros2, with optional octomap wll clipping

* fixing ci

* lidar demo added intermediate_nodes option

* added netherdrone as demo test

* running rtabmap_demos tests on ci

* added rtabmap_launch tests, fixed ground_truth_base_frame_id usage

* ficing rolling

* updating demo test harnest

* densify golden trajectories to avoid tf missing

* fixing tf steps

* updated min icp ratio for netherdrone demo

* fixing image_transport arg->params

* export pose opt=0

* lets process all frames

* updated netherdrone golden

* fixing publishers queue size just for tests

* added playdback demo doc

* Adding more logs to debug ci

* Fixing QOS for CI to reliable, added find-object demo test

* name threads

* fixing camera info expected transient on lyrical/rolling. Fixing find_object not appearing idle

* 30 Hz polling backward comp

* fixing test tf sim lock

* fixing lyrical qos bag parsing

* faster replay

* lockstep

* fixing clock deadlock

* Added test on shutdown

* updated netherdrone golden poses

* Extended stereo outdoor test

* updated shutdown test

* updated test

* multi-thread flaky test

* adding backtrace when test fails

* increased closure slack for netherdrone

* g2o gauss newton on stereo

* adjusted maximum optimizer iterations

* updated default iterations

* Added netherdrone in list of demos
2026-10-10 12:23:49 -07:00

5.2 KiB

lidar_deskewing

Removes the motion distortion from a lidar scan.

A spinning lidar takes tens of milliseconds to complete a sweep, and on a moving robot every point in that sweep is measured from a slightly different pose. The result is a skewed cloud: straight walls come out bent, and registration against it drifts.

This node uses TF to find where the sensor actually was when each point was taken, and moves every point into the pose at the start of the sweep. A straight wall comes back straight.

Contents

Usage

ros2 run rtabmap_util lidar_deskewing --ros-args \
  -p fixed_frame_id:=odom \
  -r input_cloud:=/velodyne_points
ComposableNode(
    package='rtabmap_util',
    plugin='rtabmap_util::LidarDeskewing',
    name='lidar_deskewing',
    parameters=[{'fixed_frame_id': 'odom'}],
    remappings=[('input_cloud', '/velodyne_points')])

Subscribed Topics

Connect one of the two.

Topic Type Description
input_cloud sensor_msgs/msg/PointCloud2 Must carry a per-point time channel, see Requirements.
input_scan sensor_msgs/msg/LaserScan Per-point times come from time_increment.

Published Topics

Output names are derived from the resolved input names, so remapping the input moves the output with it. With input_cloud remapped to /velodyne_points the output is /velodyne_points/deskewed.

Topic Type Description
<input_cloud>/deskewed sensor_msgs/msg/PointCloud2 The deskewed cloud, same frame and stamp as the input.
<input_scan>/deskewed sensor_msgs/msg/PointCloud2 A LaserScan cannot represent a deskewed sweep — the points no longer lie on a regular angular grid — so the scan input also produces a cloud.

Required Transforms

Transform Description
fixed_frame_id → sensor frame, across the sweep Must be available for the whole span of the sweep, at both its first and last stamp.

Parameters

Parameter Type Default Description
fixed_frame_id string "" Required. Frame the motion is measured against, usually odom.
wait_for_transform double 0.01 Seconds to wait for the transforms spanning the sweep. Raise it if odometry lags the lidar.
slerp bool false Interpolate between the poses at the start and end of the sweep instead of looking up TF per point. Much cheaper, and accurate enough at constant velocity.
queue_size int 5 Queue depth of the input subscriptions.
output_queue_size int 1 History depth of the deskewed cloud publishers.
qos int 0 Reliability of the input subscriptions: 0 system default, 1 reliable, 2 best effort.

Requirements

For input_cloud, the cloud must have a per-point time field. Without one the node cannot know when each point was taken and cannot deskew.

The field has to be named t, time, stamps or timestamp — anything else is not recognized, whatever it contains. Its type decides how the value is read:

Type Meaning
uint32 nanoseconds since the cloud's own stamp
float32 seconds since the cloud's own stamp
float64 an absolute timestamp; seconds, milliseconds, microseconds and nanoseconds are told apart by magnitude

Common drivers that satisfy this out of the box: Ouster (t), Velodyne (time), RoboSense (timestamp) and Livox (timestamp). Livox needs its PointCloud2 output rather than the default CustomMsg format, which this node cannot subscribe to at all.

To check what your driver actually publishes:

ros2 topic echo /your/points --field fields --once

If none of the four names is in that list, look for a driver option to add per-point timestamps before anything else.

The fixed_frame_id → sensor transform must cover the whole sweep, which means odometry has to be at least as recent as the lidar. If it lags, raise wait_for_transform.

Behavior when TF is missing

The two inputs deliberately differ:

  • A cloud is republished unchanged with a warning. Deskewing is an improvement, not a precondition, and dropping frames would break the pipeline behind it.
  • A scan is dropped, because converting it to a cloud is only worth doing as part of deskewing.

Notes

Deskewing matters most when rotating: at 1 rad/s a 100 ms sweep spans nearly 6°, and the far end of the scan is badly misplaced. Pure translation at walking speed is a few centimeters, which matters at close range.

Put this node before ICP odometry or point_cloud_assembler, not after. Anything registering against a skewed cloud has already paid for the distortion.