Trong bài toán định vị (localization) ở robot, chúng ta cần sự trợ giúp từ các cảm biến như IMU, GPS, và Odometry. Bằng cách kết hợp các tín hiệu từ các cảm biến lại với nhau(sensor fusion) một lượng thông tin bổ ích sẽ giúp cho robot định vị được tọa độ chính xác trong các môi trường khác nhau. Trong project này, mình sẽ giới thiệu tới các bạn Robot Localization package một công cụ hữu ích để tích hợp sensor fusion cùng với turtlebot 3. Ở project này, mức độ đòi hỏi các bạn cần có một số kiến thức cơ bản như urdf, frames,… để có thể chỉnh sửa và thay đổi các file sẵn có. Hy vọng blog này sẽ mang lại cái nhìn tổng quan về chủ đề sensor fusion và localization ở robotics.
Khởi động rosject
The Construct đã tạo ra một công cụ rất hữu ích là rosject, nơi các bạn dễ dàng truy cập và khởi tạo các project cùng với ROS ở các phiên bản khác nhau:
Như hình bên dưới là một rosject hoàn chỉnh, bạn chỉ việc nhấn vào nút RUN để khởi động rosject.
Sau khi nhấn vào nút RUN bạn sẽ thấy rosject được chạy trên màn hình. Ở đây, rosject cung cấp cho bạn cả terminal để có thể nhập các câu lệnh:
Bây giờ cùng nhau đến bước tiếp theo trong ví dụ này nhé.
Cài đặt các package cần thiết
Đầu tiên các bạn cần cài đặt các package cần thiết để có thể thực hiện project này, sau khi vào môi trường làm việc catkin là ros2_ws các bạn làm theo bằng cách thực hiện các câu lệnh sau đây:
Sau bước này thì các bạn sẽ cần một ít thời gian để có thể cài đặt thành công các gói cần thiết cho chương trình.
Tiếp theo sử dụng câu lệnh sau để build môi trường catkin
colcon build --symlink-install
Mình sẽ giải thích lý do tại sao các bạn lại phải sử dụng đường link github phía bên trên. Vì ở trong project này mình muốn tổng hợp và sử dụng những packages cần thiết và chỉnh sửa một số nội dung file nên mình đã gom thành một package lớn bao gồm các packages nhỏ để tiện cho việc chạy chương trình.
Mô phỏng trên Gazebo cùng robot
Sau khi đã cài đặt đầy đủ các package cần thiết. Bước tiếp theo bạn cần chọn model robot cho project của bạn. mình sử dụng waffle trong project này:
source ~/ros2_ws/install/setup.bash
export TURTLEBOT3_MODEL=waffle
Sau đó để sử dụng môi trường gazebo và robot, bạn cần truy xuất môi trường gazebo và chạy ros launch package:
Cửa sổ Gazebo sẽ xuất hiện như trên cùng với robot waffle.
Chạy Robot Localization package
Như bạn thấy môi trường và robot đã có, điểm quan trọng trong project lần này là biết cách áp dụng các cảm biến như IMU, Odometry, GPS,.. cùng với robot trong nhiệm vụ tổng hợp các tiến hiệu từ các cảm biến khác để giải quyết bài toán định vị cho robot. Ở cửa sổ thứ 2, các bạn thực hiện câu lệnh sau đây:
source ~/ros2_ws/install/setup.bash
ros2 launch robot_localization ekf.launch.py
Tạo và mô phỏng robot cùng cảm biến trên Rviz
Sau khi đã có môi trường cho robot hoạt động là gazebo, có các cảm biến được tích hợp trên robot, nhiệm vụ là làm sao để có thể xác định và sử dụng các cảm biến để phục vụ cho bài toán định vị trên robot. Ở đây trên Rviz sẽ mô phỏng và cho thấy các trục tọa độ của từng thành phần được gắn vào robot. Trong các thư mục được cài đặt mình cũng đã chỉnh sửa phần mô phỏng để cho thấy sự thay đổi khi sử dụng gói Robot Localization:
Các bạn lưu ý là ở đây odom sẽ là frame chính của chúng ta, sau khi mô phỏng trên RViz thành công bây giờ các bjan có thể tận dụng các cảm biến đưuọc cài đặt sẵn trên robot và phục vụ cho project của các bạn như SLAM,…
Thêm một chút gợi ý nho nhỏ cho các bạn để kiểm tra rằng mình đã có các topic và frame được liên kết với nhau chưa. Đầu tiên mình sử dụng:ros2 topic list và kết quả xuất hiện các topic như GPS, IMU, Odometry.
Bên cạnh đó, ROS cung cấp câu lệnh để kiểm tra sự kết nối giữa các frame được khai báo: ros2 run tf2_tools view_frames.py
Sau đó file sẽ được lưu dưới dạng pdf và bạn chỉ việc mở lên để kiểm tra bằng câu lệnh sau:
evince frames.pdf
Để có được kết quả như trên, thì chủ yếu mình đã chỉnh sửa và thêm vào IMU và GPS tại urdf file. Các bạn có thể mở gói turtlebot3_description để chỉnh sửa tùy theo project của mình.
Hy vọng qua blog này các bạn đã có thêm một số kiến thức cũng như công cụ hỗ trợ cho các dự án liên quan tới robotics sử dụng ROS. Bên cạnh đó, các bạn có thể theo dõi các blog khác cũng như các khóa học liên quan trên The Construct nhé.
As we can see on https://docs.ros.org/en/foxy/Concepts/About-Tf2.html, tf2 is the transform library for ROS2. It maintains the relationship between coordinate frames in a tree structure buffered in time and lets the user transform points between any two coordinate frames at any desired point in time.
Creating a rosject
In order to learn TF2 hands-on, we need to have a system with ROS installed. We are going to use The Construct (https://app.theconstructsim.com/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.
Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.
Once inside, if you did not decide to use the existing rosject, let’s click My Rosjects on the left side and then, Create a new rosject.
Create a new rosject
Let’s select ROS2 Foxyfor the ROS Distro of the rosject, let’s name the rosject as you want. You can leave the rosject public. You should see the rosject you just created in your rosjects list (the name is certainly different from the example below that was added just for learning purposes)
If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.
Creating a ROS2 Python package
After pressing the Run button, you should now have the rosject open. Now, it’s time to create a ROS2 Python package if you had to create your own rosject. If you used the rosject we shared at the beginning of this post, you don’t need to create the package. To create the package, let’s start by opening a terminal:
Open a new Terminal
Now, in this first terminal, let’s run the following command to create our package named tf2_examples:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python --node-name my_node tf2_examples
If everything went fine, the output should be as follows:
As we can see in the logs, we already have a node called ./tf2_examples/tf2_examples/my_node.py and a ./tf2_examples/setup.py.
Preparing our python package to have launch files
In order to be able to have launch files in our package, we need to modify the ~/ros2_ws/src/tf2_examples/setup.py file.
We have basically to import glob and modify the data_files variable. To make our life easier, I’m going to paste here the full content of the setup.py file after our modifications (bear in mind that if you used the rosject provided at the beginning, the file will already contain the correct code):
On The Construct, we do not always have all packages required for all purposes. For this TF2 demonstration, we need to have some packages. Lets install them by running the following commands in a second terminal:
If you have used the rosject we provided at the beginning, you already have the my_node.py with a Frame Publisher set.
If you created a rosject manually, however, you need to manually replace the content of my_node.py with the following content taken from the official ROS2 Tutorials:
from geometry_msgs.msg import TransformStamped
import rclpy
from rclpy.node import Node
from tf2_ros import TransformBroadcaster
import tf_transformations
from turtlesim.msg import Pose
class FramePublisher(Node):
def __init__(self):
super().__init__('turtle_tf2_frame_publisher')
# Declare and acquire `turtlename` parameter
self.declare_parameter('turtlename', 'turtle')
self.turtlename = self.get_parameter(
'turtlename').get_parameter_value().string_value
# Initialize the transform broadcaster
self.br = TransformBroadcaster(self)
# Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose
# callback function on each message
self.subscription = self.create_subscription(
Pose,
f'/{self.turtlename}/pose',
self.handle_turtle_pose,
1)
def handle_turtle_pose(self, msg):
t = TransformStamped()
t2 = TransformStamped()
# Read message content and assign it to
# corresponding tf variables
t.header.stamp = self.get_clock().now().to_msg()
t.header.frame_id = 'world'
t.child_frame_id = self.turtlename
# turtle/drone
t2.header.stamp = self.get_clock().now().to_msg()
t2.header.frame_id = self.turtlename
t2.child_frame_id = 'drone'
# Turtle only exists in 2D, thus we get x and y translation
# coordinates from the message and set the z coordinate to 0
t.transform.translation.x = msg.x - (11.08888/2)
t.transform.translation.y = msg.y - (11.08888/2)
t.transform.translation.z = 0.0
t2.transform.translation.x = -0.5
t2.transform.translation.y = 0.0
t2.transform.translation.z = 1.0
# For the same reason, turtle can only rotate around one axis
# and this why we set rotation in x and y to 0 and obtain
# rotation in z axis from the message
q = tf_transformations.quaternion_from_euler(0, 0, msg.theta)
t.transform.rotation.x = q[0]
t.transform.rotation.y = q[1]
t.transform.rotation.z = q[2]
t.transform.rotation.w = q[3]
t2.transform.rotation.x = 0.0
t2.transform.rotation.y = 0.0
t2.transform.rotation.z = 0.0
t2.transform.rotation.w = 1.0
# Send the transformation
self.br.sendTransform(t)
self.br.sendTransform(t2)
def main():
rclpy.init()
node = FramePublisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
rclpy.shutdown()
If you check carefully the code of my_node.py, you can see that around line 24 we start a TF Broadcaster:
# Initialize the transform broadcaster
self.br = TransformBroadcaster(self)
we then create a subscriber to a /turtle{1}{2}/pose topic and call handle_turtle_pose method when messages arrive on this topic. I highly recommend you have a look at the comments and the code of the handle_turtle_pose to better understand it. It not only makes you exercise your brain but also makes you better understand things by yourself.
Creating a launch file
Now that we have our code in place, we need a launch file. If you remember well, the setup.py file is already prepared for launch files. We now need to create the launch files. Bear in mind that if you are using the rosject we provided at the beginning of the post, this launch folder and its python file already exist.
Assuming you created a rosject manually, let’s create a launch folder using the terminal:
cd ~/ros2_ws/src/tf2_examples
mkdir launch
cd launch
touch tf2_example.launch.py
If you just created the tf2_example.launch.py file, it may be empty. If that is the case, just copy the following content to it:
If you are wondering why you need to compile the workspace if we are using only python, not C++, the answer is: we need to compile so that ROS2 can copy the launch files to the common share folder of the workspace.
Launching the turtle simulation using ros2 launch files
Now that our package is compiled and everything in place, let’s launch the launch file we just created:
cd ~/ros2_ws/
source install/setup.bash
ros2 launch tf2_examples tf2_example.launch.py
You should now have a simulation window that should have opened automatically with the turtlesim simulation on it.
If the simulation do not show automatically to you, you can just open the Graphical Tools:
Open Graphical Tools to see the turtlesim
If we now open a third terminal, we should be able to see our nodes there after typing ros2 node list:
ros2 node list
/broadcaster1
/sim
Open RViz2
Now that we have our simulation and our broadcaster running, we need RViz2 to see the frames.
Let’s open it running the following command in the third terminal:
ros2 run rviz2 rviz2
If rviz2 does not show automatically to you, you can just open the Graphical Tools as before.
Assuming RViz is running, remember to set the fixed frame (on the top left side) to world. You also have to click the Add button on the bottom left to add TF:
RViz add TF
After having added the TF panel in RViz, you should see the frames as it happens in the center of the image above.
Moving the robot using the keyboard
Now that we have our simulation running, rviz running, and our TF panel added, we can move our robot to better understand the TFs.
TIP: In the Graphical Tools window, to see the Turtlesim and the TFs at the same time, you can put move the turtlesim panel to the left, and put RViz on the right side.
Ok, we can now open a fourth terminal and run the turtle_teleop_key node that allows us to move the robot using the keyboard:
ros2 run turtlesim turtle_teleop_key
You should now have the instructions to move the robot around:
Reading from keyboard
---------------------------
Use arrow keys to move the turtle.
Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. 'F' to cancel a rotation.
'Q' to quit.
By pressing the arrow keys, you can see that the robot moves, and at the same time we can see the frames in RViz.
If you look carefully, in RViz we see not only the frames for the turtle and the world, but we also have a drone frame.
We did that to simulate that we have a drone following the turtle at a fixed distance. I would recommend you have a look again at the handle_turtle_pose method defined in the my_node.py to better understand it.
In the code of handle_turtle_pose, we see that the drone is 0.5 meters behind the turtle and 1 meter above:
Whenever we receive a position from the turtle, we are publishing the position of the drone based on the position of the turtle.
Checking the position of the turtle related to the world using the command line
We saw that we can do a lot of tf2-related things using Python.
It is worth mentioning that we can also use the command line to check the position/distance of the turtle with relation to the world.
ROS2 has a package named tf2_ros with many executables and one of them is tf2_echo. We can know the position of the turtle with the following command:
ros2 run tf2_ros tf2_echo world turtle1
The output should be similar to the following:
At time 1649109418.661570822
- Translation: [-2.552, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
At time 1649109419.668374440
- Translation: [-2.552, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
At time 1649109420.660446225
- Translation: [-2.552, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
You can also check the position of the drone related to the position of the robot:
ros2 run tf2_ros tf2_echo turtle1 drone
You can see that the translation is exactly as set in the my_node.py file (half meters behind, 1 meter above):
- Translation: [-0.500, 0.000, 1.000]
If you are wondering whether or not you can know the position of the drone related to the world, you can know it also just change the parameters passed to the tf2_echo node:
ros2 run tf2_ros tf2_echo world drone
Youtube video
So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.
Keep pushing your ROS Learning.
Related Courses & Training
If you want to learn more about ROS and ROS2, we recommend the following courses: