-
Notifications
You must be signed in to change notification settings - Fork 3
Using TF2, a Practical Guide
TF2 is a ROS transformation library that is very popular and incredibly useful to robot programmers. The official ROS wiki has a series of tutorials on how to use the library, but I think it is pretty hard to translate those tutorials into getting something working on your robot. With that in mind I wanted to create a brief practical guide to TF2.
To understand why one would use TF2, you have to understand what transformations and reference frames are. A reference frame is just a point of view, represented by a coordinate frame. For example a human head is one reference frame ("frame" for short). When I see something with my eyes I generally can tell its distance and position relative to my head. My right hand is another frame, objects are different distances from my head and my hand, but my brain can figure out the difference and use my hands to manipulate objects I can see with my head. This is the job of TF2: to define these frames, and transform positions and directions between them. This is very useful because much like humans, robots have many moving parts that all need to work together to accomplish tasks.
An important thing to note is that frames exist in a hierarchy. Frames are positioned relative to their parent frame, and this is how they're defined. For example human eyes can be thought of as being 0.1 meters away from the brain. Under this definition the eyes are a child frame of the brain, because their position is defined in terms of the position of the brain. You can convert between frames regardless of where they are in the hierarchy; it's just a method for defining them.
Before we jump in, some disclaimers:
- This is all Python code for ROS Melodic.
- You should know a good bit about ROS and Python before reading this.
- Any variable names can be changed.
Now lets get into the code. How do we setup and manage these frames using TF2? Well if we're going to be creating frames, we need a way to broadcast them to other nodes. A TF2 TransformBroadcaster is the perfect tool for this. One can be initialized like this:
coordinate_frame_broadcaster = tf2_ros.TransformBroadcaster()This only has to be made once per node, and only for nodes that want to make or change frames. Now that we can broadcast, let's make a frame.
Your first frame(s) must be in terms of the "world" frame, which is just a frame that exists at (0, 0) and never moves. The location of (0, 0) may change depending on your use case. In some robots it could be the center of the bot, but in this example it's the position at which the robot was first switched on. This hypothetical robot can keep track of exactly how far it has gone from that starting point. Let's define a frame called "bot" which just sits at the center of the robot and points in the direction the robot is facing.
transform = TransformStamped()
transform.header.stamp = rospy.Time.now()
transform.header.frame_id = "world"
transform.child_frame_id = "bot"
transform.transform.translation.x = robot_x
transform.transform.translation.y = robot_y
transform.transform.translation.z = robot_z
q = tf_conversions.transformations.quaternion_from_euler(current_pitch, current_roll, current_yaw)
transform.transform.rotation.x = q[0]
transform.transform.rotation.y = q[1]
transform.transform.rotation.z = q[2]
transform.transform.rotation.w = q[3]
coordinate_frame_broadcaster.sendTransform(transform)Let's break this down:
transform = TransformStamped()
transform.header.stamp = rospy.Time.now()
transform.header.frame_id = "world"
transform.child_frame_id = "bot"A transforms header tells it what frames it belongs to and when it was created. The stamp is the time, which will almost always be rospy.Time.now(). transform.header.frame_id defines what the parent frame is, and transform.child_frame_id defines what the child frame is.
transform.transform.translation.x = robot_x
transform.transform.translation.y = robot_y
transform.transform.translation.z = robot_ztransform.transform.translation.x y and z are pretty self explanatory, they define how far has the child frame moved in each direction from the parent.
q = tf_conversions.transformations.quaternion_from_euler(current_pitch, current_roll, current_yaw)
transform.transform.rotation.x = q[0]
transform.transform.rotation.y = q[1]
transform.transform.rotation.z = q[2]
transform.transform.rotation.w = q[3]transform.transform.rotation.x y z w are similar but define how much the child frame has rotated from the parent using a quaternion. Quaternions are complicated and usually its easier to think of things using euler angles, so you can use the helpful function tf_conversions.transformations.quaternion_from_euler to convert between them.
coordinate_frame_broadcaster.sendTransform(transform)Finally you can broadcast the frames using the line above.
Let's say you know that an important object is (2, 2, 0) away from your starting position, but you've moved far from your starting position and you want to find out where (2, 2, 0) is from your current robot position. How do we use the frame system setup earlier to get this answer?
Let's start by setting up a listener to get transformations between frames.
tf_buffer = tf2_ros.Buffer()
listener = tf2_ros.TransformListener(tf_buffer)this should only be done once per node.
Now we need something to actually transform. In this case it will be a 3D Vector, the one we talked about earlier: (2, 2, 0). When using TF2 make sure your geometry messages are all stamped and coming from the tf2_geometry_msgs package.
from tf2_geometry_msgs import Vector3Stamped
world_goal = Vector3Stamped()
world_goal.vector.x = 2
world_goal.vector.y = 2
world_goal.vector.z = 0
world_goal.header.frame_id = "world"This is all pretty self explanatory, remember to set frame_id to whatever frame the vector is currently defined in.
Now let's transform the vector.
try:
bot_goal = tf_buffer.transform(world_goal, 'bot')
except (tf2_ros.LookupException,
tf2_ros.ConnectivityException,
tf2_ros.ExtrapolationException):
passThis should be done in the node's main loop, so a try-catch is ideal. tf_buffer.transform() takes a stamped TF2 geometry message, and transforms it into whatever frame is specified in the second parameter.
Now bot_goal is a new vector from the robot to (2,2,0) in world coordinates. Hopefully this guide has given you some insight on how to use the TF2 library.