ROS 2边学边练(36)-- 添加一个坐标系(C++)

news2024/11/23 4:42:59

前言

        此篇将会在之前已存在的几个坐标系(/world、/turtle1、/turtle2)的基础上再增加一个坐标系,相对来说,难度不大,主要是理解一些概念(脑子里面有3D场景的想象),比如一个小车机器人处在世界坐标系(/world或/map)中,小车本身在基座底盘中心点形成一个坐标系(比如/base),小车身上前后左右又各有一个雷达,每个雷达又可以以各自中心点为原点形成各自的坐标系(/laser1、/laser2、/laser3、/laser4),在各自坐标系里方便处理一些测量数据,这样总共有1+1+4=6个坐标系了,而tf2系统则负责转换各个坐标系之间的关系。

        在之前的章节里有提到过tf2树(tf2 tree)这个东西,它可以将机器人所在的大小环境的各坐标系之间的拓扑关系展示出来,如下图所示,但是tf2树有个规则,每个坐标系最多只允许有且只有一个父坐标系,其实也能好理解,比如雷达的坐标系只能是基于机器人底座的坐标系(或其他参考坐标系)才有意义,再比如一个小区所在的位置环境我们可以认为是一个世界坐标系,小区里面的每栋楼只能是在这个世界坐标系下才能有自己意义的定位(几号楼)坐标系,每栋楼里面的每室又是相对于该栋楼的定位(坐标系)才有意义。

动动手 

写一个固定坐标系(帧)的广播

        我们将在小海龟的例子里,为/turtle1新增一个子坐标系/carrot1,/carrot1将充当/turtle2的一个游动位置目标。

        进入learning_tf2_cpp功能包的src路径下,执行如下命令下载fixed_frame_tf2_broadcaster.cpp:

$wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/fixed_frame_tf2_broadcaster.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;
}

        代码的实现内容与之前的广播例子很相似,只有一个地方不一样,此处的转换数据是固定死的(所以叫fixed frame)。

代码分析
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;

        可以看到在这里,我们创建一个新的转换(从父级turtle1新的子级carrot1)。carrot1坐标系在turtle1坐标系的y轴上偏移了2米。

CMakeLists.txt

        打开learning_tf2_cpp包下的CMakeLists.txt文件,将下面内容加进去,我们将fixed_frame_tf2_broadcaster.cpp编译完成后可执行文件的名字叫为fixed_frame_tf2_brodcaster:

add_executable(fixed_frame_tf2_broadcaster src/fixed_frame_tf2_broadcaster.cpp)
ament_target_dependencies(
    fixed_frame_tf2_broadcaster
    geometry_msgs
    rclcpp
    tf2_ros
)

install(TARGETS
    fixed_frame_tf2_broadcaster
    DESTINATION lib/${PROJECT_NAME})
重新写个launch文件

        在launch文件夹下,创建一个新的启动文件turtle_tf2_fixed_frame_demo_launch.py:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

from launch_ros.actions import Node


def generate_launch_description():
    demo_nodes = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([os.path.join(
            get_package_share_directory('learning_tf2_cpp'), 'launch'),
            '/turtle_tf2_demo_launch.py']),
        )

    return LaunchDescription([
        demo_nodes,
        Node(
            package='learning_tf2_cpp',
            executable='fixed_frame_tf2_broadcaster',
            name='fixed_broadcaster',
        ),
    ])

        该启动文件引入了另外一个启动文件turtle_tf2_demo_launch.py,用来启动demo_nodes节点,文件的最后将会将carrot1坐标系(fixed_frame_tf2_broadcaster节点)添加到demo_nodes节点所在的例子中。

构建

        执行如下依赖检查、构建步骤(回到工作空间根路径)。

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

        重开一个终端,进入工作空间根路径下先source下环境再运行启动文件。

$. install/setup.bash
$ros2 launch learning_tf2_cpp turtle_tf2_fixed_frame_demo_launch.py

        我们执行如下命令查看下当前的tf2树,确认下/turtle1下面是不是多了一个/carrot1的子坐标系。

$ros2 run tf2_tools view_frames

        也可以通过如下命令查看从/turtle1到/carrot1的转换数据。

$ros2 run tf2_ros tf2_echo turtle1 carrot1

        如果我们通过turtle_teleop_key来控制turtle1,turtle2跟之前的例子一样也是跟随turtle1的轨迹游动,并没有像 我们计划的那样与turtle1在y轴上保持2米的距离,这是因为新增的carrot1坐标系不影响其他坐标系,监听者还是使用了之前定义的坐标系在游动(turtle2的target_frame还是turtle1而不是carrot1)。

        我们有两种方法可以让turtle2朝着carrot1的目标值前进(而不是turtle1,虽然carrot1与turtle1也有着关系哈)。一种是直接在启动launch文件时直接指定target_frame参数值为carrot1,比如下面这样:

$ros2 launch learning_tf2_cpp turtle_tf2_fixed_frame_demo_launch.py target_frame:=carrot1

或者直接修改launch文件target_frame参数值为carrot1,两者效果是一样的。

def generate_launch_description():
    demo_nodes = IncludeLaunchDescription(
        ...,
        launch_arguments={'target_frame': 'carrot1'}.items(),
        )

我们再重新构建启动一下,对比一下前面的那张图,turtle2与turtle1不重合了,而是相差着点距离。

        我们再通过turtle_teleop_key控制turtle1游动几下,看看turtle2的游动轨迹是怎样的。

写一个动态坐标系(帧)的广播 

        上面的例子将carrot1对于turtle1的y轴坐标值固定成了2米,下面的例子将turtle2的x和y轴的坐标值实现成随着turtle1的实际值而作一定规律的动态变化(当然也可以统一成turtle1一模一样的值,但是这个不是在之前就已经实现了吗,再这样就没什么新鲜感了)。

        进入learning_tf2_cpp包的src路径下,执行如下命令下载源代码文件dynamic_frame_tf2_broadcaster.cpp:

$wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/dynamic_frame_tf2_broadcaster.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;

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;
}

 与fixed_frame_tf2_broadcaster.cpp的不同之处在于转换变量的x和y值的赋值不同了,此处是以x值(double x = now.seconds() * PI)为参数分别作个正弦和余弦变换。

CMakeLists.txt

        修改CMakeLists.txt,添加dynamic_frame_tf2_broadcaster节点:

add_executable(dynamic_frame_tf2_broadcaster src/dynamic_frame_tf2_broadcaster.cpp)
ament_target_dependencies(
    dynamic_frame_tf2_broadcaster
    geometry_msgs
    rclcpp
    tf2_ros
)

install(TARGETS
    dynamic_frame_tf2_broadcaster
    DESTINATION lib/${PROJECT_NAME})
重新写个launch文件

        在launch文件夹下新建turtle_tf2_dynamic_frame_demo_launch.py:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

from launch_ros.actions import Node


def generate_launch_description():
    demo_nodes = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([os.path.join(
            get_package_share_directory('learning_tf2_cpp'), 'launch'),
            '/turtle_tf2_demo_launch.py']),
        launch_arguments={'target_frame': 'carrot1'}.items(),
        )

    return LaunchDescription([
        demo_nodes,
        Node(
            package='learning_tf2_cpp',
            executable='dynamic_frame_tf2_broadcaster',
            name='dynamic_broadcaster',
        ),
    ])
构建

        回到工作空间根路径下依次执行依赖检查、构建流程:

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

        工作空间根路径下先source下环境再启动launch文件。

$. install/setup.bash
$ros2 launch learning_tf2_cpp turtle_tf2_dynamic_frame_demo_launch.py

        turtle2(黄色) 小海龟在自顾自地按照命运脚本畅游了(也可以试试控制下turtle1的情况)。

本篇完。 

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

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

相关文章

春秋云镜 CVE-2023-50563

靶标介绍&#xff1a; SEMCMS是一套支持多种语言的外贸网站内容管理系统&#xff08;CMS&#xff09;。SEMCMS v4.8版本存在SQLI&#xff0c;该漏洞源于SEMCMS_Function.php 中的 AID 参数包含 SQL 注入 开启靶场&#xff1a; 开始实验&#xff1a; 1、使用后台扫描工具&…

QT学习之QtXlsx

背景&#xff1a; 本来我是想提取xml中的信息存在xlsx文件中的&#xff0c;网上很多说是使用QtXlsx&#xff1b; 于是我找了一些帖&#xff0c; 像&#xff1a;https://www.cnblogs.com/liming19680104/p/14398459.html&#xff1b; 大家的说法都是安装第三方库到QT中&#xff…

社交媒体数据恢复:Skype国内、际版

恢复已删除的Skype聊天记录可能需要一些操作&#xff0c;但请注意&#xff0c;这不一定总是可行的&#xff0c;并且可能需要一些技术知识。以下是一些步骤&#xff0c;您可以尝试恢复您的Skype聊天记录&#xff1a; 1. 检查备份&#xff1a; - 如果您有Skype备份&#xff0…

【行为型模式】备忘录模式

一、备忘录模式概述 备忘录模式定义&#xff1a;又称之为快照模式(Snapshop Pattern)或者令牌模式(Token Pattern)&#xff0c;是指在不破坏封装的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在对象之外保存这个状态&#xff0c;这样我们就可以在需要的时候将该对…

软件测试的内容包含什么内容

软件测试的内容涵盖了多个方面&#xff0c;以确保软件的质量和性能达到既定的标准。这些内容包括但不限于以下几点&#xff1a; 注册信息验证&#xff1a;对软件产品的基本信息进行验证&#xff0c;如软件名称、版本号、开发者等&#xff0c;确保这些信息的准确性和一致性。功…

Mysql事务—隔离级别—脏读、不可重复读、幻读-遥遥领先版

事务的基本概念 事务就是一组原子性的操作&#xff0c;这些操作要么全部发生&#xff0c;要么全部不发生。事务把数据库从一种一致性状态转换成另一种一致性状态。 事务最经典也经常被拿出来说例子就是转账了。 假如小明要给小红转账1000元&#xff0c;这个转账会涉及到两个…

Java全栈开发前端+后端(全栈工程师进阶之路)-环境搭建

在课程开始前我们要配置好我们的开发环境&#xff0c;这里我的电脑太乱了&#xff0c;我使用vm虚拟机进行搭建开发环境&#xff0c;如果有需要环境的或者安装包&#xff0c;可以私信我。 那我们开始 首先我们安装数据库 这里我们使用小皮面板 小皮面板(phpstudy) - 让天下没…

【一刷《剑指Offer》】面试题 11:数值的整数次方

力扣对应题目链接&#xff1a;50. Pow(x, n) - 力扣&#xff08;LeetCode&#xff09; 牛客对应题目链接&#xff1a;数值的整数次方_牛客题霸_牛客网 (nowcoder.com) 一、《剑指Offer》内容 二、分析题目 【快速幂 递归】 当指数 n 为负数时&#xff0c;我们可以计算 x^(−…

大模型应用开发极简入门

简单的归纳一下书的前序部分 目录 LLM&#xff08;Large Language Model&#xff09;的应用技术栈通常包括以下几个方面&#xff1a; 深度学习框架&#xff1a; 数据预处理工具&#xff1a; 训练资源&#xff1a; 模型优化和调参工具&#xff1a; 部署和应用集成&#xf…

基于Vue3的Axios异步请求

基于Vue3的Axios异步请求 1. Axios安装与应用2. Axios网络请求封装3. axios网络请求跨域前端解决方案server.proxy 1. Axios安装与应用 Axios是一个基于promise的网络请求库&#xff0c;Axios.js.中文文档&#xff1a;https://axios.js.cn/ 安装&#xff1a;npm install --sa…

链表之两数相加

两数相加 题目&#xff1a; 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&am…

简单几步!新手开抖店如何快速上手?看这一篇就够了!

大家好&#xff0c;我是电商花花。 很多新手卖家在做抖音小店的时候都是0基础的小白&#xff0c;在开好店铺之后却不知道如何下手。 那么今天花花就来跟 大家分享一下我们是怎么做抖音小店&#xff0c;怎么做店铺运营的&#xff0c;如果你作为一个刚开店的新手&#xff0c;一…

Java的逻辑控制和方法的使用介绍

前言 程序的逻辑结构一共有三种&#xff1a;顺序结构、分支结构和循环结构。顺序结构就是按代码的顺序来执行相应的指令。这里主要讲述Java的分支结构和循环结构&#xff0c;由于和C语言是有相似性的&#xff0c;所以这里只会提及不同点和注意要点~~ 注意在C语言中&#xff0c;…

MobileNetV4 论文学习

论文地址&#xff1a;https://arxiv.org/abs/2404.10518 代码地址&#xff1a;https://github.com/tensorflow/models/blob/master/official/vision/modeling/backbones/mobilenet.py 解决了什么问题&#xff1f; 边端设备的高效神经网络不仅能带来实时交互的体验&#xff0c…

Linux服务器安全基础 - 查看入侵痕迹

1. 常见系统日志 /var/log/cron 记录了系统定时任务相关的日志 /var/log/dmesg 记录了系统在开机时内核自检的信息&#xff0c;也可以使用dmesg命令直接查看内核自检信息 /var/log/secure:记录登录系统存取数据的文件;例如:pop3,ssh,telnet,ftp等都会记录在此. /var/log/btmp:记…

XYCTF 2024

Web 参考博客&#xff1a;https://www.yuque.com/yunzhiyunweiji/wrgkex/rfpnkn0293l7cp09#ezMake ezhttp Via - HTTP | MDN 代理那里难住了 XFF不给用可以用client-ip ezmd5 让我们上传图片并比较&#xff0c;结合题目名可以猜测应该是比较两个图片的md5值是否相同&…

实锤!腾讯终于拥抱鸿蒙生态,微信鸿蒙原生版本即将上线

大家都知道&#xff0c;目前已知纯血鸿蒙星河版next将于今年6月份开启Bate版本的测试&#xff0c;也就是说原生鸿蒙系统快上线了。而目前对于鸿蒙生态的发展&#xff0c;大家最关心的恐怕只有腾讯系的微信和QQ是否适配了纯血鸿蒙系统。甚至有网友表示&#xff0c;微信不适配鸿蒙…

上海计算机学会2020年9月月赛C++丙组T2中心对称数

题目背景 在超市里&#xff0c;有一些价格标签倒置后&#xff0c;数字竟不会发生改变。转置 180 度后不变的十进制数字被称为中心对称数&#xff08;Strobogrammatic Numbers&#xff09;。下图分别给出 0 到 9 这十个数字倒置后的样子&#xff1a; 题目描述 中心对称数是指沿…

npm 安装 pnpm 时 报错 npm ERR! Unexpected token ‘.‘

问题 一个项目用的是 pnpm 安装的依赖&#xff0c;node 的版本是 16.16.0&#xff0c;nvm 的版本是 1.1.7&#xff0c;然后全局安装 pnpm 报错如下&#xff1a; 解决 我看网上的一些解决方案是说 nvm 版本过低导致&#xff0c;下面我们按照这个方向处理。 实首先下载 nvm-up…

第三节课,功能2:开发后端用户的管理接口--http client -- debug测试

一、idea 中 Http client 使用 二、测试步骤&#xff0c;先进入主程序 2.1 先run &#xff0c;再debug 2.2 再进入想要测试的代码 2.2.1 进入测试的接口 三、程序逻辑 1&#xff09;用户注册逻辑&#xff1a;如果用户不存在再后端&#xff0c;看用户名&密码&校验码是…