ROS使用(9)tf2

news2024/11/25 18:27:18

许多tf2教程可用于C++和Python。本教程经过了精简,以完成C++或Python。如果你想同时学习C++和Python,你应该分别学习一次C++和一次Python的教程。

工作区设置

  1. Introduction to tf2.
    tf2的 介绍

    本教程将给予你一个很好的想法tf2可以为你做什么。 它在一个使用turtlesim的多机器人示例中展示了tf2的一些功能。 这也介绍了使用tf2_echoview_framesrviz

  2. 编写一个静态广播器 (Python)(C++).

    本教程将教您如何将静态坐标框架广播到tf2。

  3. 写一个广播器 (Python)(C++).

    本教程教你如何将机器人的状态广播到tf2。

  4. 编写监听器 (Python) (C++)。

    本教程将教您如何使用tf2访问帧变换。

  5. 添加坐标 (Python) (C++)。

    本教程教你如何添加一个额外的固定帧到tf2。

  6. 使用时间 (Python) (C++)。

    本教程教您使用lookup_transform函数中的超时来 等待TF2树上可用的变换。

  7. T时间旅行 (Python) (C++)。

    本教程教你tf2的高级时间旅行功能。

Debugging tf2

  1. Quaternion fundamentals.
    四元数基础。

    This tutorial teaches you basics of quaternion usage in ROS 2.
    本教程教你在ROS 2中使用四元数的基础知识。

  2. Debugging tf2 problems.
    调试tf2问题 。

    This tutorial teaches you about a systematic approach for debugging tf2 related problems.
    本教程教你一个调试tf2相关问题的系统方法。

Using sensor message with tf2

  1. 在tf2_ros::MessageFilter中使用stamped数据类型

    本教程教你如何使用tf2_ros::MessageFilter来处理带戳的数据类型。

Writing a static broadcaster

发布静态变换对于定义机器人基座与其传感器或非移动部件之间的关系非常有用。 例如,在激光扫描仪的中心处的帧中的激光扫描测量是最容易推理的。

这是一个独立的教程,涵盖了静态转换的基础知识,它由两部分组成。 在第一部分中,我们将编写代码将静态转换发布到tf2。 在第二部分中,我们将解释如何在static_transform_publisher中使用命令行tf2_ros可执行工具。

在接下来的两个教程中,我们将编写代码,从 TF2简介 教程。 之后,下面的教程将重点介绍如何使用更高级的tf2特性来扩展演示。

在前面的教程中,您学习了如何创建工作区和创建包。

首先,我们将创建一个用于本教程和后续教程的包。 名为learning_tf2_cpp的包将依赖于geometry_msgsrclcpptf2tf2_rosturtlesim。 本教程的代码存储在此处。

打开一个新的终端并源化你的ROS 2安装,这样ros2命令就可以工作了。 导航到工作区的src文件夹并创建一个新包:

ros2 pkg create --build-type ament_cmake --dependencies geometry_msgs rclcpp tf2 tf2_ros turtlesim -- learning_tf2_cpp

您的终端将返回一条消息,验证您的软件包learning_tf2_cpp及其所有必要的文件和文件夹的创建。

write the static broadcaster node

让我们首先创建源文件。 在src/learning_tf2_cpp/src目录中,通过输入以下命令下载示例静态广播器代码:

使用首选的文本编辑器打开文件。
#include <memory>

#include "geometry_msgs/msg/transform_stamped.hpp"
#include "rclcpp/rclcpp.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2_ros/static_transform_broadcaster.h"

class StaticFramePublisher : public rclcpp::Node
{
public:
  explicit StaticFramePublisher(char * transformation[])
  : Node("static_turtle_tf2_broadcaster")
  {
    tf_static_broadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);

    // Publish static transforms once at startup
    this->make_transforms(transformation);
  }

private:
  void make_transforms(char * transformation[])
  {
    geometry_msgs::msg::TransformStamped t;

    t.header.stamp = this->get_clock()->now();
    t.header.frame_id = "world";
    t.child_frame_id = transformation[1];

    t.transform.translation.x = atof(transformation[2]);
    t.transform.translation.y = atof(transformation[3]);
    t.transform.translation.z = atof(transformation[4]);
    tf2::Quaternion q;
    q.setRPY(
      atof(transformation[5]),
      atof(transformation[6]),
      atof(transformation[7]));
    t.transform.rotation.x = q.x();
    t.transform.rotation.y = q.y();
    t.transform.rotation.z = q.z();
    t.transform.rotation.w = q.w();

    tf_static_broadcaster_->sendTransform(t);
  }

  std::shared_ptr<tf2_ros::StaticTransformBroadcaster> tf_static_broadcaster_;
};

int main(int argc, char * argv[])
{
  auto logger = rclcpp::get_logger("logger");

  // Obtain parameters from command line arguments
  if (argc != 8) {
    RCLCPP_INFO(
      logger, "Invalid number of parameters\nusage: "
      "$ ros2 run learning_tf2_cpp static_turtle_tf2_broadcaster "
      "child_frame_name x y z roll pitch yaw");
    return 1;
  }

  // As the parent frame of the transform is `world`, it is
  // necessary to check that the frame name passed is different
  if (strcmp(argv[1], "world") == 0) {
    RCLCPP_INFO(logger, "Your static turtle name cannot be 'world'");
    return 1;
  }

  // Pass parameters and initialize node
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<StaticFramePublisher>(argv));
  rclcpp::shutdown();
  return 0;
}

2.1 Examine the code

现在让我们看看与将静态乌龟姿势发布到tf2相关的代码。 第一行包括所需的头文件。 首先,我们包含 geometry_msgs/msg/transform_stamped.hpp以访问 TransformStamped消息类型,我们将其发布到转换树。
#include "geometry_msgs/msg/transform_stamped.hpp"
之后,包含 rclcpp,因此可以使用其 rclcpp::Node类。
#include "rclcpp/rclcpp.hpp"

tf2::Quaternion是一个四元数类,它提供了将欧拉角转换为四元数的方便函数,反之亦然。 我们还包括tf2_ros/static_transform_broadcaster.h,以使用StaticTransformBroadcaster来简化静态转换的发布。

#include "tf2/LinearMath/Quaternion.h"
#include "tf2_ros/static_transform_broadcaster.h"

StaticFramePublisher类构造函数使用名称static_turtle_tf2_broadcaster初始化节点。 然后,创建StaticTransformBroadcaster,它将在启动时发送一个静态转换。

tf_static_broadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);

this->make_transforms(transformation);

这里我们创建了一个TransformStamped对象,它将是我们在填充后发送的消息。 在传递实际的转换值之前,我们需要给予它适当的元数据。

  1. We need to give the transform being published a timestamp and we’ll just stamp it with the current time, this->get_clock()->now()
    我们需要为发布的转换提供一个时间戳,我们将使用当前时间标记它,this->get_clock()->now()

  2. Then we need to set the name of the parent frame of the link we’re creating, in this case world
    然后我们需要设置我们正在创建的链接的父框架的名称,在本例中为world

  3. Finally, we need to set the name of the child frame of the link we’re creating
    最后,我们需要设置正在创建的链接的子帧的名称

geometry_msgs::msg::TransformStamped t;

t.header.stamp = this->get_clock()->now();
t.header.frame_id = "world";
t.child_frame_id = transformation[1];

这里我们填充海龟的6D姿势(平移和旋转)。

t.transform.translation.x = atof(transformation[2]);
t.transform.translation.y = atof(transformation[3]);
t.transform.translation.z = atof(transformation[4]);
tf2::Quaternion q;
q.setRPY(
  atof(transformation[5]),
  atof(transformation[6]),
  atof(transformation[7]));
t.transform.rotation.x = q.x();
t.transform.rotation.y = q.y();
t.transform.rotation.z = q.z();
t.transform.rotation.w = q.w();

最后,我们使用sendTransform()函数广播静态转换。

tf_static_broadcaster_->sendTransform(t);

2.2 Add dependencies

导航一级回到src/learning_tf2_cpp目录,其中已为您创建了CMakeLists.txtpackage.xml文件。

如 创建包 教程中所述,请确保填写<description><maintainer><license>标记:

<description>Learning tf2 with rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

2.3 CMakeLists.txt

将可执行文件添加到CMakeLists.txt并将其命名为static_turtle_tf2_broadcaster,稍后将与ros2 run一起使用。

add_executable(static_turtle_tf2_broadcaster src/static_turtle_tf2_broadcaster.cpp)
ament_target_dependencies(
   static_turtle_tf2_broadcaster
   geometry_msgs
   rclcpp
   tf2
   tf2_ros
)
Finally, add the install(TARGETS…) section so ros2 run can find your executable:
最后,添加install(TARGETS…)部分,以便ros2 run可以找到您的可执行文件:

install(TARGETS
   static_turtle_tf2_broadcaster
   DESTINATION lib/${PROJECT_NAME})

Writing a broadcaster (C++)

在接下来的两个教程中,我们将编写代码,从 TF2简介 教程。 之后,下面的教程将重点介绍如何使用更高级的tf2特性来扩展演示,包括在转换查找和时间旅行中使用超时

本教程假设您具有ROS 2的工作知识,并且您已经完成了tf2教程介绍和tf2静态广播器教程(C++)。 在前面的教程中,您学习了如何创建工作区和创建包。 您还创建了learning_tf2_cpp包,这是我们将继续工作的地方。

Task

write the broadcaster node

让我们首先创建源文件。 转到我们在上一个教程中创建的learning_tf2_cpp包。 在src目录中,通过输入以下命令下载示例广播器代码:

#include <functional>
#include <memory>
#include <sstream>
#include <string>

#include "geometry_msgs/msg/transform_stamped.hpp"
#include "rclcpp/rclcpp.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2_ros/transform_broadcaster.h"
#include "turtlesim/msg/pose.hpp"

class FramePublisher : public rclcpp::Node
{
public:
  FramePublisher()
  : Node("turtle_tf2_frame_publisher")
  {
    // Declare and acquire `turtlename` parameter
    turtlename_ = this->declare_parameter<std::string>("turtlename", "turtle");

    // Initialize the transform broadcaster
    tf_broadcaster_ =
      std::make_unique<tf2_ros::TransformBroadcaster>(*this);

    // Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose
    // callback function on each message
    std::ostringstream stream;
    stream << "/" << turtlename_.c_str() << "/pose";
    std::string topic_name = stream.str();

    subscription_ = this->create_subscription<turtlesim::msg::Pose>(
      topic_name, 10,
      std::bind(&FramePublisher::handle_turtle_pose, this, std::placeholders::_1));
  }

private:
  void handle_turtle_pose(const std::shared_ptr<turtlesim::msg::Pose> msg)
  {
    geometry_msgs::msg::TransformStamped t;

    // Read message content and assign it to
    // corresponding tf variables
    t.header.stamp = this->get_clock()->now();
    t.header.frame_id = "world";
    t.child_frame_id = turtlename_.c_str();

    // 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;
    t.transform.translation.y = msg->y;
    t.transform.translation.z = 0.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
    tf2::Quaternion q;
    q.setRPY(0, 0, msg->theta);
    t.transform.rotation.x = q.x();
    t.transform.rotation.y = q.y();
    t.transform.rotation.z = q.z();
    t.transform.rotation.w = q.w();

    // Send the transformation
    tf_broadcaster_->sendTransform(t);
  }

  rclcpp::Subscription<turtlesim::msg::Pose>::SharedPtr subscription_;
  std::unique_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_;
  std::string turtlename_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<FramePublisher>());
  rclcpp::shutdown();
  return 0;
}

现在,让我们看一下与将乌龟姿势发布到tf2相关的代码。 首先,我们定义并获取一个参数turtlename,它指定了一个海龟的名字,例如turtle1turtle2

turtlename_ = this->declare_parameter<std::string>("turtlename", "turtle");

之后,节点订阅主题turtleX/pose并对每个传入消息运行函数handle_turtle_pose

subscription_ = this->create_subscription<turtlesim::msg::Pose>(
  topic_name, 10,
  std::bind(&FramePublisher::handle_turtle_pose, this, _1));

现在,我们创建一个TransformStamped对象并给予适当的元数据。

  1. 我们需要给予发布的转换一个时间戳,我们将通过调用this->get_clock()->now()来标记当前时间。这将返回Node使用的当前时间。

  2. 然后我们需要设置我们正在创建的链接的父框架的名称,在本例中为world

  3. 最后,我们需要设置我们正在创建的链接的子节点的名称,在本例中,这是turtle本身的名称。

海龟姿态消息的处理函数广播这个海龟的平移和旋转,并将其发布为从帧world到帧turtleX的变换。

geometry_msgs::msg::TransformStamped t;

// Read message content and assign it to
// corresponding tf variables
t.header.stamp = this->get_clock()->now();
t.header.frame_id = "world";
t.child_frame_id = turtlename_.c_str();
// 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;
t.transform.translation.y = msg->y;
t.transform.translation.z = 0.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
tf2::Quaternion q;
q.setRPY(0, 0, msg->theta);
t.transform.rotation.x = q.x();
t.transform.rotation.y = q.y();
t.transform.rotation.z = q.z();
t.transform.rotation.w = q.w();

最后,我们把我们构造的转换传递给sendTransformTransformBroadcaster方法,它将负责广播。

// Send the transformation
tf_broadcaster_->sendTransform(t);

1.2 CMakeLists.txt

导航一级返回到learning_tf2_cpp目录,其中包含CMakeLists.txtpackage.xml文件。

现在打开CMakeLists.txt添加可执行文件并将其命名为turtle_tf2_broadcaster,稍后您将与ros2 run一起使用。

 
 
add_executable(turtle_tf2_broadcaster src/turtle_tf2_broadcaster.cpp)
ament_target_dependencies(
    turtle_tf2_broadcaster
    geometry_msgs
    rclcpp
    tf2
    tf2_ros
    turtlesim
)
install(TARGETS turtle_tf2_broadcaster DESTINATION lib/${PROJECT_NAME})

Writing a listener (C++)

在之前的教程中,我们创建了一个tf2广播器,将乌龟的姿势发布到tf2。在本教程中,我们将创建一个tf2侦听器来开始使用tf2。

本教程假设您已经完成了 tf2静态广播器教程(C++)和tf2广播器教程(C++)。 在上一个教程中,我们创建了一个learning_tf2_cpp包,这是我们将继续工作的地方。

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "geometry_msgs/msg/transform_stamped.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "rclcpp/rclcpp.hpp"
#include "tf2/exceptions.h"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/buffer.h"
#include "turtlesim/srv/spawn.hpp"

using namespace std::chrono_literals;

class FrameListener : public rclcpp::Node
{
public:
  FrameListener()
  : Node("turtle_tf2_frame_listener"),
    turtle_spawning_service_ready_(false),
    turtle_spawned_(false)
  {
    // Declare and acquire `target_frame` parameter
    target_frame_ = this->declare_parameter<std::string>("target_frame", "turtle1");

    tf_buffer_ =
      std::make_unique<tf2_ros::Buffer>(this->get_clock());
    tf_listener_ =
      std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);

    // Create a client to spawn a turtle
    spawner_ =
      this->create_client<turtlesim::srv::Spawn>("spawn");

    // Create turtle2 velocity publisher
    publisher_ =
      this->create_publisher<geometry_msgs::msg::Twist>("turtle2/cmd_vel", 1);

    // Call on_timer function every second
    timer_ = this->create_wall_timer(
      1s, std::bind(&FrameListener::on_timer, this));
  }

private:
  void on_timer()
  {
    // Store frame names in variables that will be used to
    // compute transformations
    std::string fromFrameRel = target_frame_.c_str();
    std::string toFrameRel = "turtle2";

    if (turtle_spawning_service_ready_) {
      if (turtle_spawned_) {
        geometry_msgs::msg::TransformStamped t;

        // Look up for the transformation between target_frame and turtle2 frames
        // and send velocity commands for turtle2 to reach target_frame
        try {
          t = tf_buffer_->lookupTransform(
            toFrameRel, fromFrameRel,
            tf2::TimePointZero);
        } catch (const tf2::TransformException & ex) {
          RCLCPP_INFO(
            this->get_logger(), "Could not transform %s to %s: %s",
            toFrameRel.c_str(), fromFrameRel.c_str(), ex.what());
          return;
        }

        geometry_msgs::msg::Twist msg;

        static const double scaleRotationRate = 1.0;
        msg.angular.z = scaleRotationRate * atan2(
          t.transform.translation.y,
          t.transform.translation.x);

        static const double scaleForwardSpeed = 0.5;
        msg.linear.x = scaleForwardSpeed * sqrt(
          pow(t.transform.translation.x, 2) +
          pow(t.transform.translation.y, 2));

        publisher_->publish(msg);
      } else {
        RCLCPP_INFO(this->get_logger(), "Successfully spawned");
        turtle_spawned_ = true;
      }
    } else {
      // Check if the service is ready
      if (spawner_->service_is_ready()) {
        // Initialize request with turtle name and coordinates
        // Note that x, y and theta are defined as floats in turtlesim/srv/Spawn
        auto request = std::make_shared<turtlesim::srv::Spawn::Request>();
        request->x = 4.0;
        request->y = 2.0;
        request->theta = 0.0;
        request->name = "turtle2";

        // Call request
        using ServiceResponseFuture =
          rclcpp::Client<turtlesim::srv::Spawn>::SharedFuture;
        auto response_received_callback = [this](ServiceResponseFuture future) {
            auto result = future.get();
            if (strcmp(result->name.c_str(), "turtle2") == 0) {
              turtle_spawning_service_ready_ = true;
            } else {
              RCLCPP_ERROR(this->get_logger(), "Service callback result mismatch");
            }
          };
        auto result = spawner_->async_send_request(request, response_received_callback);
      } else {
        RCLCPP_INFO(this->get_logger(), "Service is not ready");
      }
    }
  }

  // Boolean values to store the information
  // if the service for spawning turtle is available
  bool turtle_spawning_service_ready_;
  // if the turtle was successfully spawned
  bool turtle_spawned_;
  rclcpp::Client<turtlesim::srv::Spawn>::SharedPtr spawner_{nullptr};
  rclcpp::TimerBase::SharedPtr timer_{nullptr};
  rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr publisher_{nullptr};
  std::shared_ptr<tf2_ros::TransformListener> tf_listener_{nullptr};
  std::unique_ptr<tf2_ros::Buffer> tf_buffer_;
  std::string target_frame_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<FrameListener>());
  rclcpp::shutdown();
  return 0;
}

要了解产卵海龟背后的服务是如何工作的,请参考编写简单的服务和客户端(C++)教程。现在,让我们看一下与访问框架转换相关的代码。 tf2_ros包含一个TransformListener头文件实现,它使接收转换的任务变得更容易。

#include "tf2_ros/transform_listener.h"

在这里,我们创建一个TransformListener对象。一旦创建了侦听器,它就开始通过连接接收tf2转换,并将它们缓冲最多10秒。

tf_listener_ =
  std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);

最后,我们向侦听器查询特定的转换。我们使用以下参数调用lookup_transform方法:

  1. Target frame目标帧

  2. Source frame源帧

  3. The time at which we want to transform我们想要转变的时间

提供tf2::TimePointZero()只会让我们得到最新的可用转换。 所有这些都被包装在一个try-catch块中,以处理可能的异常。

t = tf_buffer_->lookupTransform(
  toFrameRel, fromFrameRel,
  tf2::TimePointZero);

使用文本编辑器打开名为turtle_tf2_demo.launch.py的启动文件,向启动描述中添加两个新节点,添加一个启动参数,然后添加导入。 生成的文件应该如下所示:

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='sim'
        ),
        Node(
            package='learning_tf2_cpp',
            executable='turtle_tf2_broadcaster',
            name='broadcaster1',
            parameters=[
                {'turtlename': 'turtle1'}
            ]
        ),
        DeclareLaunchArgument(
            'target_frame', default_value='turtle1',
            description='Target frame name.'
        ),
        Node(
            package='learning_tf2_cpp',
            executable='turtle_tf2_broadcaster',
            name='broadcaster2',
            parameters=[
                {'turtlename': 'turtle2'}
            ]
        ),
        Node(
            package='learning_tf2_cpp',
            executable='turtle_tf2_listener',
            name='listener',
            parameters=[
                {'target_frame': LaunchConfiguration('target_frame')}
            ]
        ),
    ])

Adding a frame (C++)

在前面的教程中,我们通过编写tf2广播器和tf2侦听器重新创建了turtle演示。 本教程将教你如何添加额外的固定和动态帧到转换树。 事实上,在tf2中添加帧与创建tf2广播器非常相似,但此示例将向您展示tf2的一些附加功能。

对于许多与转换相关的任务,在局部框架内思考更容易。 例如,在激光扫描仪的中心处的帧中的激光扫描测量是最容易推理的。 tf2允许您为系统中的每个传感器、链接或关节定义局部框架。 当从一个帧变换到另一个帧时,tf2将处理引入的所有隐藏中间帧变换。

tf2 Tree

TF2建立帧的树结构,并且因此不允许帧结构中的闭环。 这意味着一个框架只有一个父框架,但它可以有多个子框架。 目前,我们的tf2树包含三个帧:worldturtle1turtle2。 这两个turtle帧是world帧的子帧。 如果我们想在tf2中添加一个新的帧,现有的三个帧中的一个需要是父帧,新的帧将成为它的子帧。

1 Write the fixed frame broadcaster

在我们的turtle示例中,我们将添加一个新帧carrot1,它将是turtle1的子帧。 这一帧将作为第二只海龟的目标。

让我们首先创建源文件。转到我们在前面的教程中创建的learning_tf2_cpp包。 输入以下命令下载固定帧广播器代码:

#include <chrono>
#include <functional>
#include <memory>

#include "geometry_msgs/msg/transform_stamped.hpp"
#include "rclcpp/rclcpp.hpp"
#include "tf2_ros/transform_broadcaster.h"

using namespace std::chrono_literals;

class FixedFrameBroadcaster : public rclcpp::Node
{
public:
  FixedFrameBroadcaster()
  : Node("fixed_frame_tf2_broadcaster")
  {
    tf_broadcaster_ = std::make_shared<tf2_ros::TransformBroadcaster>(this);
    timer_ = this->create_wall_timer(
      100ms, std::bind(&FixedFrameBroadcaster::broadcast_timer_callback, this));
  }

private:
  void broadcast_timer_callback()
  {
    geometry_msgs::msg::TransformStamped t;

    t.header.stamp = this->get_clock()->now();
    t.header.frame_id = "turtle1";
    t.child_frame_id = "carrot1";
    t.transform.translation.x = 0.0;
    t.transform.translation.y = 2.0;
    t.transform.translation.z = 0.0;
    t.transform.rotation.x = 0.0;
    t.transform.rotation.y = 0.0;
    t.transform.rotation.z = 0.0;
    t.transform.rotation.w = 1.0;

    tf_broadcaster_->sendTransform(t);
  }

rclcpp::TimerBase::SharedPtr timer_;
  std::shared_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<FixedFrameBroadcaster>());
  rclcpp::shutdown();
  return 0;
}

2 Write the dynamic frame broadcaster

我们在本教程中发布的额外帧是一个固定的帧,它不会随时间的推移而相对于父帧发生变化。 然而,如果你想发布一个移动的帧,你可以编写广播程序来随着时间的推移改变帧。 让我们改变我们的carrot1帧,以便它随着时间的推移相对于turtle1帧而改变。 现在,输入以下命令下载动态帧广播器代码:

#include <chrono>
#include <functional>
#include <memory>

#include "geometry_msgs/msg/transform_stamped.hpp"
#include "rclcpp/rclcpp.hpp"
#include "tf2_ros/transform_broadcaster.h"

using namespace std::chrono_literals;

const double PI = 3.141592653589793238463;

class DynamicFrameBroadcaster : public rclcpp::Node
{
public:
  DynamicFrameBroadcaster()
  : Node("dynamic_frame_tf2_broadcaster")
  {
    tf_broadcaster_ = std::make_shared<tf2_ros::TransformBroadcaster>(this);
    timer_ = this->create_wall_timer(
      100ms, std::bind(&DynamicFrameBroadcaster::broadcast_timer_callback, this));
  }

private:
  void broadcast_timer_callback()
  {
    rclcpp::Time now = this->get_clock()->now();
    double x = now.seconds() * PI;

    geometry_msgs::msg::TransformStamped t;
    t.header.stamp = now;
    t.header.frame_id = "turtle1";
    t.child_frame_id = "carrot1";
    t.transform.translation.x = 10 * sin(x);
    t.transform.translation.y = 10 * cos(x);
    t.transform.translation.z = 0.0;
    t.transform.rotation.x = 0.0;
    t.transform.rotation.y = 0.0;
    t.transform.rotation.z = 0.0;
    t.transform.rotation.w = 1.0;

    tf_broadcaster_->sendTransform(t);
  }

  rclcpp::TimerBase::SharedPtr timer_;
  std::shared_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<DynamicFrameBroadcaster>());
  rclcpp::shutdown();
  return 0;
}

Using time (C++)

目标:学习如何在特定时间获得转换,并使用lookupTransform()函数等待tf2树上的转换可用。

在前面的教程中,我们通过编写tf2广播器和tf2侦听器重新创建了turtle演示。 我们还学习了如何向变换树添加新帧,并学习了tf2如何跟踪坐标帧树。 这个树会随着时间的推移而变化,tf2会为每个变换存储一个时间快照(默认情况下最多10秒)。 到目前为止,我们使用lookupTransform()函数来访问tf2树中最新的可用变换,而不知道该变换是在什么时候记录的。 本教程将教您如何在特定时间获得变换。

1 tf2 and time

让我们回到最后的部分 添加框架教程 . 转到learning_tf2_cpp包。 打开turtle_tf2_listener.cpp并查看lookupTransform()调用:

transformStamped = tf_buffer_->lookupTransform(
   toFrameRel,
   fromFrameRel,
   tf2::TimePointZero);

您可以看到我们通过调用tf2::TimePointZero指定了一个等于0的时间。

对于tf2,时间0意味着缓冲器中的“最新可用”变换。 现在,更改此行以获取当前时间的变换,this->get_clock()->now()

rclcpp::Time now = this->get_clock()->now();
transformStamped = tf_buffer_->lookupTransform(
   toFrameRel,
   fromFrameRel,
   now);

要理解为什么会发生这种情况,我们需要了解缓冲区是如何工作的。 首先,每个接收器具有缓冲器,在该缓冲器中存储来自不同tf2广播器的所有坐标变换。 其次,当广播器发送转换时,转换进入缓冲区需要一些时间(通常是几毫秒)。 因此,当您在时间“now”请求帧转换时,您应该等待几毫秒以等待该信息到达。

2 Wait for transforms

tf2提供了一个很好的工具,它会一直等到转换可用。 您可以通过向lookupTransform()添加一个超时参数来使用它。 要修复此问题,请按如下所示编辑代码(添加最后一个超时参数):

rclcpp::Time now = this->get_clock()->now();
transformStamped = tf_buffer_->lookupTransform(
   toFrameRel,
   fromFrameRel,
   now,
   50ms);

lookupTransform()可以接受四个参数,其中最后一个是可选的超时。 它将阻塞该持续时间,等待超时。

你应该注意到lookupTransform()实际上会阻塞,直到两个turtle之间的转换可用(这通常需要几毫秒)。 一旦超时(在本例中为50毫秒),只有转换仍然不可用时才会引发异常。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/424201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自学大数据第十天~Hbase

随着数据量的增多,数据的类型也不像原来那样都是结构化数据,还有非结构化数据; Hbase时google 的bigtable的开源实现, BigtableHbase文件存储系统GFSHDFS海量数据处理MRMR协同管理服务chubbyzookeeper虽然有了HDFS和MR,但是对于数据的实时处理是比较困难的,没有办法应对数据的爆…

实现3D动画

一、transform Transform是形变的意思&#xff08;通常也叫变换&#xff09;&#xff0c;transformer就是变形金刚 常见的函数transform function有&#xff1a; 平移&#xff1a;translate(x, y) 缩放&#xff1a;scale(x, y) 旋转&#xff1a;rotate(deg) 倾斜&#xff1a;sk…

病毒丨3601lpk劫持病毒分析

作者&#xff1a;黑蛋 一、病毒简介 文件名称&#xff1a; 1f3e836b4677a6df2c2d34d3c6413df2c5e448b5bc1d5702f2a96a7f6ca0d7fb 文件类型(Magic)&#xff1a; PE32 executable (GUI) Intel 80386, for MS Windows 文件大小&#xff1a; 52.50KB SHA256&#xff1a; 1f3e836b4…

面试字节,三面HR天坑,想不到自己也会阴沟里翻船....

阎王易见&#xff0c;小鬼难缠。我一直相信这个世界上好人居多&#xff0c;但是也没想到自己也会在阴沟里翻船。我感觉自己被字节跳动的HR坑了。 在这里&#xff0c;我只想告诫大家&#xff0c;offer一定要拿到自己的手里才是真的&#xff0c;口头offer都是不牢靠的&#xff0…

可实现电控调光、宽视场角,FlexEnable曲面液晶解析

曲面显示屏、可变焦液晶透镜&#xff0c;这些似乎是属于未来AR/VR的趋势&#xff0c;而实际上已经有公司在提供此类技术&#xff0c;而这将有望对AR/VR产生重要影响。AR/VR光学专家Karl Guttag指出&#xff0c;其在CES 2023看到了一家专注于柔性显示、传感器的有机电子公司&…

传统机器学习(五)—分类、回归模型的常见评估指标

传统机器学习—分类、回归模型的常见评估指标 一、分类模型的常见评估指标 1.1 混淆矩阵 混淆矩阵&#xff0c;在无监督学习中被称为匹配矩阵(matching matrix)&#xff0c;之所以叫混淆矩阵&#xff0c;是因为我们能够很容易从图表中看到分类器有没有将样本的类别给混淆了。…

E4--光纤接口通信测试应用2023-04-17

1.场景 使用两块开发板A和B&#xff0c;通过光纤接口将在A板上ROM中存储的图片数据转发到B板并显示在B板连接的显示屏上&#xff0c;实现光纤接口通信。 具体场景是&#xff0c;由于A735T片上资源有限&#xff0c;因此ROM IP存储了一张1024*600&#xff08;LVDS屏幕&#xff0…

【Linux】调试器---gdb的使用

文章目录一.背景知识二.安装gdb三.gdb的用法使用须知gdb的常用指令1.进入调试2.退出调试操作3.显示源代码4.设置断点breakPoint5.查看断点信息/禁用断点/开启断点/删除断点6.运行程序&#xff0c;开始调试run7.查看变量8.其它重要命令一.背景知识 程序的发布方式有两种&#x…

【并发】Volatile作用详解

volatile保证变量的可见性禁止指令重排不保证原子性如何保证原子性volatile volatile关键字可以保证变量的可见性。 被volatile修饰的变量是共享的&#xff0c;因此不会将该变量上的操作与其他内存操作一起重排序。 无法保证原子性 保证变量的可见性 当多个线程访问同一个…

217页企业大数据能力平台建设技术方案(word可编辑)

1.1.1.1 建设方案架构 数据治理平台覆盖了从标准、设计、采集、开发到使用&#xff0c;再到交付的全数据治理流程&#xff0c;为组织提供了一站式数据治理运营平台&#xff0c;可满足数据共享交换、数据开发、数据运营多种应用场景&#xff0c;并通过构建数据工厂&#xff0c;实…

基于ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气等领域数据分析能力与项目科研水平

目录 专题一、空间数据获取与制图 专题二、ArcGIS专题地图制作 专题三、空间数据采集与处理 专题四、遥感数据处理与应用 专题五、DEM数据处理与应用 专题六、采样数据处理与应用 专题七、土地利用处理与应用 专题八、土地利用景观格局分析 专题九、土壤数据处理与应用…

【音视频第12天】GCC论文阅读(3)

A Google Congestion Control Algorithm for Real-Time Communication draft-alvestrand-rmcat-congestion-03论文理解 看中文的GCC算法一脸懵。看一看英文版的&#xff0c;找一找感觉。 目录Abstract1. Introduction1.1 Mathematical notation conventions2. System model2.1 …

代码随想录第17天 | 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

654.最大二叉树 /*** Definition for a binary tree node.* function TreeNode(val, left, right) {* this.val (valundefined ? 0 : val)* this.left (leftundefined ? null : left)* this.right (rightundefined ? null : right)* }*/ /*** param {numbe…

2.docker-本地镜像发布

1.发布到阿里云 前往 容器镜像服务 (aliyun.com) 进入容器镜像服务 1.创建命名空间 2.创建镜像仓库 3.进入仓库管理页面获得脚本 # 需要输入密码,终端输出 Login Succeeded 则为登录成功 docker login --username用户名 registry.cn-hangzhou.aliyuncs.com# 标记 docker tag …

TCP 协议的相关特性

TCP 协议的相关特性&#x1f50e;TCP协议的特点&#x1f50e;TCP协议段格式&#x1f50e;TCP协议的相关特性确认应答(ACK)超时重传三次握手四次挥手三次挥手与四次握手的注意事项&#x1f50e;结尾TCP(Transmission Control Protocol) 传输控制协议 &#x1f50e;TCP协议的特点…

Hbase伪分布安装配置

Hbase安装配置 文章目录Hbase安装配置Hbase安装前提下载Hbase压缩包软件版本兼容性Hadoop和HbaseHbase和JDK软件安装软件位置创建数据保存和日志保存文件夹修改配置文件修改hbase-site.xml文件修改hbase-env.sh文件修改~/.bashrc文件启动hbase并验证权限问题Permission denied修…

外源6-BA在缓解多花黄精种子出苗过程中的代谢及转录组学变化

文章标题&#xff1a;Transcriptomics and metabolomics changes triggered by exogenous 6-benzylaminopurine in relieving epicotyl dormancy of Polygonatum cyrtonema Hua seeds 发表期刊&#xff1a;Frontiers in Plant Science 影响因子&#xff1a;6.627 作者单位&a…

电镀废水中的三价铬去除效率

电镀废水中铬的主要存在形式为六价铬&#xff08;绝大多数&#xff09;和三价铬&#xff0c;二者在一定条件下可互相转换&#xff0c;且二者都可能具有致癌左右&#xff0c;有所区别的是六价铬的毒性大约是三价铬毒性的100倍。 目前电镀废水中对铬的处理工艺一般为先将毒性较大…

KD2684S绕组匝间故障检测仪

一、产品简介 KD2684S匝间冲击耐压试验仪适用于电机、变压器、电器线圈等这些由漆包线绕制的产品。因漆包线的绝缘涂敷层本身存在着质量问题&#xff0c;以及在绕线、嵌线、刮线、接头端部整形、绝缘浸漆、装配等工序工艺中不慎而引起绝缘层的损伤等&#xff0c;都会造成线圈层…

【高危】Apache Linkis <1.3.2 存在反序列化漏洞(CVE-2023-29216)

漏洞描述 Apache Linkis 是一个用于将上层应用与底层数据引擎解耦&#xff0c;提供标准化接口的中间件。 该项目受影响版本存在存在反序列化漏洞&#xff0c;由于SqlConnection.java中未对host、port、username,、password等参数进行充分过滤&#xff0c;当恶意用户完全控制应…