首页 > 编程语言 >ros2笔记-5.3 C++中地图坐标系变换

ros2笔记-5.3 C++中地图坐标系变换

时间:2025-01-11 19:59:34浏览次数:3  
标签:5.3 rclcpp transform C++ broadcaster tf include ros2 tf2

本节继续跟小鱼老师学习5.3.需求背景:

地图坐标系为map,机器人坐标系为base link,目标点为target_point,已知map到base link之间的关系,map到target_point关系。要控制机器人到达目标点,就需要知道目标点 target_point和机器人base_link 之间的关系。

5.3.1 通过C++发布静态TF

目标点是固定的。采用静态TF发布map与target_point之间的关系。

在chapt/chapt5_ws/src下创建功能包

ros2 pkg create demo_cpp_tf --build-type ament_cmake --dependencies rclcpp tf2 tf2_ros geometry_msgs tf2_geometry_msgs --license Apache-2.0

src/demo_cpp_tf/src 目录下新建文件:static_tf_broadcaster.cpp文件,代码如下:

#include <memory>
#include "geometry_msgs/msg/transform_stamped.hpp"  // 提供消息接口
#include "rclcpp/rclcpp.hpp"
#include "tf2/LinearMath/Quaternion.h"  // 提供 tf2::Quaternion 类
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"  // 提供消息类型转换函数
#include "tf2_ros/static_transform_broadcaster.h"  // 提供静态坐标广播器类

class staticTFBroadcaster : public rclcpp::Node
{
private:
    std::shared_ptr<tf2_ros::StaticTransformBroadcaster> broadcaster_;
public:
    staticTFBroadcaster():Node("tf_broadcaster_node"){
        //创建静态广播发布器并进行发布
        broadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);
        this->publish_tf();
    }

    void publish_tf(){
        geometry_msgs::msg::TransformStamped transform;
        transform.header.stamp = this->get_clock()->now();
        transform.header.frame_id ="map";
        transform.child_frame_id = "target_point";
        transform.transform.translation.x = 5.0;
        transform.transform.translation.y = 3.0;
        transform.transform.translation.z = 0.0;
        tf2::Quaternion quat;
        quat.setRPY(0,0,60*M_PI/180);//弧度制欧拉角转换四元数
        transform.transform.rotation = tf2::toMsg(quat);
        broadcaster_->sendTransform(transform);
    }
 
};

int main(int argc,char** argv){
    rclcpp::init(argc,argv);
    auto node = std::make_shared<staticTFBroadcaster>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

主要逻辑就是定义类staticTFBroadcaster,声明了一个静态坐标广播的共享指针,在构造函数进行初始化,然后调用publish_tf进行静态TF发布,里面就是拼装参数,注意角度的转换,先转为弧度值,再把欧拉角转为四元数。

参照上一节课python版,静态发布只需要发布一次,ros2 为订阅者保留数据,新的订阅者可以直接获取保留的数据。

在CMakeLists.txt注册节点:

cmake_minimum_required(VERSION 3.8)
project(demo_cpp_tf)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)

add_executable(static_tf_broadcaster src/static_tf_broadcaster.cpp)
ament_target_dependencies(static_tf_broadcaster rclcpp tf2 tf2_ros geometry_msgs tf2_geometry_msgs)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

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


ament_package()

构建运行

bohu@bohu-TM1701:~/chapt5/chapt5_ws$ colcon build
Starting >>> demo_cpp_tf
Starting >>> demo_python_tf
Finished <<< demo_python_tf [1.21s]                                                 
Finished <<< demo_cpp_tf [7.94s]                     

Summary: 2 packages finished [8.67s]
bohu@bohu-TM1701:~/chapt5/chapt5_ws$ source install/setup.bash 
bohu@bohu-TM1701:~/chapt5/chapt5_ws$ ros2 run demo_cpp_tf static_tf_broadcaster 
^C[INFO

可以查看发布的话题

bohu@bohu-TM1701:~/chapt5/chapt5_ws$ ros2 topic echo /tf_static
transforms:
- header:
    stamp:
      sec: 1736563886
      nanosec: 445839509
    frame_id: map
  child_frame_id: target_point
  transform:
    translation:
      x: 5.0
      y: 3.0
      z: 0.0
    rotation:
      x: 0.0
      y: 0.0
      z: 0.49999999999999994
      w: 0.8660254037844387
---

5.3.2 通过C++发布动态TF

动态TF对比静态就是需要有定时器不断发布。

src/demo_cpp_tf/src 目录下新建文件 dynamic_tf_broadcaster.cpp

#include <memory>
#include "geometry_msgs/msg/transform_stamped.hpp"  // 提供消息接口
#include "rclcpp/rclcpp.hpp"
#include "tf2/LinearMath/Quaternion.h"  // 提供 tf2::Quaternion 类
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"  // 提供消息类型转换函数
#include "tf2_ros/transform_broadcaster.h"  // 提供坐标广播器类
#include "chrono" //时间类头文件
using namespace std::chrono_literals;//时间单位 2s,5ms

class DynamicTFBroadcaster : public rclcpp::Node
{
private:
    std::shared_ptr<tf2_ros::TransformBroadcaster> broadcaster_;
    rclcpp::TimerBase::SharedPtr timer_;
public:
    DynamicTFBroadcaster():Node("dynamic_tf_broadcaster"){
        broadcaster_ = std::make_shared<tf2_ros::TransformBroadcaster>(this);
        timer_ = create_wall_timer(10ms, std::bind(&DynamicTFBroadcaster::publish_tf,this));
    }

    void publish_tf(){
        geometry_msgs::msg::TransformStamped transform;
        transform.header.stamp = this->get_clock()->now();
        transform.header.frame_id ="map";
        transform.child_frame_id = "base_link";
        transform.transform.translation.x = 2.0;
        transform.transform.translation.y = 3.0;
        transform.transform.translation.z = 0.0;
        tf2::Quaternion quat;
        quat.setRPY(0,0,30*M_PI/180);//弧度制欧拉角转换四元数
        transform.transform.rotation = tf2::toMsg(quat);
        broadcaster_->sendTransform(transform);
    }
 
};

int main(int argc,char** argv){
    rclcpp::init(argc,argv);
    auto node = std::make_shared<DynamicTFBroadcaster>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

小鱼老师讲动态的时候也讲了定时器的区别,吧静态的拿来改了下。基本上差不多。

在CMakeLists.txt注册static_tf_broadcaster节点,构建并运行

bohu@bohu-TM1701:~/chapt5/chapt5_ws$ colcon build
Starting >>> demo_cpp_tf
Starting >>> demo_python_tf
Finished <<< demo_python_tf [1.18s]                                 
Finished <<< demo_cpp_tf [6.53s]                     

Summary: 2 packages finished [6.87s]
bohu@bohu-TM1701:~/chapt5/chapt5_ws$ source install/setup.bash 
bohu@bohu-TM1701:~/chapt5/chapt5_ws$ ros2 run demo_cpp_tf dynamic_tf_broadcaster 

新开终端查看动态发布 ros2 topic ech /tf

transforms:
- header:
    stamp:
      sec: 1736565823
      nanosec: 655117538
    frame_id: map
  child_frame_id: base_link
  transform:
    translation:
      x: 2.0
      y: 3.0
      z: 0.0
    rotation:
      x: 0.0
      y: 0.0
      z: 0.25881904510252074
      w: 0.9659258262890683
---
transforms:
- header:
    stamp:
      sec: 1736565823
      nanosec: 665037284
    frame_id: map
  child_frame_id: base_link
  transform:
    translation:
      x: 2.0
      y: 3.0
      z: 0.0
    rotation:
      x: 0.0
      y: 0.0
      z: 0.25881904510252074
      w: 0.9659258262890683
---

如果此时,静态TF、动态TF都在运行,可以使用命令行查看base_link和target_poit之间的关系。

5.3.3 通过C++查询TF关系

src/demo_cpp_tf/src 目录下新建文件tf_listener.cpp,代码如下:

#include <memory>
#include "geometry_msgs/msg/transform_stamped.hpp"  // 提供消息接口
#include "rclcpp/rclcpp.hpp"
#include "tf2/LinearMath/Quaternion.h"  // 提供 tf2::Quaternion 类
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"  // 提供消息类型转换函数
#include "tf2/utils.h"  //提供tf2 :getEulerYPR 函数
#include "tf2_ros/buffer.h" //提供tf缓冲类buffer
#include "tf2_ros/transform_listener.h"  // 提供坐标广播器类
#include "chrono" //时间类头文件
using namespace std::chrono_literals;//时间单位 2s,5ms

class TFListener : public rclcpp::Node
{
private:
    std::shared_ptr<tf2_ros::Buffer> buffer_;
    std::shared_ptr<tf2_ros::TransformListener> listener_;
    rclcpp::TimerBase::SharedPtr timer_;
public:
    TFListener():Node("tf_listener"){
        buffer_ = std::make_shared<tf2_ros::Buffer>(this->get_clock());
        listener_ = std::make_shared<tf2_ros::TransformListener>(*buffer_,this);
        timer_ = create_wall_timer(10ms, std::bind(&TFListener::getTransform,this));
    }

    void getTransform(){
        try{
            //
            const auto transform = buffer_->lookupTransform("base_link","target_point",
            this->get_clock()->now(),rclcpp::Duration::from_seconds(1.0f));
            const auto &translation = transform.transform.translation;
            const auto &roration = transform.transform.rotation;
            double y,p,r;
            tf2::getEulerYPR(roration,y,p,r);
            RCLCPP_INFO(get_logger(),"平移:(%f,%F,%f)",translation.x,translation.y,translation.z);
            RCLCPP_INFO(get_logger(),"旋转:(%f,%F,%f)",r,p,y);


        }catch(tf2::TransformException &ex){
            RCLCPP_WARN(get_logger(),"ex:%s",ex.what());
        }
    }
 
};

int main(int argc,char** argv){
    rclcpp::init(argc,argv);
    auto node = std::make_shared<TFListener>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

基本结构跟动态的类似,小鱼老师视频也是吧动态拷过来改的。在构造函数对listener_初始化时候,第一个参数传的是Buffer对象的引用。随后创建定时器5秒调用一次。演示的 时候开始设置为1秒后面的查询函数getTransform就要1秒,这样会造成阻塞查不到数据.

其中获取结果 buffer_.lookup_transform  获取base_link到target_link的坐标变化,接口参数如下

  TF2_ROS_PUBLIC
  geometry_msgs::msg::TransformStamped
  lookupTransform(
    const std::string & target_frame, const std::string & source_frame,
    const rclcpp::Time & time,
    const rclcpp::Duration timeout = rclcpp::Duration::from_nanoseconds(0)) const
  {
    return lookupTransform(target_frame, source_frame, fromRclcpp(time), fromRclcpp(timeout));
  }

还有一个点,        tf2::getEulerYPR(roration,y,p,r); 是 调用了utils函数来吧四元数转换为欧拉角。

所以roration是输入,y,pr 是输出。跟常见的入参出参格式不太一样,不太习惯。

最后,在CMakeLists.txt对tf_lisenter进行注册,重新构建后运行。

ros2 run demo_cpp_tf dynamic_tf_broadcaster

ros2 run demo_cpp_tf static_tf_broadcaster

bohu@bohu-TM1701:~/chapt5/chapt5_ws$ ros2 run demo_cpp_tf tf_listener 
[INFO] [1736573518.585501251] [tf_listener]: 平移:(2.598076,-1.500000,0.000000)
[INFO] [1736573518.585884521] [tf_listener]: 旋转:(0.000000,-0.000000,0.523599)
[INFO] [1736573518.596441302] [tf_listener]: 平移:(2.598076,-1.500000,0.000000)
[INFO] [1736573518.596568749] [tf_listener]: 旋转:(0.000000,-0.000000,0.523599)

标签:5.3,rclcpp,transform,C++,broadcaster,tf,include,ros2,tf2
From: https://blog.csdn.net/bohu83/article/details/145065128

相关文章

  • 深入了解 C++ 函数模板
    函数模板是C++泛型编程的基石之一,它使得程序员可以编写类型无关的代码,提升代码复用性和灵活性.在本文中,我们将从基础到进阶,一步步解析C++函数模板的核心概念,常见用法及其限制.1.初识函数模板1.1定义函数模板:以Max函数为例函数模板允许我们定义一个......
  • 【DNS攻防】深入探讨DNS数据包注入与DNS中毒攻击检测 (C/C++代码实现)
    DNS数据包注入和DNS中毒攻击是网络安全领域中的两个重要主题。DNS(域名系统)是互联网中的一项核心服务,负责将域名转换为与之相对应的IP地址。DNS数据包注入是指攻击者通过篡改或伪造DNS请求或响应数据包来干扰或破坏DNS服务的过程。攻击者可通过注入恶意数据包来改变DNS解析结果,将......
  • C++学习
    引入根据菜鸟教程学习,供自用打印helloworld#include<iostream>usingnamespacestd;intmain(){cout<<"HelloWorld"<<"\n";return0;}现象PSC:\Users\86177>cd"d:\DeskTop\cppstudy\";if($?){......
  • msys2 + vscode + C++
    MSYS2isacollectionoftoolsandlibrariesprovidingyouwithaneasy-to-useenvironmentforbuilding,installingandrunningnativeWindowssoftware.msys2在windows上提供了类似linux的构建环境,可以方便地安装开发所需的各种库文件。网址为https://www.msys2.org/......
  • C/C++新春烟花
    系列文章序号直达链接1C/C++爱心代码2C/C++跳动的爱心3C/C++李峋同款跳动的爱心代码4C/C++满屏飘字表白代码5C/C++大雪纷飞代码6C/C++烟花代码7C/C++黑客帝国同款字母雨8C/C++樱花树代码9C/C++奥特曼代码10C/C++精美圣诞树11C/C++俄罗斯方块小游戏12C/C++贪吃蛇小游戏13C/C++......
  • c++ imu
      #include<iostream>#include<cmath>#include<chrono>#include<thread>#include<random>//Simplehelper:wrapsangleto[-pi,pi]doublewrapToPi(doubleangle){while(angle>M_PI){angle-=2.0*......
  • 【华为OD技术面试手撕真题】- C++手撕技术面试八股文(1)
    文章目录一、delete和delete[]的区别二、const解释一下其作用1.定义常量2.修饰指针3.修饰函数参数4.修饰类成员函数三、struct和class的区别1.默认访问控制2.继承的默认访问控制四、#include<file.h>#include"file.h"的区别五、C++文件......
  • C++语言的学习路线
    C++语言的学习路线C++是一种强大的高级编程语言,广泛应用于系统软件、游戏开发、嵌入式系统和高性能应用等多个领域。由于其丰富的功能和灵活性,C++是一门值得深入学习的语言。本文旨在为初学者制定一条系统的学习路线,帮助他们循序渐进地掌握C++语言。第一阶段:基础知识1.......
  • 1.10日学习笔记之C++的类
    ·类其实就是一种数据类型,和结构相似。类的成员包括两类,属性(成员变量)和行为(成员函数)。·成员函数定义的两种方法(可能有多种,觉得这两种比较常用)1、将类的成员函数定义在类体内,如classCPerson{public:shortage;shortgetage(){returnage;}};2、将......
  • 【C++】穿越编程岁月,细品C++进化轨迹,深化入门基石(续章)——揭秘函数缺省参数的魅力、函
    文章目录一、函数缺省参数二、函数重载三、引用1.引用的概念和定义2.引用的特性3.引用的使用4.const引用5.指针和引用的关系四、inline内联函数和nullptr1.inline2.nullptr一、函数缺省参数   缺省参数其实就是默认参数,它是声明或定义函数时为函数的参数指定......