Autonomous Driving using ROS for Beginners

Sasha Melkonyan
6 min readApr 13, 2019

--

For 6 months I was a part of a team that developed autonomous navigation software for Roboy, a humanoid robot, to ride a tricycle. We used ROS, an industry standard framework for robotics. ROS comes with lots of components that enable you to do many typical robotics tasks out of the box. Autonomous driving software is implemented in a package called ROS Navigation Stack. It comes with many tutorials describing how to configure the package and deploy it on your robot. However, (1) it doesn’t provide a fully-functioning example (2) that doesn’t require actual hardware. In this article, I’d like to present a plug-and-play example of an autonomously driving tricycle in a simulation and use it to describe high-level concepts of autonomous navigation systems and how they are implemented in ROS Navigation Stack. It’s oriented towards beginners, although I assume that you’re familiar with the basic ROS concepts like topics, nodes, launch files, and tf tree. If any of those sounds unfamiliar, consult the ROS tutorials first.

Setup

Before we dive in I’d recommend running the demo code. The setup process should take no more than a couple of minutes.

  • Install ROS. The version you need depends on your Ubuntu version. E.g. if you have Ubuntu 14.10, you can use the following commands:
# Setup your computer to accept software from packages.ros.org.
sudo sh -c ‘echo “deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main” > /etc/apt/sources.list.d/ros-latest.list’
# Set up your keys
sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
# Update your package index
sudo apt-get update
# Install ROS
sudo apt-get install ros-kinetic-desktop-full
source /opt/ros/kinetic/setup.bash
  • Install required ROS libraries
sudo apt-get install ros-kinetic-navigation ros-kinetic-gazebo-ros-control
mkdir ~/catkin_wscd ~/catkin_ws/git clone --recursive https://github.com/melkonyan/ros_navigation_tutorial.git srccatkin_make
source devel/setup.bash

If no errors appeared, you should be able to start a demo by running

roslaunch roslaunch navigation demo.launch

Now let’s take a look under the hood…

Autonomous Navigation

Let’s begin by asking ourselves a question: “what is the bare minimum set of requirements a robot should fulfill to autonomously navigate and drive in an environment?”

Here’s a good candidate set:

  • First of all, we need a map of the environment, telling us where we can go and where not. In the ROS universe its called a costmap. A 2D costmap divides the world’s surface into small squared patches called cells. Each cell is marked as either free or an obstacle.
  • Then, we need to figure out where our robot is on the map. The process of doing so is called localization.
  • Once we know the position of the robot and the place we want to reach, called the goal, we can compute a path connecting the two. This path is also called a trajectory and is represented by an array of points on the map. A valid trajectory has to avoid obstacles and be feasible for the robot to follow.
  • Once the trajectory is computed, we can start issuing velocity commands to the robot, like go forward at 1 m/s or turn left at 10°/s.
  • We have to convert velocity commands to commands for the robot motors.
  • After the robot starts moving, we have to constantly adjust its movement once it deviates from the reference trajectory or if dynamic obstacles appear in its way.

ROS Navigation Stack

ROS Navigation Stack is a package for ROS that implements many of the requirements we discussed above. It contains a global planner that generates trajectories connecting points on the map of the environment, called a global costmap. A local planner issues velocity commands that will follow the specified trajectory and avoid dynamic obstacles. It listens to sensor data, detects those dynamic obstacles and puts them on a smaller map, centered around the robot, called a local costmap.

This figure shows the whole computation pipeline when using ROS Navigation Stack in a simulated environment. It can be logically split into two parts. The static part contains computations that have to be performed only once in a while. For example, we compute a new trajectory only when a new goal is set. The dynamic part, however, has to be executed as frequently as hardware allows.

All of the Navigation Stack components can and should be configured for a particular robot. Config files used in our example are located in navigation/config directory.

Simulation

In order to test navigation software, we need the robot to react to the commands and update its position in the world. If a physical robot is not available, we can use a simulation engine to approximate the robot’s interactions with the environment. We use Gazebo, a standard simulation engine for ROS. Again, let’s sketch a list of things we need to do to simulate a robot:

  • Describe the physical properties of the robot
  • Forward velocity commands to the robot in simulation
  • Read out the updated position from the simulation

The last two are pretty straightforward because Gazebo provides plug-ins for ROS that make interacting with the robot in simulation as easy as reading and publishing ROS topics.

Describing the robot boils down to specifying the physical properties (mass, inertia, geometric shape, friction coefficients) of individual parts as well as how these parts are connected to each other. For that ROS uses an xml-based language, called urdf and provides a set of tutorials on how to define simple robots.

An example of our tricycle simulated in Gazebo

Are we done?

The setup described above is enough to get the robot running. However, for some robots including our autonomous tricycle, results would be unsatisfactory. This is because ROS Navigation Stack was designed for the so-called holonomic robots, i.e. robots that can move in every direction and perform in-place rotations. A tricycle, on the other hand, can only turn by following a circle of a certain radius, called turning radius. To take into account the motion constraints of the tricycle, we developed a custom global planner.

The difference between the standard planner (on the top/left) and our custom planner (on the bottom/right).

Another problem is that velocity commands issued by the local planner should be interpreted as the linear and angular velocity of the robot. We cannot directly control the angular velocity of a tricycle. Instead, we compute a steering angle that will achieve the commanded angular velocity. For example, for our tricycle to turn at 20°/s while driving 1 m/s we need to turn the steering bar at 30°. We used a PID controller to achieve the steering angle, which is implemented insimulation/tricycle_controller.py.

Results and limitations

The setup described above was successfully used to navigate a real tricycle in a building.

However, there are a number of limitations:

  1. The absence of higher-level decision making, like detecting traffic signs of lanes.
  2. Scalability. First of all, our planning algorithm grows quadratically with the size of the map of the environment and becomes unpractical once the navigation area exceeds couple hundred of meters. Moreover, representing such a big environment using a high-resolution grid map is also impractical from a memory usage standpoint.

So stay tuned for future updates!

 by the author.

--

--