当前位置:   article > 正文

ROS 2边学边练(41)-- 使用基于tf2_ros::MessageFilter带标记(位姿、时间...)的数据类型

ROS 2边学边练(41)-- 使用基于tf2_ros::MessageFilter带标记(位姿、时间...)的数据类型

前言

        此篇将介绍如何利用tf2来使用传感器数据(如单声道和立体声摄像机以及雷达)。

        假设我们创建了一只海龟叫turtle3,它的里程计不大好用,为了监视turtle3的活动轨迹,有台头顶摄像机被安装到该海龟的背上(负碑的赑屃),并且实时发布相对于世界坐标系的PointStamped消息(包含位姿和时间)。

        有只叫turtle1的海龟想要知道turtle3相对自己的位姿(在turtle1坐标系中)。

        于是乎turtle1订阅了摄像机发布的关于turtle3位姿的主题,并等待可用的转换数据以便执行其他操作。为了方便实现这个目标,我们可以利用tf2_ros::MessageFilter。

        tf2_ros::MessageFilter会订阅任何带有头(header)的消息并缓存起来,直到可以将该消息从源坐标系变换到目标坐标系为止。

动动手

        需要实现两个节点,一个python实现一个C++实现,其中python的实现需要在learning_tf2_py功能包下,如果前期一直用的learning_tf2_cpp包而没有此包的话,请在工作空间根路径的src下执行如下命令:

$ros2 pkg create --build-type ament_python --license Apache-2.0 -- learning_tf2_py

写一个PointStamped消息的广播节点

        我们先实现一个广播turtle3 PointStamped位置消息的节点代码(python)。

        进入learning_tf2_py包路径下src/learning_tf2_py/learning_tf2_py,执行如下命令下载传感器消息广播节点的例子代码turtle_tf2_message_broadcaster.py:

$wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_py/turtle_tf2_py/turtle_tf2_message_broadcaster.py

内容如下:

  1. from geometry_msgs.msg import PointStamped
  2. from geometry_msgs.msg import Twist
  3. import rclpy
  4. from rclpy.node import Node
  5. from turtlesim.msg import Pose
  6. from turtlesim.srv import Spawn
  7. class PointPublisher(Node):
  8. def __init__(self):
  9. super().__init__('turtle_tf2_message_broadcaster')
  10. # Create a client to spawn a turtle
  11. self.spawner = self.create_client(Spawn, 'spawn')
  12. # Boolean values to store the information
  13. # if the service for spawning turtle is available
  14. self.turtle_spawning_service_ready = False
  15. # if the turtle was successfully spawned
  16. self.turtle_spawned = False
  17. # if the topics of turtle3 can be subscribed
  18. self.turtle_pose_cansubscribe = False
  19. self.timer = self.create_timer(1.0, self.on_timer)
  20. def on_timer(self):
  21. if self.turtle_spawning_service_ready:
  22. if self.turtle_spawned:
  23. self.turtle_pose_cansubscribe = True
  24. else:
  25. if self.result.done():
  26. self.get_logger().info(
  27. f'Successfully spawned {self.result.result().name}')
  28. self.turtle_spawned = True
  29. else:
  30. self.get_logger().info('Spawn is not finished')
  31. else:
  32. if self.spawner.service_is_ready():
  33. # Initialize request with turtle name and coordinates
  34. # Note that x, y and theta are defined as floats in turtlesim/srv/Spawn
  35. request = Spawn.Request()
  36. request.name = 'turtle3'
  37. request.x = 4.0
  38. request.y = 2.0
  39. request.theta = 0.0
  40. # Call request
  41. self.result = self.spawner.call_async(request)
  42. self.turtle_spawning_service_ready = True
  43. else:
  44. # Check if the service is ready
  45. self.get_logger().info('Service is not ready')
  46. if self.turtle_pose_cansubscribe:
  47. self.vel_pub = self.create_publisher(Twist, 'turtle3/cmd_vel', 10)
  48. self.sub = self.create_subscription(Pose, 'turtle3/pose', self.handle_turtle_pose, 10)
  49. self.pub = self.create_publisher(PointStamped, 'turtle3/turtle_point_stamped', 10)
  50. def handle_turtle_pose(self, msg):
  51. vel_msg = Twist()
  52. vel_msg.linear.x = 1.0
  53. vel_msg.angular.z = 1.0
  54. self.vel_pub.publish(vel_msg)
  55. ps = PointStamped()
  56. ps.header.stamp = self.get_clock().now().to_msg()
  57. ps.header.frame_id = 'world'
  58. ps.point.x = msg.x
  59. ps.point.y = msg.y
  60. ps.point.z = 0.0
  61. self.pub.publish(ps)
  62. def main():
  63. rclpy.init()
  64. node = PointPublisher()
  65. try:
  66. rclpy.spin(node)
  67. except KeyboardInterrupt:
  68. pass
  69. rclpy.shutdown()
代码分析
  1. # Initialize request with turtle name and coordinates
  2. # Note that x, y and theta are defined as floats in turtlesim/srv/Spawn
  3. request = Spawn.Request()
  4. request.name = 'turtle3'
  5. request.x = 4.0
  6. request.y = 2.0
  7. request.theta = 0.0
  8. # Call request
  9. self.result = self.spawner.call_async(request)

        on_timer回调函数中,我们通过异步调用turtlesim中的Spawn服务孵化出turtle3,并给予turtle3初始位置(4, 2, 0)。

  1. self.vel_pub = self.create_publisher(Twist, '/turtle3/cmd_vel', 10)
  2. self.sub = self.create_subscription(Pose, '/turtle3/pose', self.handle_turtle_pose, 10)
  3. self.pub = self.create_publisher(PointStamped, '/turtle3/turtle_point_stamped', 10)

        之后节点发布主题/turtle3/cmd_vel及主题/turtle3/turtle_point_stamped数据,并订阅了/turtle3/pose主题,当进来消息后会调用handle_turtle_pose回调函数来处理这些消息。

  1. vel_msg = Twist()
  2. vel_msg.linear.x = 1.0
  3. vel_msg.angular.z = 1.0
  4. self.vel_pub.publish(vel_msg)
  5. ps = PointStamped()
  6. ps.header.stamp = self.get_clock().now().to_msg()
  7. ps.header.frame_id = 'world'
  8. ps.point.x = msg.x
  9. ps.point.y = msg.y
  10. ps.point.z = 0.0
  11. self.pub.publish(ps)

        最后,在回调函数handle_turtle_pose中,我们初始化了turtle3的Twist类型消息(半径为1米的圆周运动)并发布了它们,接着我们将接收到的Pose消息解析并填充到PointStamped消息中,最后发布了这个PointStamped类型消息。

写一个启动文件

        在learning_tf2_py包中的launch文件夹内创建turtle_tf2_sensor_message_launch.py文件用来运行我们的例子。

  1. from launch import LaunchDescription
  2. from launch.actions import DeclareLaunchArgument
  3. from launch_ros.actions import Node
  4. def generate_launch_description():
  5. return LaunchDescription([
  6. DeclareLaunchArgument(
  7. 'target_frame', default_value='turtle1',
  8. description='Target frame name.'
  9. ),
  10. Node(
  11. package='turtlesim',
  12. executable='turtlesim_node',
  13. name='sim',
  14. output='screen'
  15. ),
  16. Node(
  17. package='turtle_tf2_py',
  18. executable='turtle_tf2_broadcaster',
  19. name='broadcaster1',
  20. parameters=[
  21. {'turtlename': 'turtle1'}
  22. ]
  23. ),
  24. Node(
  25. package='turtle_tf2_py',
  26. executable='turtle_tf2_broadcaster',
  27. name='broadcaster2',
  28. parameters=[
  29. {'turtlename': 'turtle3'}
  30. ]
  31. ),
  32. Node(
  33. package='turtle_tf2_py',
  34. executable='turtle_tf2_message_broadcaster',
  35. name='message_broadcaster',
  36. ),
  37. ])
添加入口点

        我们必须在src/learning_tf2_py路径下的setup.py文件中添加入口点才能让ros2 run命令启动我们的节点。将下面语句添加到'console_scripts':括弧内:

'turtle_tf2_message_broadcaster = learning_tf2_py.turtle_tf2_message_broadcaster:main',

        另外为了使ros2 launch能够通过包名找到上面的launch文件,我们还需添加相关引入模块及安装信息(否则就会提示在share下找不到launch文件,因为那个路径下根本就未安装成功):

  1. import os
  2. from glob import glob
  3. from setuptools import find_packages, setup
  4. package_name = 'launch_tutorial'
  5. setup(
  6. # Other parameters ...
  7. data_files=[
  8. # ... Other data files
  9. # Include all launch files.
  10. (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*')))
  11. ]
  12. )

        完整的setup.py信息如下: 

  1. import os
  2. from glob import glob
  3. from setuptools import find_packages, setup
  4. package_name = 'learning_tf2_py'
  5. setup(
  6. name=package_name,
  7. version='0.0.0',
  8. packages=find_packages(exclude=['test']),
  9. data_files=[
  10. ('share/ament_index/resource_index/packages',
  11. ['resource/' + package_name]),
  12. ('share/' + package_name, ['package.xml']),
  13. (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*')))
  14. ],
  15. install_requires=['setuptools'],
  16. zip_safe=True,
  17. maintainer='mike',
  18. maintainer_email='mike@todo.todo',
  19. description='TODO: Package description',
  20. license='Apache-2.0',
  21. tests_require=['pytest'],
  22. entry_points={
  23. 'console_scripts': [
  24. 'turtle_tf2_message_broadcaster = learning_tf2_py.turtle_tf2_message_broadcaster:main',
  25. ],
  26. },
  27. )
构建

        进入工作空间根路径,分别执行如下命令进行依赖检查和最终包的构建工作。

$rosdep install -i --from-path src --rosdistro iron -y
$colcon build --packages-select learning_tf2_py

写一个消息过滤器/监听器节点

        现在,为了可靠地在turtle1的坐标系下获取turtle3的流式PointStamped数据,我们将创建消息过滤器/监听器节点的源文件。

        进入src/learning_tf2_cpp/src路径,执行下面的命令下载turtle_tf2_message_filter.cpp文件:

$wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/turtle_tf2_message_filter.cpp

内容如下:

  1. #include <chrono>
  2. #include <memory>
  3. #include <string>
  4. #include "geometry_msgs/msg/point_stamped.hpp"
  5. #include "message_filters/subscriber.h"
  6. #include "rclcpp/rclcpp.hpp"
  7. #include "tf2_ros/buffer.h"
  8. #include "tf2_ros/create_timer_ros.h"
  9. #include "tf2_ros/message_filter.h"
  10. #include "tf2_ros/transform_listener.h"
  11. #ifdef TF2_CPP_HEADERS
  12. #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
  13. #else
  14. #include "tf2_geometry_msgs/tf2_geometry_msgs.h"
  15. #endif
  16. using namespace std::chrono_literals;
  17. class PoseDrawer : public rclcpp::Node
  18. {
  19. public:
  20. PoseDrawer()
  21. : Node("turtle_tf2_pose_drawer")
  22. {
  23. // Declare and acquire `target_frame` parameter
  24. target_frame_ = this->declare_parameter<std::string>("target_frame", "turtle1");
  25. std::chrono::duration<int> buffer_timeout(1);
  26. tf2_buffer_ = std::make_shared<tf2_ros::Buffer>(this->get_clock());
  27. // Create the timer interface before call to waitForTransform,
  28. // to avoid a tf2_ros::CreateTimerInterfaceException exception
  29. auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
  30. this->get_node_base_interface(),
  31. this->get_node_timers_interface());
  32. tf2_buffer_->setCreateTimerInterface(timer_interface);
  33. tf2_listener_ =
  34. std::make_shared<tf2_ros::TransformListener>(*tf2_buffer_);
  35. point_sub_.subscribe(this, "/turtle3/turtle_point_stamped");
  36. tf2_filter_ = std::make_shared<tf2_ros::MessageFilter<geometry_msgs::msg::PointStamped>>(
  37. point_sub_, *tf2_buffer_, target_frame_, 100, this->get_node_logging_interface(),
  38. this->get_node_clock_interface(), buffer_timeout);
  39. // Register a callback with tf2_ros::MessageFilter to be called when transforms are available
  40. tf2_filter_->registerCallback(&PoseDrawer::msgCallback, this);
  41. }
  42. private:
  43. void msgCallback(const geometry_msgs::msg::PointStamped::SharedPtr point_ptr)
  44. {
  45. geometry_msgs::msg::PointStamped point_out;
  46. try {
  47. tf2_buffer_->transform(*point_ptr, point_out, target_frame_);
  48. RCLCPP_INFO(
  49. this->get_logger(), "Point of turtle3 in frame of turtle1: x:%f y:%f z:%f\n",
  50. point_out.point.x,
  51. point_out.point.y,
  52. point_out.point.z);
  53. } catch (const tf2::TransformException & ex) {
  54. RCLCPP_WARN(
  55. // Print exception which was caught
  56. this->get_logger(), "Failure %s\n", ex.what());
  57. }
  58. }
  59. std::string target_frame_;
  60. std::shared_ptr<tf2_ros::Buffer> tf2_buffer_;
  61. std::shared_ptr<tf2_ros::TransformListener> tf2_listener_;
  62. message_filters::Subscriber<geometry_msgs::msg::PointStamped> point_sub_;
  63. std::shared_ptr<tf2_ros::MessageFilter<geometry_msgs::msg::PointStamped>> tf2_filter_;
  64. };
  65. int main(int argc, char * argv[])
  66. {
  67. rclcpp::init(argc, argv);
  68. rclcpp::spin(std::make_shared<PoseDrawer>());
  69. rclcpp::shutdown();
  70. return 0;
  71. }
代码分析
  1. #include "geometry_msgs/msg/point_stamped.hpp"
  2. #include "message_filters/subscriber.h"
  3. #include "rclcpp/rclcpp.hpp"
  4. #include "tf2_ros/buffer.h"
  5. #include "tf2_ros/create_timer_ros.h"
  6. #include "tf2_ros/message_filter.h"
  7. #include "tf2_ros/transform_listener.h"
  8. #ifdef TF2_CPP_HEADERS
  9. #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
  10. #else
  11. #include "tf2_geometry_msgs/tf2_geometry_msgs.h"
  12. #endif

        首先需包含tf2_ros::MessageFilter、tf2、ros2等相关头文件,以使能相关API。

  1. std::string target_frame_;
  2. std::shared_ptr<tf2_ros::Buffer> tf2_buffer_;
  3. std::shared_ptr<tf2_ros::TransformListener> tf2_listener_;
  4. message_filters::Subscriber<geometry_msgs::msg::PointStamped> point_sub_;
  5. std::shared_ptr<tf2_ros::MessageFilter<geometry_msgs::msg::PointStamped>> tf2_filter_;

        其次声明有关tf2_ros::Buffer、tf2_ros::TransformListener及tf2_ros::MessageFilter的全局变量。

  1. PoseDrawer()
  2. : Node("turtle_tf2_pose_drawer")
  3. {
  4. // Declare and acquire `target_frame` parameter
  5. target_frame_ = this->declare_parameter<std::string>("target_frame", "turtle1");
  6. std::chrono::duration<int> buffer_timeout(1);
  7. tf2_buffer_ = std::make_shared<tf2_ros::Buffer>(this->get_clock());
  8. // Create the timer interface before call to waitForTransform,
  9. // to avoid a tf2_ros::CreateTimerInterfaceException exception
  10. auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
  11. this->get_node_base_interface(),
  12. this->get_node_timers_interface());
  13. tf2_buffer_->setCreateTimerInterface(timer_interface);
  14. tf2_listener_ =
  15. std::make_shared<tf2_ros::TransformListener>(*tf2_buffer_);
  16. point_sub_.subscribe(this, "/turtle3/turtle_point_stamped");
  17. tf2_filter_ = std::make_shared<tf2_ros::MessageFilter<geometry_msgs::msg::PointStamped>>(
  18. point_sub_, *tf2_buffer_, target_frame_, 100, this->get_node_logging_interface(),
  19. this->get_node_clock_interface(), buffer_timeout);
  20. // Register a callback with tf2_ros::MessageFilter to be called when transforms are available
  21. tf2_filter_->registerCallback(&PoseDrawer::msgCallback, this);
  22. }

        第三,ROS 2中的message_filters::Subscriber必须使用主题("/turtle3/turtle_point_stamped")进行初始化。同时,tf2_ros::MessageFilter也必须使用那个Subscriber对象(point_sub_)进行初始化。在MessageFilter构造函数中值得注意的其他参数包括目标帧(target_frame)和回调函数(callback)。目标帧是确保canTransform能够成功执行的目标坐标系。当数据准备就绪时,回调函数(msgCallback)就会被调用。

  1. private:
  2. void msgCallback(const geometry_msgs::msg::PointStamped::SharedPtr point_ptr)
  3. {
  4. geometry_msgs::msg::PointStamped point_out;
  5. try {
  6. tf2_buffer_->transform(*point_ptr, point_out, target_frame_);
  7. RCLCPP_INFO(
  8. this->get_logger(), "Point of turtle3 in frame of turtle1: x:%f y:%f z:%f\n",
  9. point_out.point.x,
  10. point_out.point.y,
  11. point_out.point.z);
  12. } catch (const tf2::TransformException & ex) {
  13. RCLCPP_WARN(
  14. // Print exception which was caught
  15. this->get_logger(), "Failure %s\n", ex.what());
  16. }
  17. }

        最后,在回调函数msgCallback中,当数据准备好的时候会调用transform函数将数据转换为目标坐标系视角下的对应数据,并会将结果数据输出到控制台。

package,xml添加依赖

        我们需要增加下面两行内容到package.xml:

  1. <depend>message_filters</depend>
  2. <depend>tf2_geometry_msgs</depend>
CMakeLists.txt

        同样,CMakeLists.txt文件也需添加下面两行内容:

  1. find_package(message_filters REQUIRED)
  2. find_package(tf2_geometry_msgs REQUIRED)

        下面内容是为了处理不同版本的ROS:

  1. if(TARGET tf2_geometry_msgs::tf2_geometry_msgs)
  2. get_target_property(_include_dirs tf2_geometry_msgs::tf2_geometry_msgs INTERFACE_INCLUDE_DIRECTORIES)
  3. else()
  4. set(_include_dirs ${tf2_geometry_msgs_INCLUDE_DIRS})
  5. endif()
  6. find_file(TF2_CPP_HEADERS
  7. NAMES tf2_geometry_msgs.hpp
  8. PATHS ${_include_dirs}
  9. NO_CACHE
  10. PATH_SUFFIXES tf2_geometry_msgs
  11. )

        接着,我们还需加上下面内容,我们将消息过滤器/监听器节点可执行文件命名为turtle_tf2_message_filter:

  1. add_executable(turtle_tf2_message_filter src/turtle_tf2_message_filter.cpp)
  2. ament_target_dependencies(
  3. turtle_tf2_message_filter
  4. geometry_msgs
  5. message_filters
  6. rclcpp
  7. tf2
  8. tf2_geometry_msgs
  9. tf2_ros
  10. )
  11. if(EXISTS ${TF2_CPP_HEADERS})
  12. target_compile_definitions(turtle_tf2_message_filter PUBLIC -DTF2_CPP_HEADERS)
  13. endif()

        最后再加上安装信息,使ros2 run命令能够根据路径找到可执行文件:

  1. install(TARGETS
  2. turtle_tf2_message_filter
  3. DESTINATION lib/${PROJECT_NAME})
构建

        执行依赖检查和最终构建:

$rosdep install -i --from-path src --rosdistro iron -y
$colcon build --packages-select learning_tf2_cpp

运行

        新开一个终端,进入工作空间根路径,source下环境(. install/setup.bash),首先启动几个节点(PointStamped消息的广播节点):

$ros2 launch learning_tf2_py turtle_tf2_sensor_message_launch.py

        如果上述命令提示找不到turtle_tf2_sensor_message_launch.py文件(原因及解决方法见上文setup.py文件的修改,主要是未添加安装信息,如cmake中的install()一样)也可以直接进入launch路径执行,如下图所示。 

        启动完成后会有两只小海龟,turtle3在做圆周运动,turtle1静止不动,我们可以再开启一个终端执行如下命令控制turtle1:

$ros2 run turtlesim turtle_teleop_key

        我们可以订阅查看下turtle3/turtle_point_stamped主题的消息:

$ros2 topic echo /turtle3/turtle_point_stamped

        这些都完成之后,我们再运行下最后构建的消息过滤器/监听器节点:

$ros2 run learning_tf2_cpp turtle_tf2_message_filter

        如果一切OK,我们会在终端看到下面的信息(turtle3在turtle1坐标系中的位姿数据):

本篇完。 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/538491
推荐阅读
相关标签
  

闽ICP备14008679号