engineering-structures
How to Use Ros Navigation Stack for Dynamic Obstacle Avoidance
Table of Contents
Understanding the ROS Navigation Stack for Autonomous Robots
The Robot Operating System (ROS) Navigation Stack remains the de facto standard for enabling mobile robots to move autonomously in unknown or semi-structured environments. From warehouse floor managers using fleets of delivery carts to academic researchers testing new path-planning algorithms, the stack provides a robust framework that fuses sensor data, path planners, and motion controllers. One of its most critical capabilities is dynamic obstacle avoidance—the ability to detect and navigate around moving objects (people, other robots, vehicles) in real time without stopping or crashing. This article provides an in-depth guide to configuring the ROS Navigation Stack for dynamic obstacle avoidance, with practical parameter explanations and tuning strategies suitable for students, educators, and intermediate robotics developers.
Core Components of the Navigation Stack
Before diving into dynamic avoidance, it helps to understand the stack’s architecture and the role each component plays. The stack is built on several nodes and libraries that communicate via ROS topics. The primary orchestrator is the move_base node, which subscribes to goal topics and manages global and local planning.
- Global Planner – Computes a long‑range path from the robot’s current pose to the goal. Common implementations include NavFn and CarrotPlanner. The global planner works with a static or partially updated global costmap.
- Local Planner – Executes short‑term motion commands while respecting real‑time sensor data. The two most popular local planners for dynamic avoidance are the Dynamic Window Approach (DWA) and the Timed Elastic Band (TEB) planner. They continuously recalculate velocity commands to avoid obstacles that suddenly appear.
- Costmaps – Layered grid maps that assign occupancy probabilities and costs to cells. The global costmap is used by the global planner; the local costmap is used by the local planner. Each costmap can have multiple layers (static map, obstacle layer, inflation layer, etc.).
- Sensor Sources – Laser scanners (LIDAR), depth cameras (RGB‑D), or sonars publish data to the
/scanor/pointcloudtopics. The obstacle layer processes these messages to mark obstacles in the costmap. - Recovery Behaviors – Safety nets triggered when the robot gets stuck. They range from rotating in place to clearing costmaps and attempting replanning.
The stack’s modular design allows you to swap planners or costmap configurations without touching low‑level motor control. For dynamic obstacle avoidance, the local planner and the obstacle layer are the two most important modules to tune.
Prerequisites and Environment Setup
To follow the examples, you should have a working ROS environment (ROS Noetic or ROS2 Humble recommended) and a robot with differential drive or omnidirectional capabilities. You can test with a simulated robot in Gazebo before moving to hardware. The navigation stack itself is installed via:
sudo apt-get install ros-<distro>-navigation
We assume you have a robot URDF description, a static map of the environment (for the global costmap), and a working sensor publishing obstacle data. For dynamic avoidance, you do not need a fully static map; the stack can operate in “exploration” mode using only sensor layers, but having a floor plan improves global planning efficiency.
Configuring Dynamic Obstacle Avoidance
Dynamic obstacles introduce rapid changes in the costmap. The stack must react within milliseconds to avoid collisions. The following subsections walk through the key configuration files and parameters.
Sensor Integration and Costmap Layers
All obstacle data enters the costmap through the obstacle layer. Create a YAML file (e.g., costmap_common_params.yaml) that defines the layers and sensor settings. A minimal but effective dynamic avoidance configuration looks like this:
obstacle_range: 2.5
raytrace_range: 3.0
footprint: [[-0.2, -0.2], [-0.2, 0.2], [0.2, 0.2], [0.2, -0.2]]
inflation_radius: 0.55
observation_sources: scan
scan: {sensor_frame: laser, data_type: LaserScan, topic: /scan, marking: true, clearing: true}
Key parameters for dynamic environments:
- obstacle_range – Maximum distance (in meters) at which sensor readings are considered to add obstacle information. Lowering it reduces map noise but may miss obstacles far away.
- raytrace_range – Distance used to clear free space (sonar model). A larger value helps remove ghost obstacles but can eat CPU cycles.
- inflation_radius – Increases cost around obstacles. For dynamic objects, keep it between 0.4–0.7 m to leave a safety margin without making the robot too timid.
- marking and clearing – Both should be
truefor the primary sensor so that obstacles appear and disappear as they move through the laser field of view.
If you use multiple sensors (e.g., LIDAR + depth camera), add each as a separate observation source. Use the observation_persistence parameter sparingly: for dynamic obstacles, you want them to clear quickly instead of lingering in the costmap.
Local Planner Selection and Configuration
The local planner receives the global plan and sensor updates and outputs Twist commands. Two strong choices for dynamic avoidance are DWA and TEB.
DWA (Dynamic Window Approach) – Uses velocity search space sampling: it simulates thousands of trajectory candidates for a short time horizon (sim_time) and picks the best one that avoids obstacles while moving towards the goal. Critical parameters:
sim_time: 1.5–2.5 seconds. Longer horizons cause slow reactions; shorter ones may be jerky.xy_goal_tolerance: 0.10–0.25 m. Smaller values force the robot to be precise, but may cause oscillation near dynamic goals.yaw_goal_tolerance: 0.1–0.5 rad.max_vel_xandmax_vel_theta: Set conservatively on hardware. For dynamic avoidance, lower speeds give more reaction time.sim_granularity: 0.025 m. Finer simulation steps produce smoother movements but increase computational load.
TEB (Timed Elastic Band) – Optimizes a trajectory as an elastic band subject to obstacle constraints. It natively handles dynamic obstacles better than DWA because it explicitly models time and leverages nonlinear optimization. Key parameters include:
min_obstacle_dist: 0.2–0.4 m. The minimum distance the robot must maintain from obstacles.include_costmap_obstacles: true. Integrates costmap information directly.global_plan_viapoint_sep: 0.5–1.0 m. Closer spacing helps the local planner follow turns tightly around moving obstacles.weight_max_vel_x: 1.0–5.0. Adjust to prioritize speed over obstacle clearance.
For both planners, enable dynamic replanning by setting make_plan to true and planner_frequency to 1–2 Hz. This forces the global planner to recalculate if the local planner drifts too far from the original path.
Recovery Behaviors for Dynamic Environments
When a moving obstacle blocks the robot’s entire path (e.g., a person standing still for several seconds), the robot should not simply sit there. The move_base node triggers recovery behaviors in sequence. A typical set:
- Clear costmap – Removes obstacle markings that may be stale (sensor noise).
- Rotate recovery – Rotates 360° to scan the environment and update the costmap.
- Move backwards – A short reverse motion to free the robot from tight spots.
For dynamic avoidance, you may want to reduce the timeout before recovery triggers (parameter recovery_behavior_enabled: true). Use clearing_rotation_allowed to allow the robot to turn when blocked. Some teams even implement a custom recovery that sends a slow forward “nudge” while updating the costmap at high frequency.
Tuning Parameters for Real‑World Performance
No two dynamic environments are identical. The following tuning strategies help adapt the stack to your specific scenario. Always test in simulation first, then on the real robot with safety limits enforced.
Sensor Update Rates and Fusion
A LIDAR spinning at 10 Hz provides a new scan every 100 ms. For a robot moving at 0.5 m/s, the obstacle position shifts about 5 cm between scans—acceptable for most planners. If you use a depth camera at 30 Hz, you gain faster updates but may introduce noise. Isolate the sensor driver from CPU‑heavy processes. Use the observation_keep_time parameter in the obstacle layer to control how long an obstacle point stays in the costmap before being cleared. For highly dynamic environments, set observation_keep_time to 0.5–1.0 seconds.
If you fuse multiple sensor sources, ensure their coordinate frames are correctly published in the TF tree. Misaligned frames produce false obstacles. Validate using rviz and the “PointCloud2” display.
Costmap Inflation and Clearing
Inflation adds a cost gradient around obstacles, encouraging the planner to keep distance. The inflation_radius and cost_scaling_factor work together; a factor of 10 creates a steep gradient, while 3–5 gives a gentler slope. For dynamic obstacles, a steeper gradient (higher cost_scaling_factor) reduces the chance of the robot cutting close to a moving object.
Clearing is equally important. The raytrace algorithm in the obstacle layer removes free space that was previously marked as occupied. If your sensor suffers from specular reflections (e.g., glass), you may see obstacles appear and disappear. Increase raytrace_range to 4–5 m to force more aggressive clearing, or add a “voxel layer” with 3D ray tracing for better handling of overhanging obstacles.
Local Planner Weight Adjustments
Most local planners expose multiple criteria weights. For DWA:
- pdist_scale – Weight for staying close to the global path. Lowering this gives the robot more freedom to swerve around dynamic obstacles.
- gdist_scale – Weight for making progress toward the goal. Keep high to avoid aimless wandering.
- occdist_scale – Weight for obstacle avoidance. Increase to prioritize safety over path following.
A common tuning sequence: start with pdist_scale = 0.5, gdist_scale = 0.6, occdist_scale = 0.1, then increase occdist_scale by 0.05 until the robot stops brushing against obstacles. For TEB, adjust weight_obstacle (typically around 100–500) and weight_dynamic_obstacle (if using the teb_local_planner with dynamic obstacle support from messages).
Testing and Validation
Use a staged environment first: place a single moving obstacle (e.g., a person walking at 0.5 m/s perpendicular to the robot’s path). Observe the robot’s reaction in rviz. The local costmap should show the obstacle appearing and clearing quickly. The planned trajectory should deviate smoothly.
Common metrics to measure:
- Time to re‑plan – Should be under 200 ms.
- Minimum clearance – Ensure the robot never passes closer than the inflation radius.
- Goal achievement success rate – Over 20 runs with random obstacle speeds, the robot should reach its goal at least 90% of the time.
If you experience failures, record bag files with rosbag record -a and replay them while tuning parameters offline. This decouples tuning from hardware constraints.
Common Pitfalls and Troubleshooting
- Obstacles linger in the costmap – Check that
clearingis enabled for all observation sources. Reduceobservation_keep_time. - Robot oscillates near obstacles – Increase
xy_goal_toleranceor reducepdist_scalein DWA. For TEB, increasemin_obstacle_dist. - CPU overload – Lower the update frequency of the local costmap (
update_frequencyfrom 5 Hz to 2 Hz during dynamic segments) or reduce the simulation horizon. - Inconsistent global plan – Ensure the static map is accurately aligned with the real world. Dynamic replanning with a high
planner_frequencyhelps compensate for map errors. - Robot reverses unnecessarily – Review recovery behaviors. Disable the “move_backwards” behavior if the robot can safely rotate instead.
Expanding with Custom Dynamic Detection
The stack’s built‑in obstacle layer treats all moving objects as static obstacles that appear and disappear each cycle. If you want predictive avoidance (e.g., estimate velocity of a person and intercept), you need to publish custom obstacle messages with velocity information. The costmap_2d does not inherently track velocities, but you can write a node that subscribes to /scan, runs a clustering algorithm (like DBSCAN), estimates speed, and publishes filtered obstacle arrays. The teb_local_planner supports dynamic obstacles via the /move_base/TebLocalPlannerROS/obstacles topic. Alternatively, use the social navigation layers found in packages like spatio_temporal_voxel_layer for 4D costmaps (space+time).
For a hands‑on tutorial, refer to the ROS Navigation Tutorials and the DWA local planner documentation for parameter descriptions. If you are transitioning to ROS2, the Nav2 project offers similar concepts with a redesigned API and better lifecycle management.
Conclusion
Dynamic obstacle avoidance with the ROS Navigation Stack is achievable through careful sensor integration, costmap layer tuning, and local planner selection. Start with a simple LIDAR source and DWA planner, then gradually introduce multiple sensors and advanced planners like TEB as your robot’s environment complexity increases. The stack’s parametric design allows you to iterate quickly in simulation, then validate on the real robot using the same configuration files. With the guidelines presented here, you can configure a robot to share spaces with moving objects safely—a foundational skill for continuous autonomous operation in real‑world applications such as hospital logistics, warehouse robotics, and collaborative manufacturing.