当前位置: 首页 > article >正文

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

本节继续跟小鱼老师学习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)


http://www.kler.cn/a/501278.html

相关文章:

  • 基于微信小程序的汽车销售系统的设计与实现springboot+论文源码调试讲解
  • ubuntu20下编译linux1.0 (part1)
  • 硬件设计-七位半电压表硬件方案(下)
  • python无需验证码免登录12306抢票 --selenium(2)
  • 每日学习30分轻松掌握CursorAI:多文件编辑与Composer功能
  • 《ROS2 机器人开发 从入门道实践》 鱼香ROS2——第6章内容
  • 分享几个高清无水印国外视频素材网站
  • 【ASP.NET学习】ASP.NET MVC基本编程
  • 电脑提示directx错误导致玩不了游戏怎么办?dx出错的解决方法
  • Python差分
  • .NET | SCM权限维持在红队实战中的应用
  • 若依框架--数据字典设计使用和前后端代码分析
  • 关于电商商品详情 API 接口 JSON 格式返回数据解析的示例
  • 正则表达式完全指南
  • 统计模型的Flops和Params
  • 2、数据验证组件框架:FluentValidation for .NET - 开源项目研究文章
  • Android adb shell GPU信息
  • 快速实现一个快递物流管理系统:实时更新与状态追踪
  • Qt for android : 简单实现弹窗创建文件,并使用JNI进行读写实例
  • LeetCode 225: 用队列实现栈
  • 每日学习30分轻松掌握CursorAI:多文件编辑与Composer功能
  • OpenGL利用DDA算法绘制图形,并增加鼠标键盘交互
  • VUE3 监听器(watch)
  • 卷积神经网络:过滤器为啥被叫作“核”
  • 内网服务器添加共享文件夹功能并设置端口映射
  • 【YOLOv5】源码(train.py)