A ROS 2 package for drift-free localization in warehouse environments and other GPS-denied spaces. The system combines Odometry data with ArUco/AprilTag marker detection to produce a globally consistent map to odom transform without any active infrastructure (no UWB anchors, no Wi-Fi fingerprinting, no surveying).
The following video is a demo of how this package was used for drone localization in a warehouse. The green path is multi-fisheye-camera VIO output and blue path is the drift-corrected path using AR markers. Upon seeing any of the markers, it does a full history correction using iSAM2 aglorithm from GTSAM library.
- It provides a marker mapping tool based on data recorded from a smart phone, through optimizing a pose-landmark factor graph. This can be an alternative to use of surveying equipment for marker mapping.
- Modular camera configuration where you can add any number of cameras that are on your robot. The condition is that there should be a single central IMU on the robot and each camera should have a Kalibr-style camera-IMU calibration config (.yaml) file.
- Support for quad-camera omni-directional setups similar to OmniNxt.
The system runs inside the D²SLAM Docker container, which bundles all required dependencies (ROS 2 Humble, GTSAM, OpenCV-CUDA, TensorRT, ONNX Runtime, etc.).
cd /path/to/your/workspace
git clone https://github.com/SaxionMechatronics/ar_slam.git
git clone https://github.com/SaxionMechatronics/D2SLAM.git
cd D2SLAM
git checkout ROS2cd D2SLAM
docker build -f docker/Dockerfile.ros2_humble -t d2slam:ros2-humble .The OpenCV CUDA build step might take several minutes.
The container needs two source trees mounted into the same ROS 2 workspace:
xhost +local:docker
docker run -it --rm --runtime=nvidia --gpus all --net=host \
-e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
-v /path/to/D2SLAM:/d2slam_ws/src/d2slam \
-v /path/to/ar_slam:/d2slam_ws/src/ar_slam \
-v /path/to/data:/data \
d2slam:ros2-humble bashReplace /path/to/D2SLAM, /path/to/ar_slam, and /path/to/data with your actual paths. The ar_slam package is volumed into the same src/ tree so both packages are built together.
pip install -r /d2slam_ws/src/ar_slam/requirements.txtsource /opt/ros/humble/setup.bash
cd /d2slam_ws
colcon build --symlink-install --parallel-workers 6 --cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bashPrint AprilTag 25H9 (any dictionary supported by opencv's aruco module) markers, measure the physical size of them, and stick them around the environment at arbitrary positions and heights. No surveying or precise placement is required; the mapper will compute their 3D positions automatically.
- Dictionary:
DICT_APRILTAG_25H9 - Recommended physical size: 25 cm (side length; adjustable in
ar_mapper.yaml) - Print source: AprilTag 25H9 PDF generator or generate with OpenCV
Place markers, making sure they will be visible from the robot's cameras.
Currently, the package supports the data recorded by Record3D iOS app (tested on an iPhone 16 Pro Max) to scan the environment.
-
Settings used:
-
Walk the environment in closed loops, visiting each marker multiple times without consecutive order, from different angles and distances.
-
Slow, deliberate movement gives better pose data
-
Export the recording from the Record3D app in
.r3dformat and transfer the file into thedata/folder of this package:
The ar_mapper node processes the recording, detects AprilTag markers in each frame, and runs a full pose-landmark factor graph optimisation with GTSAM (Levenberg–Marquardt). Every marker observation from every frame becomes a factor; the optimizer jointly estimates all camera poses and all 3D marker positions, averaging out sensor noise across all the loops you walked.
# Configure the paths in config/ar_mapper.yaml, then:
ros2 launch ar_slam ar_mapper.launch.pyKey parameters (config/ar_mapper.yaml):
| Parameter | Default | Description |
|---|---|---|
recording |
data/recording.r3d |
Path to the .r3d file (or pre-extracted directory) |
aruco_dict |
DICT_APRILTAG_36H11 |
Marker dictionary — must match printed tags |
marker_size |
0.25 |
Physical side length in metres |
freq_hz |
1.0 |
Temporal sub-sampling of the 60 fps stream |
markers_csv_out |
markers_optimized.csv |
Output landmark map path |
The node publishes a live RViz2 visualization of the optimised marker positions and the reconstruction trajectory. On completion it writes markers_optimized.csv; this is the only file that will be used for online marker-based optimization on the robot.
Upon launching the marker mapper, the results will be displayed in RVIZ similar to the following image. The optimized positions of markers will be saved to markers_optimized.csv. Note that the pose of all markers will subtracted from pose of a marker of choice (id=0 by default). Thus, be careful about how you intend to define the origin of map coordinate system defined by these markers. In our drone localization case, we needed z axis upwards, thus the marker 0 is placed horizontally.
You can use any source of odometry that suits your robot. For our aerial robot, we used D²VINS, which can give us visual-inertial odometry estimation, combining 4 fish-eye cameras.
The ar_localizer node subscribes to the live quadcam image stream and the VIO odometry from D²VINS. It detects AprilTag markers in all four camera sub-images simultaneously, adds marker-pose factors into an online GTSAM iSAM2 factor graph, and publishes a corrected map to odom TF that compensates for VIO drift.
# Terminal 1 — VIO odometry (inside Docker, see Installation section)
ros2 launch d2vins quadcam.launch.py
# Terminal 2 — AR localizer
ros2 launch ar_slam ar_localizer.launch.py
# Terminal 3 — sensor data
ros2 bag play /data/your_sequence.bagKey parameters (config/ar_localizer.yaml):
| Parameter | Default | Description |
|---|---|---|
landmark_csv |
markers_optimized.csv |
Map produced by ar_mapper (Step 3) |
camera0 |
... | An entry for introducing a camera input. You can have multiple camera entries (camera1, camera2, ...). For this entry, you can specify the topic for the camera, the type of the camera (quadcam fisheye, or mono camera), the calibration file for that camera chain, and etc. Note that for quad-cameras used in our implementation, we used the calibration tools provided by OmniNxt. |
odom_topic |
/odom |
Odometry input |
See LICENSE for details.

