Post

TF Configuration: Sensor Frames and TF Setup for Cartographer

TF Configuration: Sensor Frames and TF Setup for Cartographer

One of the first problems you hit when developing a robot is setting up sensor coordinate frames. To fuse LiDAR, IMU, and camera data, you must tell the system exactly where and in what orientation each sensor is mounted on the robot. This post covers how to set up sensor frames on the RoboRacer platform with ROS’s TF (Transform) system.

What Is TF?

TF is the ROS system that manages transforms between different coordinate frames. It defines spatial relationships — “the LiDAR is 30 cm forward of the robot center,” “the IMU is 6 cm to the left” — in a tree structure, and every frame must have exactly one parent.

The Full TF Tree

The tree we’ll build looks like this.

The sensor-frame TF tree (map → base_link → each sensor)

  • map → base_link: computed in real time by localization (out of scope here)
  • base_link → sensor: the mounting position is fixed, so define it as a static transform (the topic of this post)

base_link — the ground point at the center of the rear axle

base_link is defined as the ground point at the center of the rear axle. An Ackermann-steering vehicle’s turning center is at the rear axle, which simplifies kinematics, and placing it at $z=0$ (the ground) keeps alignment with 2D mapping clean.

base_link → laser transform (translation only, rotation 0)

Measure and enter only the translation, and set rotation to 0. Instead, get the LiDAR as level as possible in hardware via roll·pitch.

The IMU is connected in two steps, not one.

1
2
base_link → vesc_imu_rot   (translation only)
vesc_imu_rot → vesc_imu    (rotation only)

This split is because of two Cartographer constraints.

1. The tracking_frame constraint. To use the IMU in Cartographer, the tracking_frame must be the same as the IMU frame, or a frame that differs from the IMU frame only by rotation.

2. The gravity-initialization problem. If the IMU is mounted upside down and you use its frame directly as tracking_frame, the initial gravity-alignment step flips the entire map upside down.

So we create an intermediate frame vesc_imu_rot that has no translation offset from the IMU but is axis-aligned with base_link, and hand that to Cartographer. All of the mounting-orientation rotation is pushed into vesc_imu_rot → vesc_imu.

1
2
-- mapping_2d.lua
tracking_frame = "vesc_imu_rot",  -- aligned frame with no translation from the IMU

Configuration Example

The frame of the IMU mounted on the VESC

The IMU mounted on the VESC has the frame shown above.

static_transforms configuration example

Config file: stack_master/config/CAR/static_transforms.yaml

1
2
3
4
5
vesc_imu_rot_to_vesc_imu:
  parent: vesc_imu_rot
  child: vesc_imu
  xyz: [0.0, 0.0, 0.0]
  rpy: [0.0, 0.0, 1.5707963]   # choose per mounting orientation (see table below)

You only need to change the rpy value according to the VESC mounting orientation.

Mountingrpy
a[0.0, 0.0, 1.5707963] (+90°)
b[0.0, 0.0, 0.0]
c[0.0, 0.0, 3.1415927] (180°)
d[0.0, 0.0, -1.5707963] (-90°)

Wrap-up

There are three key points.

  1. Define the reference frame (base_link) at a physically clear point.
  2. For the IMU with Cartographer, use an intermediate frame (vesc_imu_rot) that separates translation and rotation as the tracking_frame.
  3. If you don’t use the IMU in Cartographer, connecting base_link directly to the IMU is fine.

For basic concepts and how to measure, also see Sensor TF Setup Guide Based on base_link.

References

This post is licensed under CC BY 4.0 by the author.