Visual SLAM with RTAB‐Map - slgrobotics/articubot_one GitHub Wiki

This is how RTAB-Map works on Seggy and Dragger

Contents:


RTAB-Map ROS is the ROS 2 integration of the RTAB-Map (Real-Time Appearance-Based Mapping) library. It provides real-time visual and RGB-D SLAM, localization, loop-closure detection, and 2D/3D map generation using cameras, depth sensors, LiDAR, IMUs, GPS, and external odometry.

RTAB-Map ROS integrates with the ROS navigation stack (Nav2) by publishing the map frame, occupancy grids, point clouds, and localization estimates while supporting both mapping and localization against previously built maps.

RTAB-Map builds a graph of camera keyframes connected by odometry and loop closures. The map, images, point clouds, and graph are stored in an SQLite database (.db). The database can later be reopened for continued mapping or for localization, where the robot estimates its pose by matching live sensor data against the previously built map.

Installation

Use the binaries, on the robot's RPi and on the Dev machine:

sudo apt install ros-$ROS_DISTRO-rtabmap-ros

Note that the rtabmap_rviz GUI is also installed (to be used on the Dev machine).

Running Gazebo simulation (dev branch)

See this guide - Seggy uses a simulated OAK-D Lite camera.

Seggy_rtabmap_sim

For the simulated Dragger robot, commands are slightly different, as it runs camera_stereo simulation:

cd robot_ws
colcon build;
source install/setup.bash;
ros2 launch articubot_one dragger_sim.launch.py

In a fresh terminal run RTAB-Map (Dragger's camera_stereo is described here):

ros2 launch articubot_one rtabmap.launch.py \
 delete_db_on_start:=true publish_tf_map:=false \
 rgb_topic:=/camera_stereo/rgb/image_raw \
 depth_topic:=/camera_stereo/depth/image_rect_raw \
 camera_info_topic:=/camera_stereo/camera_info \
 use_sim_time:=true

And in another terminal - RTAB-Map viewer:

ros2 launch articubot_one rtabmap_viz.launch.py

On Seggy robot - indoors (dev branch)

There are three common launch files:

  • launch/oakd.launch.py
  • launch/rtabmap.launch.py
  • launch/rtabmap_viz.launch.py (use on Dev machine)

Seggy's launch files are modified to start:

  • OAK-D Lite: robots/seggy/launch/seggy.sensors.launch.py
  • RTAB-Map: robots/seggy/launch/seggy.localizers.launch.py

Note: RTAB-Map is disabled by default: 'rtabmap_enabled', default='false'

On Seggy you can either set 'rtabmap_enabled' to true, or run RTAB-Map from a separate terminal:

ros2 launch articubot_one rtabmap.launch.py delete_db_on_start:=true publish_tf_map:=false

Note that by default database_path:=$HOME/.ros/rtabmap.db, and publish_tf_map:=false ensures that RTAB-Map output does not interfere with your normal localization services.

Quick Topics Check:

Your robot should at least publish the following topics:

  • /odometry/local (originally /odometry/filtered) type: nav_msgs/msg/Odometry
  • /oak/rgb/image_rect type: sensor_msgs/msg/Image - regular RGB image
  • /oak/stereo/image_raw type: sensor_msgs/msg/Image - this is actually a depth image
  • /oak/rgb/camera_info type: sensor_msgs/msg/CameraInfo

The other subscribers, such as /gps/fix, /imu/data, AprilTag topics, and goal topics, are optional interfaces provided by the RTAB-Map node. You only need to publish to them if you want to use the corresponding functionality.

Viewing images: ros2 run rqt_image_view rqt_image_view

Full list:

Click to expand the list
ros@seggy:~/robot_ws$ ros2 node info /rtabmap/rtabmap
/rtabmap/rtabmap
  Subscribers:
    /detections: apriltag_msgs/msg/AprilTagDetectionArray
    /gps/fix: sensor_msgs/msg/NavSatFix
    /imu/data: sensor_msgs/msg/Imu
    /oak/rgb/camera_info: sensor_msgs/msg/CameraInfo
    /oak/rgb/image_rect: sensor_msgs/msg/Image
    /oak/stereo/image_raw: sensor_msgs/msg/Image
    /odometry/local: nav_msgs/msg/Odometry
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rtabmap/apriltag/detections: apriltag_msgs/msg/AprilTagDetectionArray
    /rtabmap/aruco/detections: aruco_msgs/msg/MarkerArray
    /rtabmap/aruco_opencv/detections: aruco_opencv_msgs/msg/ArucoDetection
    /rtabmap/global_pose: geometry_msgs/msg/PoseWithCovarianceStamped
    /rtabmap/goal: geometry_msgs/msg/PoseStamped
    /rtabmap/goal_node: rtabmap_msgs/msg/Goal
    /rtabmap/initialpose: geometry_msgs/msg/PoseWithCovarianceStamped
    /rtabmap/landmark_detection: rtabmap_msgs/msg/LandmarkDetection
    /rtabmap/landmark_detections: rtabmap_msgs/msg/LandmarkDetections
    /rtabmap/rtabmap/republish_node_data: std_msgs/msg/Int32MultiArray
    /user_data_async: rtabmap_msgs/msg/UserData
  Publishers:
    /diagnostics: diagnostic_msgs/msg/DiagnosticArray
    /goal_pose: geometry_msgs/msg/PoseStamped
    /parameter_events: rcl_interfaces/msg/ParameterEvent
    /rosout: rcl_interfaces/msg/Log
    /rtabmap/cloud_ground: sensor_msgs/msg/PointCloud2
    /rtabmap/cloud_map: sensor_msgs/msg/PointCloud2
    /rtabmap/cloud_obstacles: sensor_msgs/msg/PointCloud2
    /rtabmap/global_path: nav_msgs/msg/Path
    /rtabmap/global_path_nodes: rtabmap_msgs/msg/Path
    /rtabmap/goal_reached: std_msgs/msg/Bool
    /rtabmap/grid_prob_map: nav_msgs/msg/OccupancyGrid
    /rtabmap/info: rtabmap_msgs/msg/Info
    /rtabmap/labels: visualization_msgs/msg/MarkerArray
    /rtabmap/landmarks: geometry_msgs/msg/PoseArray
    /rtabmap/local_grid_empty: sensor_msgs/msg/PointCloud2
    /rtabmap/local_grid_ground: sensor_msgs/msg/PointCloud2
    /rtabmap/local_grid_obstacle: sensor_msgs/msg/PointCloud2
    /rtabmap/local_path: nav_msgs/msg/Path
    /rtabmap/local_path_nodes: rtabmap_msgs/msg/Path
    /rtabmap/localization_pose: geometry_msgs/msg/PoseWithCovarianceStamped
    /rtabmap/map: nav_msgs/msg/OccupancyGrid
    /rtabmap/mapData: rtabmap_msgs/msg/MapData
    /rtabmap/mapGraph: rtabmap_msgs/msg/MapGraph
    /rtabmap/mapOdomCache: rtabmap_msgs/msg/MapGraph
    /rtabmap/mapPath: nav_msgs/msg/Path
    /rtabmap/octomap_binary: octomap_msgs/msg/Octomap
    /rtabmap/octomap_empty_space: sensor_msgs/msg/PointCloud2
    /rtabmap/octomap_full: octomap_msgs/msg/Octomap
    /rtabmap/octomap_global_frontier_space: sensor_msgs/msg/PointCloud2
    /rtabmap/octomap_grid: nav_msgs/msg/OccupancyGrid
    /rtabmap/octomap_ground: sensor_msgs/msg/PointCloud2
    /rtabmap/octomap_obstacles: sensor_msgs/msg/PointCloud2
    /rtabmap/octomap_occupied_space: sensor_msgs/msg/PointCloud2
    /tf: tf2_msgs/msg/TFMessage
  Service Servers:
    /rtabmap/rtabmap/add_link: rtabmap_msgs/srv/AddLink
    /rtabmap/rtabmap/backup: std_srvs/srv/Empty
    /rtabmap/rtabmap/cancel_goal: std_srvs/srv/Empty
    /rtabmap/rtabmap/cleanup_local_grids: rtabmap_msgs/srv/CleanupLocalGrids
    /rtabmap/rtabmap/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /rtabmap/rtabmap/detect_more_loop_closures: rtabmap_msgs/srv/DetectMoreLoopClosures
    /rtabmap/rtabmap/get_map: nav_msgs/srv/GetMap
    /rtabmap/rtabmap/get_map_data: rtabmap_msgs/srv/GetMap
    /rtabmap/rtabmap/get_map_data2: rtabmap_msgs/srv/GetMap2
    /rtabmap/rtabmap/get_node_data: rtabmap_msgs/srv/GetNodeData
    /rtabmap/rtabmap/get_nodes_in_radius: rtabmap_msgs/srv/GetNodesInRadius
    /rtabmap/rtabmap/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /rtabmap/rtabmap/get_parameters: rcl_interfaces/srv/GetParameters
    /rtabmap/rtabmap/get_plan: nav_msgs/srv/GetPlan
    /rtabmap/rtabmap/get_plan_nodes: rtabmap_msgs/srv/GetPlan
    /rtabmap/rtabmap/get_prob_map: nav_msgs/srv/GetMap
    /rtabmap/rtabmap/get_type_description: type_description_interfaces/srv/GetTypeDescription
    /rtabmap/rtabmap/global_bundle_adjustment: rtabmap_msgs/srv/GlobalBundleAdjustment
    /rtabmap/rtabmap/list_labels: rtabmap_msgs/srv/ListLabels
    /rtabmap/rtabmap/list_parameters: rcl_interfaces/srv/ListParameters
    /rtabmap/rtabmap/load_database: rtabmap_msgs/srv/LoadDatabase
    /rtabmap/rtabmap/log_debug: std_srvs/srv/Empty
    /rtabmap/rtabmap/log_error: std_srvs/srv/Empty
    /rtabmap/rtabmap/log_info: std_srvs/srv/Empty
    /rtabmap/rtabmap/log_warning: std_srvs/srv/Empty
    /rtabmap/rtabmap/octomap_binary: octomap_msgs/srv/GetOctomap
    /rtabmap/rtabmap/octomap_full: octomap_msgs/srv/GetOctomap
    /rtabmap/rtabmap/pause: std_srvs/srv/Empty
    /rtabmap/rtabmap/publish_map: rtabmap_msgs/srv/PublishMap
    /rtabmap/rtabmap/remove_label: rtabmap_msgs/srv/RemoveLabel
    /rtabmap/rtabmap/reset: std_srvs/srv/Empty
    /rtabmap/rtabmap/resume: std_srvs/srv/Empty
    /rtabmap/rtabmap/set_goal: rtabmap_msgs/srv/SetGoal
    /rtabmap/rtabmap/set_label: rtabmap_msgs/srv/SetLabel
    /rtabmap/rtabmap/set_mode_localization: std_srvs/srv/Empty
    /rtabmap/rtabmap/set_mode_mapping: std_srvs/srv/Empty
    /rtabmap/rtabmap/set_parameters: rcl_interfaces/srv/SetParameters
    /rtabmap/rtabmap/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
    /rtabmap/rtabmap/trigger_new_map: std_srvs/srv/Empty
    /rtabmap/rtabmap/update_parameters: std_srvs/srv/Empty
  Service Clients:
    /rtabmap/rtabmap/describe_parameters: rcl_interfaces/srv/DescribeParameters
    /rtabmap/rtabmap/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
    /rtabmap/rtabmap/get_parameters: rcl_interfaces/srv/GetParameters
    /rtabmap/rtabmap/list_parameters: rcl_interfaces/srv/ListParameters
    /rtabmap/rtabmap/set_parameters: rcl_interfaces/srv/SetParameters
    /rtabmap/rtabmap/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
  Action Servers:

  Action Clients:

Running GUI on the Dev machine:

ros2 launch articubot_one rtabmap_viz.launch.py

On Dragger robot - outdoors (dev branch)

Screenshot from 2026-08-07 11-10-10

Unlike Seggy, Dragger relies on video, camera info and depth topics published by a separate Raspberry Pi.

It can either use OAK-D Lite, or dual cameras.

Option 1: using OAK-D Lite

The second Raspberry Pi 5 runs the DepthAI stack and the common RTAB-Map launch:

ros2 launch articubot_one dragger.oakd.launch.py
ros2 launch articubot_one rtabmap.launch.py delete_db_on_start:=true

A Gigabit Ethernet hub between the RPis ensures a fast connection (ping < 0.2 ms), and a travel router - a reliable WiFi link.

Option 2: using Dual cameras

Dragger's launch files are modified to start dual cameras:

  • RTAB-Map: robots/dragger/launch/dragger.localizers.launch.py

Note: RTAB-Map is disabled by default: 'rtabmap_enabled', default='false'

On Dragger you can either set 'rtabmap_enabled' to true, or run RTAB-Map from a separate terminal:

ros2 launch articubot_one rtabmap.launch.py \
   delete_db_on_start:=true database_path:=$HOME/.ros/rtabmap.db \
   rgb_topic:=/camera_stereo/rgb/image_raw \
   depth_topic:=/camera_stereo/depth/image_rect_raw \
   camera_info_topic:=/camera_stereo/camera_info \
   publish_tf_map:=false

Quick Topics Check:

Dragger at least publishes the following topics:

  • /odometry/local (originally /odometry/filtered) type: nav_msgs/msg/Odometry
  • /camera_stereo/image_raw type: sensor_msgs/msg/Image - regular RGB image
  • /camera_stereo/depth/image_rect_raw type: sensor_msgs/msg/Image - this is actually a depth image
  • /camera_stereo/camera_info type: sensor_msgs/msg/CameraInfo

The other subscribers, such as /gps/fix, /imu/data, AprilTag topics, and goal topics, are optional interfaces provided by the RTAB-Map node. You only need to publish to them if you want to use the corresponding functionality.

Viewing images: ros2 run rqt_image_view rqt_image_view

RTAB-Map ROS2 and Nav2 Integration (AI generated notes)

RTAB-Map integrates very well with Nav2 and is one of the most commonly used SLAM systems for camera-based navigation in ROS 2.

Overall Architecture

           Camera(s)      IMU       Wheel Odometry
               │           │              │
               └───────────┴──────────────┘
                           │
                     RTAB-Map ROS
                 (SLAM / Localization)
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
      map->odom TF     Occupancy Grid     Point Clouds
                           │
                           ▼
                         Nav2
              (Planner, Controller, Recovery)
                           │
                           ▼
                     Robot Motion

During Mapping

RTAB-Map uses camera images together with odometry to:

  • Estimate robot motion
  • Detect loop closures
  • Optimize the pose graph
  • Publish the map → odom transform
  • Generate a 2D occupancy grid
  • Optionally generate 3D point clouds and OctoMaps

Nav2 consumes the occupancy grid exactly like one produced by SLAM Toolbox.

During Localization

After a map has been created:

  1. RTAB-Map loads the saved .db database.
  2. The robot drives using camera and odometry.
  3. RTAB-Map recognizes previously visited places.
  4. It continuously updates the map → odom transform.

Nav2 never needs to know whether RTAB-Map is mapping or localizing—it simply uses the updated transform.

Data Exchanged with Nav2

Topic / TF Used by Nav2
map -> odom TF ✅ Robot localization
/rtabmap/map (OccupancyGrid) ✅ Global costmap
/tf ✅ Transform tree
/odometry/local (external) Input to RTAB-Map
/rtabmap/mapPath Optional visualization
/rtabmap/cloud_map Optional visualization

Nav2 provides:

  • Global planner
  • Local planner/controller
  • Recovery behaviors
  • Behavior Trees
  • Waypoint following

RTAB-Map is responsible only for localization and mapping.

Typical Transform Tree

map
 │
 └──► odom
        │
        └──► base_link
                 │
                 ├──► camera_link
                 ├──► imu_link
                 └──► laser_link
  • RTAB-Map publishes map → odom
  • robot_localization or wheel odometry publishes odom → base_link
  • robot_state_publisher publishes the fixed transforms from base_link to the sensors

Mapping Workflow

Drive robot
      │
      ▼
RTAB-Map builds map
      │
      ▼
Save database (.db)
      │
      ▼
Done

Localization Workflow

Load database
      │
      ▼
RTAB-Map recognizes places
      │
      ▼
Updates map->odom
      │
      ▼
Nav2 navigates

Advantages over SLAM Toolbox

For robots equipped with cameras such as the OAK-D Lite, RTAB-Map offers several advantages:

  • Works with RGB, stereo, and RGB-D cameras
  • Produces both 2D occupancy maps and rich 3D maps from the same sensor data
  • Uses appearance-based loop closure, which is effective in visually distinctive environments
  • Stores complete mapping sessions—including images, point clouds, poses, and graph topology—in a reusable SQLite database
  • Can fuse additional sensors such as LiDAR, IMUs, GPS, and AprilTags for improved robustness

Recommended Architecture

Wheel Encoders
        │
IMU ───► robot_localization
        │
        ▼
 /odometry/local
        │
        ▼
     RTAB-Map
        │
   map -> odom
        │
        ▼
       Nav2
        │
        ▼
   Drive Controller

This architecture cleanly separates responsibilities:

  • robot_localization provides a smooth, locally accurate odometry estimate (odom → base_link).
  • RTAB-Map performs global visual SLAM, detects loop closures, and publishes the corrected map → odom transform.
  • Nav2 uses the resulting global pose for path planning, obstacle avoidance, and autonomous navigation.

This is a robust and widely adopted architecture for indoor mobile robots using visual SLAM.

Useful links:

⚠️ **GitHub.com Fallback** ⚠️