Robot Calibration for ROS

This is day 5 of my National Robotics Week blog marathon. See the full set of posts here.

Uncalibrated vs. Calibrated

Calibration is an essential step in most robotics applications. Robots have many things that need to be calibrated:

  • Camera intrinsics - basically determining the parameters for the pinhole camera model. On some RGBD (3d) cameras, this also involves estimating other parameters using in their projections. This is usually handled by exporting YAML files that are loaded by the drivers and broadcast on the device's camera_info topic.
  • Camera extrinsics - where the camera is located. This often involves updating the URDF to properly place the camera frame.
  • Joint offsets - small errors in the zero position of a joint can cause huge displacement in where the arm actually ends up. This is usually handled by a "calibration_rising" flag in the URDF.
  • Wheel rollout - for good odometry, you need to know how far you really have travelled. If your wheels wear down over time, that has to be taken into account.
  • Track width - on differential-drive robots, the distance between in your drive wheels is an essential value to know for good odometry when turning.
  • IMU - when fusing wheel-based odometry with a gyro, you want to make sure that the scale of the gyro values is correct. The gyro bias is usually estimated online by the drivers, rather than given a one-time calibration. Magnetometers in an IMU also need to be calibrated.
My robot_calibration package can do all of these except wheel rollout and magnetometer calibration (although the magnetometer calibration will be coming soon).

Evolution of ROS Calibration Packages
There are actually quite a few packages out there for calibrating ROS-based robots. The first package was probably pr2_calibration developed at Willow Garage. While the code inside this package isn't all that well documented, there is a paper describing the details of how it works: Calibrating a multi-arm multi-sensor robot: A Bundle Adjustment Approach.

In basic terms pr2_calibration works by putting checkerboards in the robots grippers, moving the arms and head to a large number of poses, and then estimating various offsets which minimize the reprojection errors through the two different chains (we can estimate where the checkerboard points are through its connection with the arm versus what the camera sees). Nearly all of the available calibration packages today rely on similar strategies.

One of the earliest robot-agnostic packages would be calibration. One of my last projects at Willow Garage before joining their hardware development team was to make pr2_calibration generic, the result of this effort is the calibration package. The downside of both this package and pr2_calibration is that they are horribly slow. For the PR2, we needed many, many samples - getting all those samples often took 25 minutes or more. The optimizer that ran over the data was also slow - adding another 20 minutes. Sadly, even after 45 minutes, the calibration failed quite often. At the peak of Willow Garage, when we often had 20+ summer interns in addition to our large staff, typically only 2-3 of our 10 robots were calibrated well enough to actually use for grasping.

After Willow Garage, I tried a new approach using Google's Ceres solver to rebuild a new calibration system. The result was the open source robot_calibration package. This package is used today on the Fetch robot and others.

What robot_calibration Can Do
The robot_calibration package is really intended be an all-inclusive calibration system. Currently, it mainly supports 3d sensors. It does take some time to setup for each robot since the system is so broad - I'm hoping to eventually create a wizard/GUI like the MoveIt Setup Assistant to handle this.

There are two basic steps to calibrating any robot with robot_calibration: first we capture a set of data which mainly includes point clouds from our sensors, joint_states data of our robot pose, and some TF data. Then we do the actual calibration step by running that data through our optimizer to generate corrections to our URDF, and also possibly our camera intrinsics.


One of the major benefits of the system is the reliability and speed. On the Fetch robot, we only needed to capture 100 poses of the arm/head to calibrate the system. This takes only 8 minutes, and the calibration step typically takes less than a minute. One of my interns, Niharika Arora, ran a series of experiments in which we reduce the number of poses down to 25, meaning that capture and calibration took only three minutes - with a less than 1% failure rate. Niharika gave a talk on robot_calibration at ROSCon 2016 and you see the video here. We also put together a paper (that was sadly not accepted to ICRA 2016) which contains more details on those experiments and how the system works [PDF].

In addition to the standard checkerboard targets, robot_calibration also works with LED-based calibration targets. The four LEDs in the gripper flash a pattern allowing the robot to automatically register the location of the gripper:

LED-based calibration target.

One of the coolest features of robot_calibration is that it is very accurate at determining joint zero angles. Because of this, we did not need fancy jigs or precision machined endstops to set the zero positions of the arm. Technicians can just eye the zero angle and then let calibration do the rest.

There is quite a bit of documentation in the README for robot_calibration.

Alternatives
I fully realize that robot_calibration isn't for everyone. If you've got an industrial arm that requires no calibration and just want to align a single sensor to it, there are probably simpler options.

Blast From the Past: UBR-1

This is day 4 of my National Robotics Week blog marathon - it’s halfway over!

In 2013, Unbounded Robotics was the last spin-off from Willow Garage. Our four person team set out to build a new robotics platform for the research and development community. The robot would cost a fraction of what the Willow Garage PR2 cost. We did build three robots and demo them at a number of events, but Unbounded eventually ran out of money and was unable to secure further funding. In the summer of 2014 the company shut down.

I wasn’t really blogging during this whole time because I was really busy when things were going well, and then I didn’t really want to talk about it while things were going downhill. The whole affair is now quite a few years ago, so here we go. First, a picture of our little robot friend:

UBR-1 by Unbounded Robotics

The robot had a differential drive base, since that is really the only cost-effective style of robot base out there. It used an interesting 10:1 worm-gear brushless motor, similar to what had been in the PlatformBot our team had previously designed at Willow Garage. The brushless motors were really quite efficient and the whole drive was super quiet, but the worm gear was terribly inefficient (more on that below). The base was about 20” in diameter - which made the robot much more maneuverable than the 26” square footprint of the PR2, even though PR2 had holonomic drive.

The 7-DOF arm had similar kinematics to the PR2 robot, but tossed the counterbalancing for simplicity, lower cost and weight reduction. A parallel jaw gripper replaced the very complex gripper used in the PR2. A torso lift motor allowed the robot to remain quite short during navigation but rise to interact with items at table height - it was a little short for typical counter-top heights but still had a decent workspace if the items weren’t too far back on the countertop.

One of the first demos I set up on the UBR-1 was my chess playing demo:

UBR-1 Software

As with everything that came out of Willow Garage, UBR-1 used the Robot Operating System (ROS). You can still download the preview repository for the UBR-1 which included simulation with navigation and grasping demos. As with the ROS legacy of Willow Garage, the open source software released by Unbounded Robotics continues to live on to this day. My simple_grasping package is an improved and robot-agnostic version of some of the grasping demos we created at Unbounded (which were actually based on some of my earlier grasping demos created for my Maxwell robot). A number of improvements and bug fixes for ROS Navigation and other packages also came out during this time since I was a primary maintainer of these packages in the dark days following Willow’s demise.

UBR-1 in Gazebo Simulation
Power Usage and Battery Life

Power usage reductions and battery life increases were some of the biggest improvements in the UBR-1 versus the PR2. The PR2 was designed in 2010 and used two computers, each with 24Gb of RAM and 2 quad-core Intel L5520 Nehalem processors. The Nehalem was the first generation of Intel Core processors. The PR2 batteries were specified to have 1.3kWh of stored energy, but only gave about two hours of runtime, regardless of whether the robot was even doing anything with the motors. There were other culprits besides the computers, in particular, the 33 motor controller boards each had two ethernet PHYs accounting about 60W of power draw. But the computers were the main power draw. This was made worse by the computers being powered by isolated DC-DC converters that were only about 70% efficient.

The UBR-1 arm.

The UBR-1 used a 4th generation Intel Core processor. The gains in just four years were quite impressive: the computer drew only 30-35W of power, but we were able to run similar demos of navigation and manipulation that ran on the PR2. Based on the Intel documentation, a large part of that was the 75% power reduction for the same performance from the first to fourth generation chips. A smaller contributor was improvements in the underlying algorithms and code base.

For dynamic movements, the UBR-1 arm was also significantly more power efficient than the PR2, since it weighed less than half as much. Gravity compensation of the 25 lb arm required only about 15W in the worst configurations - this could have been lowered with higher gearing, but would have had adverse effects on the efficiency when moving and might have jeopardized the back-drivability.

The base motors were still highly inefficient - UBR-1 needed about 250W to drive around on carpet. Hub motors have become pretty common in the years since and can improve the efficiency of the drivetrain from a measly 35% to upwards of 85%.

Robot Evolution

Processors have gotten more efficient in the years since UBR-1 - but their prices have pretty much stopped dropping. Motors haven’t really gotten any cheaper since the days of the PR2, although the controls have gotten more sophisticated. Sensors also really haven’t gotten much cheaper or better since the introduction of the Microsoft Kinect. While there has been a lot of money flowing into robotics over the past decade, we haven’t seen the predicted massive uptake in robots. Why is that?

One of my theories around robotics is that we go through a repeated cycle of “hardware limited” and “software limited” phases. A hardware innovation allows an increased capability and it takes some time for the software to catch up. At some point the software innovation has increased to a point where the hardware is now the limiting factor. New hardware innovations come out to address this, and the process repeats.

Before ROS, we were definitely software-limited. There were a number of fairly mechanically sophisticated robots in research labs all over the planet, but there were not widely-accepted common frameworks with which to share software. PhD students would re-invent the wheel for the first several years of their work before contributing something new, but then have no way to pass that new innovative code on. ROS changed this significantly.

On the hardware side, there were very few common platforms before the PR2. Even the Mobile Robots Pioneer wasn’t exactly a “common platform” because everyone installed different sensors on them, so code wasn’t very portable. The introduction of PR2 going to a number of top universities, combined with the Willow Garage intern program, really kickstarted the use of a common platform. The introduction of the Microsoft Kinect and the advent of low-cost depth sensors also triggered a huge leap forward in robot capability. I found it amusing at the time to see the several thousand dollar stereo camera suite on the PR2 pretty much replaced (and outperformed) by a single $150 Kinect.

For a few years there was a huge explosion in the software being passed around, and then we were hardware-limited again because the PR2 was too expensive for wide adoption. While the UBR-1 never got to fill that void, there are now a number of lower-cost platforms available with pretty solid capabilities. We’re back to software-limited.

So why are robots still software limited? The world is a challenging environment. The open source robotics community has made great strides in motion planning, motor control, and navigation. But perception is still really hard. Today we’re mainly seeing commercially deployed robots making inroads in industries where the environment is pretty well defined - warehouses and office spaces, for instance. In these environments we can generally get away with just knowing that an “obstacle” is somewhere - our robot doesn’t really care what the obstacle is. We’ve got pretty good sensors - although they’re still a little pricey - but we generally lack the software to really leverage them. Even in the ROS ecosystem, there’s huge amounts of hardware drivers and motion planning software (ROS Navigation, MoveIt, OMPL, SBPL, the list goes on), but very little perception code. Perception is still very dependent on the exact task you’re doing, there just isn’t a lot of “generic” perception out there.

There is a certain magic in finding applications where robots can offer enough value to the customer at the right price point. Today, those tend to be applications where the robot needs limited understanding of the environment. I look forward to what the applications of tomorrow might be.

Code Coverage for ROS

This is day 3 of my 2020 National Robotics Week blog marathon!

About two years ago I created a little package called code_coverage. This package is a bit of CMake which makes it easier to run coverage testing on your ROS packages. Initially it only supported C++, but recently it has been expanded to cover Python code as well.

What is Code Coverage?

Before I get into how to use the code_coverage package, let’s discuss what coverage testing is all about. We all know it is important to have tests for your code so that it does not break as you implement new features and inevitably refactor code. Coverage testing tells you what parts of your code your tests actually test. This can help you find branch paths or even entire modules of the code that are not properly tested. It can also help you know if new code is actually getting tested.

The output of a coverage test is generally some really nice webpages that show you line-by-line what code is getting executed during the test:

Using code_coverage for C++

We will start by discussing the usage of code_coverage with C++ code first, because it is actually quite a bit simpler. C++ coverage can be done almost entirely in CMake.

First, update your package.xml to have a test_depend on code_coverage package.

Next, we need to update two places in the CMakeLists.txt file. The first change should be right after you call to catkin_package. The second change is where you define your test targets. You need to define a new target, which we will typically call {package_name}_coverage_report

# After catkin_package()

if(CATKIN_ENABLE_TESTING AND ENABLE_COVERAGE_TESTING)
  find_package(code_coverage REQUIRED)
  # Add compiler flags for coverage instrumentation before defining any targets
  APPEND_COVERAGE_COMPILER_FLAGS()
endif()

# Add your targets here

if (CATKIN_ENABLE_TESTING)
  # Add your tests here

  # Create a target ${PROJECT_NAME}_coverage_report
  if(ENABLE_COVERAGE_TESTING)
    set(COVERAGE_EXCLUDES "*/${PROJECT_NAME}/test*" "*/${PROJECT_NAME}/other_dir_i_dont_care_about*")
    add_code_coverage(
      NAME ${PROJECT_NAME}_coverage_report
      DEPENDENCIES tests
    )
  endif()
endif()

That’s the configuration needed. Now we can compile the code (with coverage turned on) and run the coverage report (which in turn will run the tests):

catkin_make -DENABLE_COVERAGE_TESTING=ON -DCMAKE_BUILD_TYPE=Debug PACKAGE_NAME_coverage_report

You can find these same instructions (and how to use catkin tools) in the code_coverage README.

Using code_coverage for Python

Python unit tests will automatically get coverage turned on just with the CMake configuration shown above, but Python-based rostests (those that are launched in a launch file) need some extra configuration.

First, we need to turn on coverage testing in each node using the launch-prefix. You can decide on a node-by-node basis which nodes should actually generate coverage information:

<launch>

    <!-- Add an argument to the launch file to turn on coverage -->
    <arg name="coverage" default="false"/>

    <!-- This fancy line forces nodes to generate coverage -->
    <arg name="pythontest_launch_prefix" value="$(eval 'python-coverage run -p' if arg('coverage') else '')"/>

    <!-- This node will NOT generate coverage information -->
    <node pkg="example_pkg" name="publisher_node" type="publisher_node.py" />

    <!-- But this node WILL generate coverage -->
    <node pkg="example_pkg" name="subscriber_node" type="subscriber_node.py"
          launch-prefix="$(arg pythontest_launch_prefix)" />

    <!-- The test can also generate coverage information if you include the launch-prefix -->
    <test time-limit="10" test-name="sample_rostest" pkg="example_pkg" type="sample_rostest.py"
          launch-prefix="$(arg pythontest_launch_prefix)" />

</launch>

Then we turn on coverage by adding the argument in our CMakeLists.txt:

add_rostest(example_rostest.test ARGS coverage:=ENABLE_COVERAGE_TESTING)

You can find this full Python example from my co-worker Survy Vaish on GitHub.

Using codecov.io For Visualization

codecov.io is a cloud-based solution for visualizing the output of your coverage testing. It can combine all of the reports from individual packages, as well as the C++ and Python reports into some nice graphs and track results over multiple commits:

codecov.io dashboard for robot_calibration
A Full Working Example

The robot_calibration package use code_coverage, codecov.io, and Travis-CI to run code coverage testing on every pull request and commit to master branch. It uses the popular industrial-ci package as the base line and then the following changes are made:

  • I set the CMAKE_ARGS in the travis.yml so that coverage is turned on, and the build type is debug.
  • I created a .coverage.sh script which runs as the AFTER_SCRIPT in Industrial-CI. This script runs the coverage report target and then calls the codecov.io bash uploader.
  • Since Industrial-CI runs in a docker, I introduced a .codecov.sh script which exports the required environment variables into the docker. This uses the env script from codecov.io.