Gazebo simulation for text-based indoor robot navigation. TurtleBot3 Waffle (RGB-D camera) + text sign/ad banner models in a house environment.
text_nav_sim/
├── launch/
│ ├── sim_mapping.launch.py # Phase 1: SLAM mapping + OCR
│ ├── sim_navigation.launch.py # Phase 2A: Nav2 + slam_toolbox localization
│ └── sim_navigation_amcl.launch.py # Phase 2B: Nav2 + map_server + AMCL
├── config/
│ ├── slam_toolbox_mapping.yaml # SLAM async mapping mode
│ ├── slam_toolbox_localization.yaml # SLAM localization mode
│ └── nav2_sim_params.yaml # Nav2 stack parameters
├── worlds/
│ └── turtlebot3_house_signs.world # 15m x 10.5m house with signs
├── models/
│ ├── turtlebot3_waffle_rgbd/ # Robot with RGB-D camera
│ ├── turtlebot3_house/ # Static house structure
│ ├── text_sign_*/ # 10 room name signs
│ └── ad_banner_*/ # 4 advertisement banners
├── scripts/
│ ├── generate_sign_textures.py # Texture generator (PIL)
│ ├── sign_tour_node.py # Automated tour visiting all signs
│ └── depth_f32_to_u16_node.py # Depth format converter
├── urdf/
│ └── turtlebot3_waffle_rgbd.urdf
└── rviz/
├── sim_mapping.rviz
└── sim_navigation.rviz
- ROS2 Humble
- gazebo_ros, turtlebot3_gazebo
- slam_toolbox, nav2_bringup
- textmap, text_nav_bridge, navocr
- Python: Pillow (texture generation)
source /opt/ros/humble/setup.bash
colcon build --packages-select text_nav_sim
source install/setup.bashSLAM mapping + NavOCR text detection + textmap landmark recording. Drive the robot with teleop or the automated tour to explore the environment.
# Terminal 1: Launch mapping
ros2 launch text_nav_sim sim_mapping.launch.py
# Terminal 2: Manual teleop
ros2 run turtlebot3_teleop teleop_keyboard
# Terminal 2 (alternative): Automated sign tour
ros2 run text_nav_sim sign_tour_node.py --ros-args -p use_sim_time:=trueWhen the tour completes, save the map before pressing Ctrl+C:
# Terminal 3: Create save directory
mkdir -p ~/map/<DIR>
# Save occupancy grid (.pgm + .yaml)
ros2 run nav2_map_server map_saver_cli -f ~/map/<DIR>/map \
--ros-args -p map_subscribe_transient_local:=true
# Save slam_toolbox serialized map (.posegraph + .data, for Phase 2A)
ros2 service call /slam_toolbox/serialize_map \
slam_toolbox/srv/SerializePoseGraph \
"{filename: /home/sehyeon/map/<DIR>/map}"The exact commands with the timestamped directory are printed at launch. Press Ctrl+C after saving. Landmarks are auto-saved on shutdown.
~/map/2026-04-08_14-30-25/
map.pgm # Occupancy grid image
map.yaml # map_server metadata
map.posegraph # slam_toolbox serialized map
map.data # slam_toolbox serialized data
landmarks.yaml # Text landmark positions
Launch arguments:
| Argument | Default | Description |
|---|---|---|
use_sim_time |
true |
Use simulation clock |
landmark_save_path |
~/map/<timestamp>/landmarks.yaml |
Path to save detected landmarks |
Nodes launched in Phase 1:
- Gazebo (house + signs world)
- robot_state_publisher
- slam_toolbox (async mapping)
- depth_converter (32FC1 -> 16UC1)
- navocr_node (text detection + OCR)
- textmap_node (landmark mapping)
- RViz
Autonomous text-based navigation using the map and landmarks saved in Phase 1. Two launch files are available depending on map format.
ros2 launch text_nav_sim sim_navigation.launch.py \
landmark_file:=~/map/DIR/landmarks.yaml \
map_file:=~/map/DIR/map| Argument | Default | Description |
|---|---|---|
use_sim_time |
true |
Use simulation clock |
landmark_file |
(required) | Path to landmarks.yaml from Phase 1 |
map_file |
"" |
Path to slam_toolbox serialized map (without extension) |
ros2 launch text_nav_sim sim_navigation_amcl.launch.py \
landmark_file:=~/map/DIR/landmarks.yaml \
map_yaml_file:=~/map/DIR/map.yaml| Argument | Default | Description |
|---|---|---|
use_sim_time |
true |
Use simulation clock |
landmark_file |
(required) | Path to landmarks.yaml from Phase 1 |
map_yaml_file |
(required) | Path to map.yaml from Phase 1 |
Nodes launched in Phase 2:
- Gazebo (same world)
- robot_state_publisher
- slam_toolbox (Option A) or map_server + AMCL (Option B)
- Nav2 navigation stack (controller, planner, behavior, bt_navigator, smoother, velocity_smoother, waypoint_follower)
- text_nav_bridge_node (landmark -> Nav2 goal)
- RViz
Important: Always kill previous Gazebo processes before relaunching:
killall -9 gzserver gzclientSend a text command to navigate to a landmark:
ros2 topic pub --once /text_nav/command std_msgs/msg/String "data: 'Kitchen'"text_nav_bridge matches the command against landmarks from Phase 1 and sends a Nav2 goal.
sign_tour_node.py drives the robot through all 14 signs and banners automatically.
The route avoids walls and cabinets,
and navigates through doorways. All signs are placed on the shorter wall
of their room to maximize viewing distance.
ros2 run text_nav_sim sign_tour_node.py --ros-args -p use_sim_time:=trueTour order: Exit(+Open) -> Office -> Bathroom -> Storage(+Tidy) -> Living Room -> Closet -> Kitchen(+Chef) -> Laundry -> Bedroom -> Garage(+Tools)
Sign+Ad pairs are viewed together from 2.5m+ so both fit in the camera FOV. The table, table_marble, cafe_table, and cafe_table_0 have been removed from the house model to prevent robot collision. Floor obstacle boxes (0.5x0.5x0.4m) are placed in large open rooms to provide LIDAR features for SLAM loop closure. The Dining/Living Room area uses a perimeter loop for maximum occupancy grid coverage.
Parameters:
| Parameter | Default | Description |
|---|---|---|
max_linear_vel |
0.26 | Maximum forward speed (m/s) |
max_angular_vel |
1.0 | Maximum rotation speed (rad/s) |
position_tolerance |
0.25 | Waypoint arrival threshold (m) |
heading_tolerance |
0.1 | Heading alignment threshold (rad) |
linear_kp |
0.5 | Proportional gain for linear velocity |
angular_kp |
2.0 | Proportional gain for angular velocity |
pause_scale |
1.0 | Multiplier for pause duration at each sign |
Room name signs. Black text on white background, mounted at Z=1.8m.
| Key | Display Text | Size (m) |
|---|---|---|
| exit | Exit | 0.6 x 0.25 |
| kitchen | Kitchen | 0.8 x 0.25 |
| bathroom | Bathroom | 0.8 x 0.25 |
| bedroom | Bedroom | 0.8 x 0.25 |
| office | Office | 0.8 x 0.25 |
| garage | Garage | 0.8 x 0.25 |
| closet | Closet | 0.8 x 0.25 |
| living_room | Living Room | 1.0 x 0.25 |
| laundry | Laundry | 0.8 x 0.25 |
| storage | Storage Room | 1.0 x 0.25 |
Vertical advertisement banners (0.3 x 0.6m), mounted at Z=1.9m. Low-contrast text (blended toward background color) to suppress OCR detection. Subtitles on top, title on bottom.
| Key | Title | Subtitles | Background |
|---|---|---|---|
| chef | CHEF | FRESH MEALS, DAILY SPECIALS, HOME COOKING | Red |
| tidy | TIDY | ORGANIZE, SMART STORAGE, CLEAN SPACE | Green |
| tools | TOOLS | POWER DRILL, HAND SAW, WRENCH SET | Blue |
| open | OPEN | 24 HOURS, EVERYDAY, WELCOME | Brown |
TurtleBot3 Waffle with RGB-D camera.
| Sensor | Specs |
|---|---|
| RGB Camera | 640x480, 60 FOV, 15Hz |
| Depth | 0.05-8.0m range |
| LIDAR | 360 samples, 0.12-3.5m, 5Hz |
| IMU | 200Hz 6-axis |
Camera pose: (0.069, -0.047, 1.490) from base_footprint, pitch 12 deg upward.
scripts/generate_sign_textures.py auto-generates all sign and banner textures.
cd src/text_nav_sim
python3 scripts/generate_sign_textures.pyRun this script after modifying texture settings.
Must be run before colcon build for changes to be installed.
| Parameter | Value | Description |
|---|---|---|
BANNER_TITLE_FONT_SIZE |
30 | Title font size (px) |
BANNER_SUBTITLE_FONT_SIZE |
22 | Subtitle font size (px) |
BANNER_SEPARATOR_Y_RATIO |
0.62 | Separator position (ratio from top) |
BANNER_TEXT_BLEND_RATIO |
0.35 | Text-background color blend (0=bg color, 1=white) |
Gazebo RGB-D camera outputs 32FC1 (float32, meters).
textmap expects 16UC1 (uint16, millimeters).
depth_f32_to_u16_node.py performs the conversion.
- Input:
/camera/rgbd_camera/depth/image_raw(32FC1) - Output:
/camera/depth/image_rect_raw(16UC1)
Previous gzserver is still running. Kill it before relaunching:
killall -9 gzserver gzclient; pkill -9 -f ros2If (base) conda environment is active, spawn_entity.py fails due to Python version mismatch (needs 3.10, not 3.13). Deactivate conda first:
conda deactivate