[ICRA 2023] Robust Imaging Sonar-based Place Recognition and Localization in Underwater Environments
Underwater there is no GPS and little light; a forward-looking imaging sonar sees 50 m ahead through murky water, but its images are noisy and change with the viewpoint. Sonar Context turns one sonar image into a small, robust place descriptor, so the robot knows it has been here before:
- π Sonar Context: the polar sonar image pooled into patches (range rings Γ bearing sectors), plus a polar key (one value per ring) that does not change with the heading.
- π Coarse to fine: the polar key finds candidates in a k-d tree; the Sonar Context compares them over every bearing shift (a rotation) and range shift.
- β‘ Lightweight: NumPy or C++17 with Eigen, no GPU and no training.
- π Online loop closing: DISO sonar odometry + Sonar Context loops, verified by dense phase correlation, in an iSAM2 pose graph, on ROS 1 and ROS 2.
- πΊοΈ Multi-session SLAM: sessions recorded apart are aligned into one map, LT-SLAM style (guide).
- π₯ Multi-robot: map merging from a few exchanged keyframes (Commerge).
The examples use Aracati 2017 (an ROV with a BlueView P900-130 in a marina): the bag (/son, /pose_gt, /cmd_vel) for the SLAM and the polar images (polar_image.zip, stored beams Γ range: layout="beam_range") for the library. DISO's Aracati2017_DISO (a submodule) adds the dead-reckoning prior. Also: HoloOcean simulation (Google Drive); KRISO data is not public.
pip install sonar-context # the library (NumPy, SciPy, Pillow)git clone --recursive https://github.com/sparolab/sonar_context.git && cd sonar_context
docker build -f docker/Dockerfile.slam -t sonar-context-slam:noetic . # ROS 1: DISO + SLAM
docker build -f docker/Dockerfile.slam-ros2 -t sonar-context-slam:humble . # ROS 2: SLAMC++ library
sudo apt install libeigen3-dev
cmake -S cpp -B cpp/build && cmake --build cpp/build -j && ctest --test-dir cpp/buildIn your project, add_subdirectory(sonar_context/cpp) and link sonar_context::sonar_context. Same distances and shifts as Python (checked in CI).
ROS 1 Noetic / ROS 2 Humble
GTSAM and OpenCV, then applications/slam in a catkin or colcon workspace (keep only the ROS version you build; ROS 1 also needs DISO's dependencies). The Dockerfiles list every step.
# ROS 1: the bag + DISO + Sonar Context SLAM (record:= also writes a bag for ROS 2)
docker run --rm -v <Aracati>:/data:ro -v $PWD/out:/out sonar-context-slam:noetic \
roslaunch sonar_context_slam aracati2017.launch bag:=/data/ARACATI_2017_8bits_full.bag save_dir:=/out/slam record:=/out/aracati_diso.bag
# ROS 2: the same SLAM on the recorded DISO poses (rosbags-convert the bag first)
docker run --rm -v $PWD/out:/out sonar-context-slam:humble \
ros2 launch sonar_context_slam aracati2017.launch.py bag:=/out/aracati_diso_ros2 save_dir:=/out/slam_ros2
# Your own odometry: set pose_topic (PoseStamped or Odometry), image_topic and image_layoutKeyframes every 1 m; Sonar Context proposes revisits; each is verified by dense phase correlation of the two sonar frames and closed with iSAM2. On Aracati 2017 (620 m in a marina), the loops bring DISO's sonar odometry from 4.54 m to 1.91 m APE against the DGPS ground truth. Results: applications/slam.
Without ROS:
import sonar_context as sc
desc = sc.sonar_context(image, patch=(4, 4)) # rows = range bins, columns = beams
dist, shift = sc.column_distance(desc_a, desc_b) # 1 - similarity, best bearing shift
m = sc.SonarContextManager(col_threshold=0.2, exclude_recent=30)
loop = m.update(image) # Loop(query, match, distance, column_shift, ...) or Nonesonar-context describe polar_image.zip --layout beam_range --out seq.npz # descriptors of a sequence
sonar-context loops seq.npz --out loops.csv --poses poses.txt # online loop detection
sonar-context affinity a.npz b.npz --out affinity.npy # two sessions, for CommergeLT-SLAM style (LT-mapper), as in RadLoc and ReFeree: sessions saved by the SLAM keep their own frames and get one anchor node each; inter-session Sonar Context loops, verified by dense correlation, connect them through the anchors. Aracati 2017 split in two sessions, each in its own frame, comes together at 2.52 m APE. Results: applications/multi-session-slam.
sonar_context_align_sessions session_a/ session_b/ merged/ --threshold 0.25See the multi-session SLAM guide.
Each robot keeps its Sonar Contexts; Commerge (IJRR 2026) picks the few keyframes that carry the overlap, and only those are exchanged: on Aracati split into two robots, 4.5 % of the keyframes (2.6 KB of poses) merge the maps within 1.3 m.
See applications/multi-robot-slam.
Open Claude Code or Codex in an empty folder and paste:
Clone https://github.com/sparolab/sonar_context, read its AGENTS.md, and follow it to build Sonar Context and run its SLAM on Aracati 2017 on this machine. Ask me what you need along the way.
The agent follows AGENTS.md: it builds the Docker images, runs the SLAM on the bag and checks the result.
If you use Sonar Context in your research, please cite our paper.
See bibtex
@inproceedings{kim2023robust,
title={Robust Imaging Sonar-based Place Recognition and Localization in Underwater Environments},
author={Kim, Hogyun and Kang, Gilhwan and Jeong, Seokhwan and Ma, Seungjun and Cho, Younggun},
booktitle={2023 IEEE International Conference on Robotics and Automation (ICRA)},
pages={1083--1089},
year={2023},
organization={IEEE}
}Issues and pull requests are welcome.
Sonar Context builds on Scan Context (Kim and Kim, IROS 2018). The SLAM uses DISO (Xu et al., ICRA 2024, GPLv3, a submodule) for odometry and GTSAM. Data: Aracati 2017 (dos Santos et al., RA-L 2022) and HoloOcean (Potokar et al., ICRA 2022); multi-session underwater SLAM: Jang et al., ICRA 2021. Related: SOLiD, Commerge, ReFeree, RadLoc.
We sincerely thank Giseop Kim for open-sourcing Scan Context, which Sonar Context builds on, and LT-mapper, whose multi-session design our multi-session SLAM follows.
BSD 3-Clause for academic use; for any commercial purpose, please contact the authors (hg.kim@inha.edu). DISO (thirdparty/DISO) is GPLv3 and is not part of this license.
