Configuration#
Every parameter is a launch argument. Set it on the command line as name:=value, or put any number of them in a YAML
file and pass that with config_file:=. Command line values override the file, and anything you leave unset keeps the
default listed here. Launch-only arguments go on the command line, and a config file that sets them is refused:
config_file and log_level everywhere, rviz and rviz_config_file for the two SLAM launch files, mode for
slam.launch.py, and rko_lio_config_file and autodetect_timeout for odometry_and_slam.launch.py.
ros2 launch rko_slam slam.launch.py -s # the same list, with defaults, from the launch file itself
config/ros_default.yaml has every parameter
a config file takes, with its default, commented out, as a file to start from.
Set lidar_topic, which is required, and imu_topic; odometry_and_slam.launch.py sets both itself, see
Running rko_lio with it. Everything else has a default that works as is, and
the ones worth changing first are splitting_distance and overlap_threshold.
Mode#
mode (default
online)onlinesubscribes to live topics.offlinedrains a rosbag at full speed and also needsbag_path.bag_path (offline only, required)
The rosbag2 directory to process.
odom_tum_path (offline only)
A TUM trajectory file to take the odometry from instead of the bag’s
/tf, see Frames.use_sim_time (
bool, defaultfalse)Use the
/clocktopic, for bag replay with--clock.
Input#
lidar_topic (required)
The
PointCloud2topic with the scans, taken as already deskewed unless you setdeskew. It can also be apoint_cloud_interfaces/CompressedPointCloud2topic, decoded by thepoint_cloud_transportplugin named in itsformatfield.odometry_and_slam.launch.pysets it to rko_lio’s deskewed scan and refuses it as an argument.base_frame (optional)
The frame rko_slam works in, usually
base_link, see Frames. Underodometry_and_slam.launch.pyit is shared with rko_lio: set it and rko_lio is started with it, leave it and rko_lio’s is used. Unset underslam.launch.py, it is the scan’s own frame.odom_frame (default
odom), map_frame (defaultmap)The odometry’s frame and the frame rko_slam publishes, see Frames: rko_slam broadcasts
map_frame <- odom_frame, inverted withinvert_map_tf. Underodometry_and_slam.launch.py,odom_frameis shared with rko_lio the same waybase_frameis.invert_map_tf (
bool, defaultfalse)Publish
map_frameas the TF child ofodom_frame, with the inverted transform. Set it when the odometry publishesodom_frameas the child ofbase_frame, as rko_lio does withinvert_odom_tf.odometry_and_slam.launch.pysets it to rko_lio’sinvert_odom_tfand refuses it as an argument.deskew (
bool, defaultfalse)Deskew the scan in rko_slam, from its per-point timestamps and the odometry TF. Set it when the scans are raw, and leave it off for an already deskewed cloud such as
/rko_lio/deskewed_scan.odometry_and_slam.launch.pysets it false and refuses it as an argument.imu_topic
The
sensor_msgs/Imutopic, the same IMU your odometry reads. Its accelerometer adds gravity edges that improve the map, see Keeping the map level. Its frame must be connected tobase_frameby a static transform on TF.odometry_and_slam.launch.pysets it to rko_lio’simu_topicand refuses it as an argument.tf_lookup_timeout_ms (
int, online only, default80)How long a TF lookup blocks before the scan is dropped, both the odometry and the static
base_frame <- the scan's frame.lidar_timestamps.multiplier_to_seconds (
float, default0.0), lidar_timestamps.force_absolute (bool, defaultfalse), lidar_timestamps.force_relative (bool, defaultfalse)Used with
deskew:=true. The multiplier scales per-point timestamps to seconds,0.0auto-detects seconds versus nanoseconds. The two flags force the timestamps to be read as absolute times, or as relative to the message stamp, when the auto-detection gets it wrong.
Sub-maps#
splitting_distance (
float, default50.0)Straight-line distance in metres from the sub-map’s keypose at which the sub-map is closed and the next one starts. This decides how many sub-maps a run has, and with
no_of_sub_maps_to_skipat 3 the first loop can close at the fifth sub-map. Smaller means more chances to catch a revisit, at the cost of more closure searches and a bigger graph. Across the environments I evaluated on: 100 m on long car sequences, 50 m on compact campus-scale ones, 30 m on short forest plots.voxel_size (
float, default0.5), max_points_per_voxel (int, default20)The resolution a sub-map is kept at: voxel size in metres, and how many points a voxel keeps. Closure refinement and the overlap check see the sub-map at this resolution.
min_range (
float, default1.0), max_range (float, default100.0)Points closer or further than this many metres never enter a sub-map.
Finding a revisit#
These shape how a candidate revisit is found, see How it works, and can be left at their defaults.
density_map_resolution (
float, default0.5), density_threshold (float, default0.05)The cell size in metres of the top-down image a sub-map is matched in, and how full a cell must be, against the fullest cell of the same sub-map, to show up in that image.
hamming_distance_threshold (
int, default50)How different two places may look and still be called a match, on a scale of 0 to 256: 1 accepts only identical ones. Lower is stricter.
inliers_threshold (
int, default5)How many matched features between the two sub-maps must agree on the same alignment for the candidate to be refined.
no_of_sub_maps_to_skip (
int, default3)How many of the most recent sub-maps to leave out of the search, so a sub-map does not match its own neighbours. It must be at least 1 within a run. Multi-session alignment sets it to 0, see below.
Accepting a closure#
overlap_threshold (
float, default0.4)After refinement, how much of the two sub-maps must overlap for the closure to be accepted: the voxels they share once aligned, over the voxel count of the smaller one, so 0 to 1. Raise it where many places look alike, lower it where revisits are missed. I used 0.4 on car and forest data, 0.5 on the Oxford Spires campus sequences, where 0.4 accepted false closures, and 0.3 when merging sessions that barely overlap.
Pose graph#
These can be left at their defaults.
rotation_info_scale (
float, default25.0)How much the optimizer trusts the rotation of an odometry or closure edge against its translation. It is the square of the range at which a rotation error and a translation error cost the same: the default corresponds to 5 m.
closure_info_scale (
float, default1.0)How much the optimizer trusts closures against odometry. Above 1 trusts closures more.
closure_kernel_delta (
float, default1.0)Closures that disagree with the rest of the graph by more than this many metres are given less weight.
gravity_info_scale (
float, default100.0)How much the optimizer trusts each sub-map’s measured up direction against the rest of the graph. Used with an
imu_topic, and in multi-session alignment of runs that had one.max_iterations (
int, default10)The optimizer runs at most this many iterations at each split.
Output#
dump_results (
bool, defaultfalse)Write the run directory: trajectory, pose graph, config and the sub-maps, see Outputs.
dump_sub_maps (
bool, defaulttrue)Also write each sub-map under
<run>/sub_maps/, which multi-session alignment needs. Ignored whendump_resultsis off. Turn it off to save disk space on long runs.results_dir (default
results), run_name (defaultrko_slamonline, the bag’s name offline)Where the run directory goes, and its name. A number is appended so nothing is overwritten.
Visualization#
publish_sub_maps, publish_closure_maps, publish_keypose_graph (
bool, defaultfalse)Publish each closed sub-map on
rko_slam/sub_mapswith asub_map_<i>TF chain, each accepted closure pair as a two-tone cloud onrko_slam/closure_maps, and the keyposes with their odometry and closure edges as markers onrko_slam/keypose_graph. For what that looks like, see Visualization.rviz (
bool, defaultfalse), rviz_config_file (defaultconfig/default.rviz)Launch RViz alongside. With the default config, the launch file sets this run’s frames in it and turns the three publishers above on. Under
odometry_and_slam.launch.pyit also shows rko_lio’s deskewed scan and its local map; with your ownrko_lio_config_file, the local map shows only if that file publishes one. Any other RViz config is passed through unchanged, with nothing forced on.
odometry_and_slam.launch.py only#
rko_lio_config_file
The YAML config the rko_lio node runs on, in the flat format of rko_lio’s own
config_file; what goes in it is in rko_lio’s configuration. It replaces rko_lio’s autodetection, so it must setlidar_topic,imu_topicand, unless given on this launch file,base_frame.base_frame,odom_frameanduse_sim_timeset on the launch file override what it defines,publish_deskewed_scan: trueis forced, and a file setting it false is refused.autodetect_timeout (
float, default10.0)Seconds rko_lio’s autodetection waits for each of the topics, their messages and the TF tree. Raise it when the robot or the bag needs longer to come up.
Other#
config_file
A YAML file with parameters from this page. Command line values override it.
log_level (default
info)ROS log level:
DEBUG,INFO,WARN,ERRORorFATAL.
Multi-session alignment#
align.launch.py works offline on run directories and takes only the parameters named here. The sub-map parameters come
from each run’s dumped config, and all runs must agree on voxel_size and max_points_per_voxel. It takes the
Finding a revisit, Accepting a closure and Pose graph
parameters, with the defaults above except where listed below, plus config_file, log_level and:
run_dirs (required)
The run directories to merge, at least two, as a list:
run_dirs:="[results/run_1, results/run_2]".results_dir (default
results), run_name (defaultaligned)Where the merged run goes, and its name. A number is appended so nothing is overwritten.
no_of_sub_maps_to_skip (
int, default0)Zero here, since neighbouring sub-maps from different sessions are real candidates.
max_iterations (
int, default100)The optimizer runs at most this many iterations over the merged graph.