当前位置:   article > 正文

ROS学习笔记06、ROS常用组件(TF坐标变换、rosbag、rqt工具箱)_rqt rosbag

rqt rosbag

前言

马上开学,目前学校很多实验室都是人工智能这块,大部分都是和机器人相关,然后软件这块就是和cv、ros相关,就打算开始学习一下。

本章节是虚拟机安装Ubuntu18.04以及安装ROS的环境。

学习教程:【Autolabor初级教程】ROS机器人入门,博客中一些知识点是来源于赵老师的笔记在线笔记,本博客主要是做归纳总结,如有侵权请联系删除。

视频中的案例都基本敲了遍,这里给出我自己的源代码文件:

链接:https://pan.baidu.com/s/13CAzXk0vAWuBsc4oABC-_g 
提取码:0hws 
  • 1
  • 2

所有博客文件目录索引:博客目录索引(持续更新)

一、TF坐标变换

1.1、坐标msg消息

坐标转换实现中常用的 msg:geometry_msgs/TransformStampedgeometry_msgs/PointStamped。在坐标变换中,频繁的需要使用到坐标系的相对关系以及坐标点信息。

  • 前者用于传输坐标系相关位置信息,后者用于传输某个坐标系内坐标点的信息。

查询geometry_msgs/TransformStamped消息

# 查询geometry_msgs/TransformStamped 
rosmsg info geometry_msgs/TransformStamped 

# geometry_msgs/TransformStamped 的结构体
std_msgs/Header header   #头信息
  uint32 seq			  #|-- 序列号
  time stamp              #|-- 时间戳
  string frame_id         #|-- 坐标 ID
string child_frame_id    #子坐标系的 id
geometry_msgs/Transform transform    #坐标信息
  geometry_msgs/Vector3 translation    #偏移量
    float64 x								#|-- X 方向的偏移量
    float64 y								#|-- Y 方向的偏移量
    float64 z								#|-- Z 方向上的偏移量
  geometry_msgs/Quaternion rotation	   #四元数
    float64 x
    float64 y
    float64 z
    float64 w
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

查询geometry_msgs/PointStamped消息

# 查询geometry_msgs/PointStamped
rosmsg info geometry_msgs/PointStamped

# geometry_msgs/PointStamped 的结构体
std_msgs/Header header    #头
  uint32 seq				#|-- 序号
  time stamp				#|-- 时间戳
  string frame_id			#|-- 所属坐标系的 id
geometry_msgs/Point point #点坐标
  float64 x					#|-- x y z 坐标
  float64 y
  float64 z
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

1.2、静态坐标变换

需求描述:现有一机器人模型,核心构成包含主体与雷达,各对应一坐标系,坐标系的原点分别位于主体与雷达的物理中心,已知雷达原点相对于主体原点位移关系如下: x 0.2 y0.0 z0.5。当前雷达检测到一障碍物,在雷达坐标系中障碍物的坐标为 (2.0 3.0 5.0),请问,该障碍物相对于主体的坐标是多少?

实现方案:发布geometry_msgs/TransformStamped格式的消息,接着使用订阅者来接收该坐标系相关位置信息,在订阅者端使用转换算法以及对应的障碍物坐标来得到base_link的坐标点。

  • 对于其中发布者替代方案我们不需要自己去手动编写发布者代码,可直接使用ros给我们提供的tf2_ros static_transform_publisher来进行实现。

发布者实现

模拟实际应用,实际场景中会从雷达里传出来。

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为06tf_static的包
catkin_create_pkg --rosdistro melodic 06tf_static roscpp rospy std_msgs tf2 tf2_ros tf2_geometry_msgs geometry_msgs
  • 1
  • 2
  • 3
  • 4
  • 5

image-20220912180147971

demo01_static_pub.cpp:

#include "ros/ros.h"
//静态坐标转换广播器
#include "tf2_ros/static_transform_broadcaster.h"
//坐标系信息
#include "geometry_msgs/TransformStamped.h"
//欧拉角
#include "tf2/LinearMath/Quaternion.h"  

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    //2、初始化节点
    ros::init(argc, argv, "static_brocast");
    //3、创建静态坐标转换广播器
    tf2_ros::StaticTransformBroadcaster broadcaster;
    //4、创建坐标系信息
    geometry_msgs::TransformStamped ts;
    //---设置头信息
    ts.header.seq = 1;
    ts.header.stamp = ros::Time::now();
    ts.header.frame_id = "base_link";
    //--设置子级坐标系
    ts.child_frame_id = "laser";
    //--设置子级相对于父级的偏移量
    ts.transform.translation.x = 0.2;
    ts.transform.translation.y = 0.0;
    ts.transform.translation.z = 0.5;
    //--设置四元数:将欧拉角数据转换成四元数
    tf2::Quaternion qtn;
    qtn.setRPY(0, 0, 0);
    ts.transform.rotation.x = qtn.getX();
    ts.transform.rotation.y = qtn.getY();
    ts.transform.rotation.z = qtn.getZ();
    ts.transform.rotation.w = qtn.getW();
    //5、广播发布坐标系信息
    broadcaster.sendTransform(ts);
    ros::spin();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

修改CMakeLists.txt:

add_executable(demo01_static_pub src/demo01_static_pub.cpp)

add_dependencies(demo01_static_pub ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo01_static_pub
  ${catkin_LIBRARIES}
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

接着进行编译与运行发布者:

source ./devel/setup.bash

rosrun 06tf_static demo01_static_pub
  • 1
  • 2
  • 3

image-20220912175631119

接着我们可以去查看下当前的话题信息并进行打印:

rostopic list

rostopic echo /tf_static
  • 1
  • 2
  • 3

image-20220912175617340

rviz可视化工具

在启动发布节点之后,我们就可以运行rviz工具,来进行可视化查看:

rviz
  • 1

image-20220912175849959

Add添加的是TF:

image-20220912175907629

替代发布者实现(ros工具包)

示例针对于上面发布者实现,我们这里可以调用ros提供的工具包来进行调用:

# 运行工具包
rosrun tf2_ros static_transform_publisher 1.5 0 0.5 0 0 0 /base_link /laser
  • 1
  • 2

订阅者实现

image-20220912213708877

demo01_static_sub.cpp:

#include "ros/ros.h"
//对应tf2_ros::TransformListener监听器
#include "tf2_ros/transform_listener.h"
//对应的是tf2_ros::Buffer节点(用于监听器来接收数据的一块缓冲区)
#include "tf2_ros/buffer.h"
//对应的是地图消息的坐标标识
#include "geometry_msgs/PointStamped.h"
//若是不引入该依赖会报错
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"


int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    //2、初始化ROS节点
    ros::init(argc, argv, "tf_sub");
    ros::NodeHandle nh;
    //3、创建 TF 订阅节点
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener listener(buffer);
    //4、生成一个坐标点(相对于子级坐标系)
    geometry_msgs::PointStamped ps;
    ps.header.frame_id = "laser";
    ps.header.stamp = ros::Time::now();
    ps.point.x = 2.0;
    ps.point.y = 3.0;
    ps.point.z = 5.0; 
    ros::Duration(2).sleep();
    //5、使用转换算法,需要调用TF内置实现
    ros::Rate rate(10);
    while (ros::ok()) {
        //核心代码:将ps转换为base_link的坐标点
        geometry_msgs::PointStamped ps_out;
        ps_out = buffer.transform(ps, "base_link");
        //6、最后输出
        ROS_INFO("转换后的坐标值:(%.2f, %.2f, %.2f),参考的坐标系: %s", 
                    ps_out.point.x,
                    ps_out.point.y,
                    ps_out.point.z,
                    ps_out.header.frame_id.c_str()
                    );
        rate.sleep();
        ros::spinOnce();
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

编辑修改CMakeLists.txt:

add_executable(demo02_static_sub src/demo02_static_sub.cpp)

add_dependencies(demo02_static_sub ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo02_static_sub
  ${catkin_LIBRARIES}
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

编译整个项目,并进行运行节点:

image-20220912213801510

source ./devel/setup.bash

# 启动订阅者
rosrun 06tf_static demo01_static_sub
  • 1
  • 2
  • 3
  • 4

image-20220912203109951

tf2::LookupException报错解决方案

报错:tf2::LookupException

原因描述:tf2_ros::TransformListener listener(buffer);还没有接收到消息的时候就往下执行到了transform转换方法,此时就会报错。

  • 如何解决呢?就是说要等到接收到消息了才往下执行就不会异常结束了!

image-20220912204550748

解决方案一:在接收消息下添加一个延时函数。

image-20220912205059968

ros::Duration(2).sleep();
  • 1

解决方案二:在对应执行转换算法的时候我们去进行一个try catch捕捉异常即可。

image-20220912205233729

//核心代码:将ps转换为base_link的坐标点
geometry_msgs::PointStamped ps_out;
try{   
    //转换算法
    ps_out = buffer.transform(ps, "base_link");
}catch(const std::exception& e){
    std::cerr << e.what() << '\n';
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

1.3、动态坐标变换

实现需求:

  • 发布方:订阅乌龟的坐标话题,乌龟坐标转为坐标系相对信息,接着将对应的坐标系相对信息通过广播发布出去。
  • 订阅方:订阅到坐标系相对信息,接着对其进行转换得到该乌龟相对于主体的坐标。

发布者

流程:自己编写的发布者去订阅ros乌龟的话题,其中会将乌龟的坐标消息转为TransformStamped消息类型然后通过广播器发布出去(/tf),接着可以使用rviz图形化工具来去订阅(world、tf的坐标信息,来实现一个乌龟的3D坐标同步)。

image-20220913105117949

demo02_dynamic_pub.cpp:动态坐标系的发布者

#include "ros/ros.h"
#include "turtlesim/Pose.h"
// 广播器
#include "tf2_ros/transform_broadcaster.h"
// 转换标记坐标
#include "geometry_msgs/TransformStamped.h"
// 欧拉角引入
#include "tf2/LinearMath/Quaternion.h"

//处理坐标转换并发布
void doPose(const turtlesim::Pose::ConstPtr& pose) {
    ROS_INFO("获取乌龟的位姿:x=%.2f,y=%.2f,theta=%.2f,lv=%.2f,av=%.2f", pose->x, pose->y, pose->theta, pose->linear_velocity, pose->angular_velocity);
    //获取位姿信息,转换成坐标相对关系(核心),并发布
    //a、创建发布对象
    static tf2_ros::TransformBroadcaster pub;
    //b、组织被发布的数据
    geometry_msgs::TransformStamped tfs;
    // |-- 头设置
    tfs.header.frame_id = "world"; //坐标id表示为world
    tfs.header.stamp = ros::Time::now();
    // |-- 坐标系ID
    tfs.child_frame_id = "turtle1";
    // |-- 坐标系相对信息设置
    tfs.transform.translation.x = pose->x;
    tfs.transform.translation.y = pose->y;
    tfs.transform.translation.z = 0;
    //坐标轴四元数
    /*
        位姿信息中没有四元数,但是有个偏航角度,又已知乌龟是2D,没有翻滚和俯仰角度,所以
        可以得到乌龟的欧拉角:0 0 theta
     */
    tf2::Quaternion qtn;
    qtn.setRPY(0, 0, pose->theta);
    tfs.transform.rotation.x = qtn.getX();
    tfs.transform.rotation.y = qtn.getY();
    tfs.transform.rotation.z = qtn.getZ();
    tfs.transform.rotation.w = qtn.getW();
    //c、广播器发布数据
    pub.sendTransform(tfs);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "dynamic_pub");
    ros::NodeHandle nh;
    //1、创建订阅对象,订阅 /turtle1/pose
    ros::Subscriber sub = nh.subscribe("/turtle1/pose", 100, doPose);
    //2、回调函数处理订阅的消息:将坐姿信息转化为坐标相对关系并发布(关注)
    //3、spin()回调函数
    ros::spin();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

修改CMakeLists.txt文件:

add_executable(demo02_dynamic_pub src/demo02_dynamic_pub.cpp)

add_dependencies(demo02_dynamic_pub ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo02_dynamic_pub
  ${catkin_LIBRARIES}
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后来进行实操启动下

1、启动ros核心以及GUI及键盘控制

image-20220913113024789

<launch>
    <!-- 乌龟GUI节点 -->
    <node pkg="turtlesim" type="turtlesim_node"  name="myTurtle" output="screen" />
    <!-- keyboard控制 -->
    <node pkg="turtlesim" type="turtle_teleop_key"  name="myTurtleContro" output="screen" />
</launch>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
# 启动GUI以及键盘控制
roslaunch 06tf_static 01start_turtle.launch
  • 1
  • 2

2、启动动态坐标变换的发布者

source ./devel/setup.bash

# 启动发布者
rosrun 06tf_static demo02_dynamic_pub
  • 1
  • 2
  • 3
  • 4

3、查看坐标话题

rostopic list
  • 1

image-20220913104806235

4、打开图形化界面rviz工具,选择fixed Frame【world】,Add【TF】,然后去操控键盘去实现动态的信息发布

# 选择Fixed Frame为world,add TF坐标
rviz
  • 1
  • 2

GIF


订阅者

image-20220913112530468

image-20220913112051157

//修改1:表示当前的frame_id为turtle1
ps.header.frame_id = "turtle1";
//修改2:时间戳为0.0
ps.header.stamp = ros::Time(0.0);、
    
//修改3:与发布方的坐标id一致都是world
ps_out = buffer.transform(ps, "world");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对于时间戳更改为0.0的解释:

  • 动态坐标变换中buffer是有许多值,而之前静态坐标变换就只有一个值,其中每个动态发布中也有时间戳并且是动态的,在订阅者监听之后肯定是有延时的,转化的时候会拿你当前设置的时间戳与之前发布消息的时间戳进行比对,比较判断两个时间戳是否是接近的,若是接近就可以转,若是时间差距比较大就不会允许。
  • 若是直接设置为0.0,那么在底层的代码发现你没有设置就不会进行比对了,也就不会抛出异常了。

按照上方的发布者发布好之后,我们来去运行订阅者:

source ./devel/setup.bash

# 启动发布者
rosrun 06tf_static demo02_dynamic_sub
  • 1
  • 2
  • 3
  • 4

image-20220913112440776


1.4、多坐标变换

需求分析:现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,son1 相对于 world,以及 son2 相对于 world 的关系是已知的,求 son1原点在 son2中的坐标,又已知在 son1中一点的坐标,要求求出该点在 son2 中的坐标。

发布者

image-20220913140725444

直接使用ros提供的工具包来进行发布两个相对于世界的坐标:

<launch>
    <node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="5 0 0 0 0 0 /world /son1" output="screen" />
    <node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="3 0 0 0 0 0 /world /son2" output="screen" />
</launch>
  • 1
  • 2
  • 3
  • 4

来进行启动:

# 运行launch文件
roslaunch 06tf_static 03_tfs.launch

# 打开GUI坐标系
rviz
  • 1
  • 2
  • 3
  • 4
  • 5

image-20220913140923774

订阅者

image-20220913141003901

demo03_tfs.cpp:

#include "ros/ros.h"
//引入监听器:tf2_ros::TransformListener
#include "tf2_ros/transform_listener.h"
//缓冲区
#include "tf2_ros/buffer.h"
#include "geometry_msgs/PointStamped.h"
//若是不引入该依赖会报错
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "tfs_sub");
    ros::NodeHandle nh;
    //1、创建订阅对象
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener sub(buffer);
    //2、编写解析逻辑

    //创建坐标点
    geometry_msgs::PointStamped psAtSon1;

    psAtSon1.header.stamp = ros::Time::now();
    psAtSon1.header.frame_id = "son1";
    psAtSon1.point.x = 1.0;
    psAtSon1.point.y = 2.0;
    psAtSon1.point.z = 3.0;

    ros::Rate rate(10);
    while (ros::ok()) {
        try
        {
            //1、计算son1和son2的相对关系
            /*
                A 相对于 B的坐标系关系
                参数1:目标坐标系 B
                参数2:源坐标系   A
                参数3:ros::Time(0) 取间隔最短的两个坐标关系帧计算相对关系
                返回值:geometry_msgs::TransformStamped  源相对于目标坐标系的相对关系
             */
            geometry_msgs::TransformStamped son1ToSon2 =  buffer.lookupTransform("son2", "son1", ros::Time(0));
            ROS_INFO("son1 相对于son2 的信息:父级%s, 子级%s 偏移量(%.2f, %.2f, %.2f)", 
                    son1ToSon2.header.frame_id.c_str(), //son2
                    son1ToSon2.child_frame_id.c_str(),   //son1
                    son1ToSon2.transform.translation.x,
                    son1ToSon2.transform.translation.y,
                    son1ToSon2.transform.translation.z
                ); //son1

            //2、计算son1的某个坐标点在son2中的坐标值(转换算法)
            geometry_msgs::PointStamped psAtSon2 = buffer.transform(psAtSon1, "son2");
            ROS_INFO("坐标点在 Son2 中的值(%.2f, %.2f, %.2f)",
                    psAtSon2.point.x,
                    psAtSon2.point.y,
                    psAtSon2.point.z
                    );
        }
        catch(const std::exception& e)
        {
            ROS_INFO("错误提示:%s", e.what());
        }
        rate.sleep();
        ros::spinOnce();
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

修改CMakeLists.txt:

add_executable(demo03_tfs src/demo03_tfs.cpp)

add_dependencies(demo03_tfs ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo03_tfs
  ${catkin_LIBRARIES}
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

开始进行编译运行:

source ./devel/setup.bash

# 启动发布者☀️
rosrun 06tf_static demo03_tfs
  • 1
  • 2
  • 3
  • 4

image-20220913141133093


1.5、坐标关系查看(tf2_tools)

在机器人系统中,涉及的坐标系有多个,为了方便查看,ros 提供了专门的工具,可以用于生成显示坐标系关系的 pdf 文件,该文件包含树形结构的坐标系图谱。

步骤一:安装tf2_tools

# 注意命令格式:ros-【ros版本】-tf2-tools
sudo apt-get install ros-melodic-tf2-tools
  • 1
  • 2

步骤二:发布坐标关系

image-20220913143416376

03_tfs.launch:

<launch>
    <node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="5 0 0 0 0 0 /world /son1" output="screen" />
    <node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="3 0 0 0 0 0 /world /son2" output="screen" />
</launch>
  • 1
  • 2
  • 3
  • 4

执行发布命令:

# 运行launch文件
roslaunch 06tf_static 03_tfs.launch
  • 1
  • 2

步骤三:启动坐标系广播程序之后,来生成相应的pdf文档

# 首先启动发布两个坐标点,接着就可以在当前目录下生成pdf文档
rosrun tf2_tools view_frames.py

# 打开pdf
evince frames.pdf
  • 1
  • 2
  • 3
  • 4
  • 5

image-20220913143238192


TF坐标变换实操

实际效果演示即方案

实际效果演示:

GIF

实现介绍说明

1、发布新建乌龟服务。

  • service:/spawn
  • 服务srv:turtlesim::Spawn

2、订阅两个乌龟的pose坐标,转换为世界坐标来进行广播发布。

  • 订阅topic:/turtle1/pose、/turtle2/pose;msg:turtlesim::Pose
  • 发布广播(topic):tf2_ros::TransformBroadcaster;msg:geometry_msgs::TransformStamped

3、订阅广播,获取到turtleA、turtleB并计算其相对的距离,并使用数学公式来计算出速度,发布turtleB的坐标即可实现乌龟2的跟随。

  • 订阅广播(topic):geometry_msgs::TransformStamped
  • 发布topic:/turtle2/cmd_vel;msg:geometry_msgs::Twist

创建新功能包

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为06tf_practical的包
catkin_create_pkg --rosdistro melodic 06tf_practical roscpp rospy std_msgs tf2 tf2_ros tf2_geometry_msgs geometry_msgs
  • 1
  • 2
  • 3
  • 4
  • 5

image-20220913165403529


功能1:新建乌龟

image-20220913165430425

01_new_turtle.cpp:

#include "ros/ros.h"
#include "turtlesim/Spawn.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    //1、初始化节点
    ros::init(argc,  argv, "setTurtle_node");

    //2、实例化句柄
    ros::NodeHandle nh;

    //3、使用句柄实例化服务客户端
    ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");

    //4、等待服务启动
    //方式一:使用客户端实例的方法
    // client.waitForExistence();
    //方式二:使用ros提供的方法
    ros::service::waitForService("/spawn");

    //5、发送请求
    //填充request请求对象
    turtlesim::Spawn spawn;
    spawn.request.x = 1;
    spawn.request.y = 1;
    spawn.request.theta = 1.5;
    spawn.request.name = "turtle2";

    bool flag = client.call(spawn);
    if (flag) {
        ROS_INFO("生成乌龟成功!乌龟的名称为:%s", spawn.response.name.c_str());
    }else {
        ROS_INFO("生成乌龟失败!");
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

功能2:订阅乌龟坐标并广播发布

image-20220913165622636

02_turtle_pub.cpp:

#include "ros/ros.h"
#include "turtlesim/Pose.h"
// 广播器
#include "tf2_ros/transform_broadcaster.h"
// 转换标记坐标
#include "geometry_msgs/TransformStamped.h"
// 欧拉角引入
#include "tf2/LinearMath/Quaternion.h"

//乌龟名称
std::string turtle_name;

//处理坐标转换并发布
void doPose(const turtlesim::Pose::ConstPtr& pose) {
    // ROS_INFO("获取乌龟的位姿:x=%.2f,y=%.2f,theta=%.2f,lv=%.2f,av=%.2f", pose->x, pose->y, pose->theta, pose->linear_velocity, pose->angular_velocity);
    //获取位姿信息,转换成坐标相对关系(核心),并发布
    //a、创建发布对象
    static tf2_ros::TransformBroadcaster pub;
    //b、组织被发布的数据
    geometry_msgs::TransformStamped tfs;
    // |-- 头设置
    tfs.header.frame_id = "world"; //坐标id表示为world
    tfs.header.stamp = ros::Time::now();
    // |-- 坐标系ID
    tfs.child_frame_id = turtle_name;
    // |-- 坐标系相对信息设置
    tfs.transform.translation.x = pose->x;
    tfs.transform.translation.y = pose->y;
    tfs.transform.translation.z = 0;
    //坐标轴四元数
    /*
        位姿信息中没有四元数,但是有个偏航角度,又已知乌龟是2D,没有翻滚和俯仰角度,所以
        可以得到乌龟的欧拉角:0 0 theta
     */
    tf2::Quaternion qtn;
    qtn.setRPY(0, 0, pose->theta);
    tfs.transform.rotation.x = qtn.getX();
    tfs.transform.rotation.y = qtn.getY();
    tfs.transform.rotation.z = qtn.getZ();
    tfs.transform.rotation.w = qtn.getW();
    //c、广播器发布数据
    pub.sendTransform(tfs);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "dynamic_pub");

    //校验参数
    if (argc != 2) {
        ROS_ERROR("请传入正确的参数!");
        return 1;
    }else {
        turtle_name = argv[1];
        ROS_INFO("乌龟【%s】的坐标发送启动", turtle_name.c_str());  
    }

    ros::NodeHandle nh;
    //1、创建订阅对象,订阅 /turtle1/pose
    ros::Subscriber sub = nh.subscribe(turtle_name + "/pose", 1000, doPose);
    //2、回调函数处理订阅的消息:将坐姿信息转化为坐标相对关系并发布(关注)
    //3、spin()回调函数
    ros::spin();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

功能3:订阅广播并计算turtle2的速度

image-20220913165720861

速度计算的图示:

image-20220913193326666

03_turtle2_sub.cpp:

#include "ros/ros.h"
//引入监听器:tf2_ros::TransformListener
#include "tf2_ros/transform_listener.h"
//缓冲区
#include "tf2_ros/buffer.h"
#include "geometry_msgs/PointStamped.h"
//若是不引入该依赖会报错
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
// 乌龟坐标的发布
#include "geometry_msgs/Twist.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "tfs_sub");
    ros::NodeHandle nh;
    //1、创建订阅对象
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener sub(buffer);
    //2、获取到发布对象
    ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel", 1000);

    ros::Rate rate(10);
    while (ros::ok()) {
        try
        {
            //1、计算turtle1和turtle2的相对关系
            geometry_msgs::TransformStamped son1ToSon2 =  buffer.lookupTransform("turtle2", "turtle1", ros::Time(0));
            // ROS_INFO("son1 相对于son2 的信息:父级%s, 子级%s 偏移量(%.2f, %.2f, %.2f)", 
            //         son1ToSon2.header.frame_id.c_str(), //turtle2
            //         son1ToSon2.child_frame_id.c_str(),   //turtle1
            //         son1ToSon2.transform.translation.x,
            //         son1ToSon2.transform.translation.y,
            //         son1ToSon2.transform.translation.z
            //     ); //son1

            //2、根据相对计算并组织速度消息
            geometry_msgs::Twist twist;
            /*
                组织速度,只需要设置线速度的x与角速度的z
                x = 系数 * 开方(y^2 + x^2)
                z = 系数 * 反正切(对边,临边)
             */
            twist.linear.x = 1 * sqrt(pow(son1ToSon2.transform.translation.x, 2) + pow(son1ToSon2.transform.translation.y, 2));
            twist.angular.z = 4 * atan2(son1ToSon2.transform.translation.y, son1ToSon2.transform.translation.x);
            //发布消息
            pub.publish(twist);
        }
        catch(const std::exception& e)
        {
            ROS_INFO("错误提示:%s", e.what());
        }
        rate.sleep();
        ros::spinOnce();
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

编写launch文件统一启动

image-20220913165829421

start_turtle.launch:

<launch>
    <!-- 乌龟GUI节点 -->
    <node pkg="turtlesim" type="turtlesim_node"  name="turtle1" output="screen" />
    <!-- keyboard控制 -->
    <node pkg="turtlesim" type="turtle_teleop_key"  name="myTurtleContro" output="screen" />
    <!-- 1、生成乌龟turtle2 -->
    <node pkg="06tf_practical" type="01_new_turtle" name="turtle_spawn" output="screen" />
    <!-- 2、订阅两只乌龟的pose坐标,转换成坐标相对关系通过广播发送出去 -->
    <node pkg="06tf_practical" type="02_turtle_pub" name="tf_pub1" output="screen" args="turtle1" />
    <node pkg="06tf_practical" type="02_turtle_pub" name="tf_pub2" output="screen" args="turtle2" />
    <!-- 3、订阅得到两只乌龟的相对坐标系,接着计算速度后像turtle发布坐标 -->
    <node pkg="06tf_practical" type="03_turtle2_sub" name="tf_sub" output="screen"  />
</launch>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

启动命令:

source ./devel/setup.bash

launch 06tf_practical start_turtle.launch
  • 1
  • 2
  • 3

二、rosbag(数据留存与读取)

2.1、认识rosbag

ros-wiki-rosbag

介绍:在ROS中关于数据的留存以及读取实现,提供了专门的工具,rosbag。

作用:实现了数据的复用,方便调试、测试。

本质:rosbag本质也是ros的节点,当录制时,rosbag是一个订阅节点,可以订阅话题消息并将订阅到的数据写入磁盘文件;当重放时,rosbag是一个发布节点,可以读取磁盘文件,发布文件中的话题消息。

场景:机器人导航实现中,可能需要绘制导航所需的全局地图,地图绘制实现,有两种方式:

  • 方式1:可以控制机器人运动,将机器人传感器感知到的数据时时处理,生成地图信息。
  • 方式2:同样是控制机器人运动,将机器人传感器感知到的数据留存,事后,再重新读取数据,生成地图信息。两种方式比较,显然方式2使用上更为灵活方便。

创建工程:

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为06tf_rosbag的包
catkin_create_pkg --rosdistro melodic 06tf_rosbag roscpp rospy std_msgs rosbag
  • 1
  • 2
  • 3
  • 4
  • 5

image-20220913201025258


2.2、rosbag使用命令行

熟悉命令

#  对所有话题进行录制
rosbag record -a -o bags/hello.bag

# 指定话题录制
rosbag record /turtle1/cmd_vel -o bags/hello.bag

# 查看bag信息
rosbag info bags/xxx.bag

# 回放bag信息
rosbag play bags/xxx.bag

# 查看rosbag record的命令行提示信息
rosbag record --help
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

实际案例

案例目标:录制一段使用turtle在GUI中移动的话题消息,并进行回放!

首先启动节点:turtle的GUI界面以及keyboard控制

image-20220913201550198

roslaunch 06tf_rosbag start_turtle.launch
  • 1

录制功能:打开之后我们来使用rosbag进行监听命令:

# 这里我们执行
rosbag record /turtle1/cmd_vel -o bags/hello.bag
  • 1
  • 2

image-20220913201813449

回车即可录制结束,此时就会在当前目录下的bags中生成一个日志文件:

image-20220913201854501

回放功能

# 回放指定的bag文件
rosbag play bags/hello_2022-09-13-20-13-14.bag
  • 1
  • 2

GIF


2.3、rosbag编码实现读和写(C++)

需求:使用rosbag来向/chatter写入多条消息进行本地化存储;之后来进行读取所有的数据。

image-20220913204927624

demo01_rosbag_write.cpp:

#include "ros/ros.h"
#include "rosbag/bag.h"
#include "std_msgs/String.h"

int main(int argc, char *argv[])
{
    //初始化
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "bag_write");
    ros::NodeHandle nh;

    //创建rosbag对象
    rosbag::Bag bag;

    //打开文件流
    bag.open("bags/hello.bag", rosbag::bagmode::Write);

    //写数据(像话题/chatter写消息)
    std_msgs::String msg;
    msg.data = "hello changlu";
    bag.write("/chatter", ros::Time::now(), msg);
    bag.write("/chatter", ros::Time::now(), msg);
    bag.write("/chatter", ros::Time::now(), msg);
    bag.write("/chatter", ros::Time::now(), msg);

    //关闭文件流
    bag.close();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

修改CMakelists.txt:

add_executable(demo01_rosbag_write src/demo01_rosbag_write.cpp)

add_dependencies(demo01_rosbag_write ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo01_rosbag_write
  ${catkin_LIBRARIES}
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最终进行编译进行写入bag文件:

source ./devel/setup.bash

rosrun 06tf_rosbag demo01_rosbag_write
  • 1
  • 2
  • 3

image-20220913205042810

image-20220913205056276

demo02_rosbag_read.cpp:

#include "ros/ros.h"
#include "rosbag/bag.h"
#include "rosbag/view.h"
#include "std_msgs/String.h"

int main(int argc, char *argv[])
{
    //初始化
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "bag_read");
    ros::NodeHandle nh;

    //创建rosbag对象
    rosbag::Bag bag;

    //打开文件流
    bag.open("bags/hello.bag", rosbag::bagmode::Read);

    //读数据
    //取出话题,时间戳和消息内容
    //可以先获取消息的集合,再迭代取出消息的字段
    for (auto &&m: rosbag::View(bag)) {
        //获取话题
        std::string topic = m.getTopic();
        //获取中取出时间戳、实体数据
        ros::Time time = m.getTime();
        std_msgs::StringPtr p = m.instantiate<std_msgs::String>();
        ROS_INFO("解析得到的内容,话题:%s, 时间戳:%.2f, 消息值:%s",
              topic.c_str(),
              time.toSec(),
              p->data.c_str()
            );
    }

    //关闭文件流
    bag.close();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

编辑CMakeLists.txt:

add_executable(demo02_rosbag_read src/demo02_rosbag_read.cpp)

add_dependencies(demo02_rosbag_read ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo02_rosbag_read
  ${catkin_LIBRARIES}
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后进行编译运行:

source ./devel/setup.bash

rosrun 06tf_rosbag demo02_rosbag_read
  • 1
  • 2
  • 3

image-20220913205205170

三、rqt工具箱

3.1、rqt工具安装与启动

安装工具如下:

# melodic、noetic
sudo apt-get install ros-melodic-rqt

sudo apt-get install ros-melodic-rqt-common-plugins
  • 1
  • 2
  • 3
  • 4

rqt的启动方式有两种:

# 方式一
rqt

# 方式二
rosrun rqt_gui rqt_gui
  • 1
  • 2
  • 3
  • 4
  • 5

image-20220914102237184

3.2、常用插件

其他还有:rqt_service、rqt_topic。

Node Graph(计算图)

image-20220914102540324

或者直接执行命令:

rqt_graph
  • 1

示例图:

image-20220914102615472


rqt_console(日志窗口)

通过rqt工具箱打开或者直接命令行打开即可:

image-20220914103350771

rqt_console
  • 1

编写一段日志打印代码:

image-20220914103205726

demo02_rqtconsole.cpp:

#include "ros/ros.h"

int main(int argc, char *argv[])
{
    ros::init(argc,argv,"log_demo");
    ros::NodeHandle nh;

    ros::Rate r(0.3);
    while (ros::ok())
    {
        ROS_DEBUG("Debug message d");
        ROS_INFO("Info message oooooooooooooo");
        ROS_WARN("Warn message wwwww");
        ROS_ERROR("Erroe message EEEEEEEEEEEEEEEEEEEE");
        ROS_FATAL("Fatal message FFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
        r.sleep();
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

编译并启动节点:

source ./devel/setup.bash

rosrun 06tf_rosbag demo02_rqtconsole
  • 1
  • 2
  • 3

接着我们去看rqt_console窗口就可以看到当前结点报的日志错误信息了:

image-20220914103111658


rqt_plot(2D绘制topic数据)

介绍:图形绘制插件,可以以 2D 绘图的方式绘制发布在 topic 上的数据。

启动方式,GUI或者命令行:

image-20220914103911084

 rqt_plot
  • 1

启动乌龟GUI结点以及键盘控制节点,接着在qrt_plot中订阅位姿节点,接着进行操控:

image-20220914103804181


rqt_rosbag

rosbag演示

启动:

rqt_bag
  • 1

image-20220914105334875

参考资料

[1]. rosbag录制话题、播放话题多种模式

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

闽ICP备14008679号