Visual SLAM with RTAB‐Map - slgrobotics/articubot_one GitHub Wiki
Contents:
- Installation
- Running Gazebo simulation (dev branch)
- On Seggy robot - indoors (dev branch)
- On Dragger robot - outdoors (dev branch)
- RTAB-Map ROS2 and Nav2 Integration (AI generated notes)
- Useful links:
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.
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.
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
There are three common launch files:
launch/oakd.launch.pylaunch/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.
Your robot should at least publish the following topics:
-
/odometry/local(originally/odometry/filtered) type:nav_msgs/msg/Odometry -
/oak/rgb/image_recttype:sensor_msgs/msg/Image- regular RGB image -
/oak/stereo/image_rawtype:sensor_msgs/msg/Image- this is actually a depth image -
/oak/rgb/camera_infotype: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:
ros2 launch articubot_one rtabmap_viz.launch.py
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.
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
Dragger at least publishes the following topics:
-
/odometry/local(originally/odometry/filtered) type:nav_msgs/msg/Odometry -
/camera_stereo/image_rawtype:sensor_msgs/msg/Image- regular RGB image -
/camera_stereo/depth/image_rect_rawtype:sensor_msgs/msg/Image- this is actually a depth image -
/camera_stereo/camera_infotype: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 integrates very well with Nav2 and is one of the most commonly used SLAM systems for camera-based navigation in ROS 2.
Camera(s) IMU Wheel Odometry
│ │ │
└───────────┴──────────────┘
│
RTAB-Map ROS
(SLAM / Localization)
│
┌──────────────────┼──────────────────┐
│ │ │
map->odom TF Occupancy Grid Point Clouds
│
▼
Nav2
(Planner, Controller, Recovery)
│
▼
Robot Motion
RTAB-Map uses camera images together with odometry to:
- Estimate robot motion
- Detect loop closures
- Optimize the pose graph
- Publish the
map → odomtransform - Generate a 2D occupancy grid
- Optionally generate 3D point clouds and OctoMaps
Nav2 consumes the occupancy grid exactly like one produced by SLAM Toolbox.
After a map has been created:
- RTAB-Map loads the saved
.dbdatabase. - The robot drives using camera and odometry.
- RTAB-Map recognizes previously visited places.
- It continuously updates the
map → odomtransform.
Nav2 never needs to know whether RTAB-Map is mapping or localizing—it simply uses the updated transform.
| 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.
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_linkto the sensors
Drive robot
│
▼
RTAB-Map builds map
│
▼
Save database (.db)
│
▼
Done
Load database
│
▼
RTAB-Map recognizes places
│
▼
Updates map->odom
│
▼
Nav2 navigates
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
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 → odomtransform. - 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.
- https://introlab.github.io/rtabmap/
- https://github.com/introlab/rtabmap_ros
- https://github.com/introlab/rtabmap_ros/tree/ros2/rtabmap_examples/launch
- https://wiki.ros.org/rtabmap_ros (Tutorials)
- https://wiki.ros.org/rtabmap_ros/Tutorials/MappingAndNavigationOnTurtlebot
- https://wiki.ros.org/rtabmap_ros/Tutorials/SetupOnYourRobot