前言

系统环境: Ubuntu 18.04,硬盘安装。

ROS版本: melodic

深蓝学院(古月居)胡春旭ROS理论与实践笔记整理。

1. Launch启动文件

使用一个终端,一个语句,即可实现多节点的启动和配置。使用Launch文件时不需要roscore

Launch启动文件的标签如下:

标签名 说明 使用范例
<launch> launch文件中的根元素采用launch标签定义 <launch>...</launch>
<node> 启动节点 <node pkg="package-name" type = "executable-name"/>
pkg 节点所在的功能包名称 在node标签中,pkg="package-name"
type 节点的可执行文件名称 在node标签中,type = "executable-name"
name 节点运行时的名称,会替换掉ros::init中设置好的节点名 在node标签中,name = "talker"
output 表示节点的日志是否在终端显示 在node标签中,output="screen"
respawn 节点运行失败时自动重启 在node标签中,respawn = "true"
required 修饰的节点是启动的必须项,启动失败就会报错 在node标签中,required = "true"
ns 给节点设置一个命名空间 <rosparam file = "params.yaml" command = "load" ns = "params/">
前面就会多一个"params/"的命名空间。
args 通过命令行给main函数传递的参数
<param><rosparam> 设置ROS系统运行中的参数,全局变量,存储在参数服务器中 <param name = "output_frame" value = "odom"/>
<rosparam file = "params.yaml" command = "load" ns = "params/">
<arg> launch文件内部的局部变量,仅限于launch文件使用 <arg name = "arg-name" default = "arg-value"/>
调用:
<param name = "foo" value = "$(arg arg-name)"/>
<node name="node" pkg = "package" type = "type" args = "$(arg arg-name)"/>
<remap> 重映射ROS计算图资源的命名 <remap from = "/turtlebot/cmd_vel" to="/cmd_vel"/>
<include> 包含其他launch文件,类似C语言中的头文件包含。
file:包含的其他launch文件路径
<include file="$(dirname)/other.launch">

更多标签:ROS wiki

launch文件示例与分析:

  • 示例1:
1
2
3
4
5
6
7
8
<launch>
<!-- 打开learning_communication包下的person_subscriber,并把节点重命名为talker -->
<node pkg="learning_communication" type="person_subscriber" name="talker" />

<!-- 打开learning_communication包下的person_publisher,并把节点重命名为listener -->
<node pkg="learning_communication" type="person_publisher" name="listener" />
</launch>

  • 示例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<launch>
<!-- 全局变量turtle_number,值为2 -->
<param name="/turtle_number" value="2"/>
<!-- 全局变量TurtleName1,值为"Tom" -->
<arg name="TurtleName1" default="Tom" />
<!-- 全局变量TurtleName2,值为"Jerry" -->
<arg name="TurtleName2" default="Jerry" />
<!-- 打开turtlesim包下的turtlesim_node,并把节点重命名为turtlesim_node -->
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">
<!-- 调用局部变量,生成全局变量 -->
<param name="turtle_name1" value="$(arg TurtleName1)"/>
<param name="turtle_name2" value="$(arg TurtleName2)"/>
<!-- 加载参数文件 -->
<rosparam file="$(find learning_launch)/config/param.yaml" command="load"/>
</node>
<!-- 打开turtlesim包下的turtle_teleop_key,并把节点重命名为turtle_teleop_key -->
<!-- 将日志生成到终端上 -->
<node pkg="turtlesim" type="turtle_teleop_key" name="turtle_teleop_key" output="screen"/>

</launch>

其中的param.yaml为:

1
2
3
4
5
6
7
8
A: 123
B: "hello"

# group为一个命名空间
group:
C: 456
D: "hello"

此时调用rosparam list,可以查看设置好的全局变量:

ROS param清单

调用rosparam get可以查看全局变量具体的值:

ROS param value

  • 示例3:
1
2
3
4
5
6
7
<launch>
<include file="$(find learning_launch)/launch/simple.launch" />
<node pkg="turtlesim" type="turtlesim_node" name="turtlesim_node">
<!-- 将"/turtle1/cmd_vel"的话题重映射为"/cmd_vel" -->
<remap from="/turtle1/cmd_vel" to="/cmd_vel"/>
</node>
</launch>

此时运行rostopic list,就会发现,原来名为/turtle1/cmd_vel的话题已经变成了/cmd_vel

$(find ...)是用来定位到一个package,增强了项目的可移植性。若想在自己的项目中实现find,需要有CMakeLists.txtpackage.xml,并对这两个文件进行编辑。参考:实现launch中的find位置

2. Modeling in Robotics基础

理解TF功能包的基础知识。

2.1 欧拉角

欧拉角的表示:先绕当前z轴旋转ϕ\phi,再绕当前y轴旋转θ\theta,再绕当前z轴旋转ψ\psi

欧拉角的表示

展开:

最终化简为:

欧拉角的计算:对最后一列和最后一行元素利用正交性进行分类讨论:

情况一:若r33=±1r_{33}=\pm 1,则θ=0\theta=0有解,ϕ+θ\phi+\theta的和为定值,存在无限多组解。

情况二:若 r332<1r_{33}^2< 1 ,则 θ,ϕ,θ\theta, \phi, \theta 有解。

2.2 RPY

如果按照固定的世界坐标轴旋转,那么有两种RPY:

RPY

2.2 转轴/角度(轴角)

轴角的定义为:

轴角的定义

给定任意一个旋转矩阵RR,转角 θ\theta 和转轴 kk 可由下列式子得到:

2.2 四元数

老欧的ppt中,四元数由转轴/角度表示法定义而来,类似于欧拉角,是一种姿态的表示方式:

四元数

2.3 刚性转动

刚性变换可由齐次变换(homogeneous transformation)矩阵来表示。齐次变换矩阵是一个具有 O=[Rd01],RSO(3);dR3O = \begin{bmatrix} R & d \\ 0 & 1 \\ \end{bmatrix} ,R\in SO(3);d\in R^ 3 形式的4×44\times4的矩阵。具体分解可看老欧ppt:

刚性转动

3 TF功能包

  • TF功能包能干什么?
    • 五秒钟之前,机器人头部坐标系相对于全局坐标系的关系是什么样的?
    • 机器人夹取的物体相对于机器人中心坐标系的位置在哪里?
    • 机器人中心坐标系相对于全局坐标系的位置在哪里?
  • TF坐标变换如何实现?
    • 广播TF变换
    • 监听TF变换

3.1 查看TF树

  • 查看TF树命令(以小海龟为例):
1
2
3
4
$ sudo apt-get install ros-melodic-turtle-tf
$ roslaunch turtle_tf turtle_tf_demo.launch
$ rosrun turtlesim turtle_teleop_key
$ rosrun tf view_frames
  • 在home文件夹下会出现一个frames.pdf:

view_frames

3.2 查看齐次变换矩阵

查看两个海龟之间的rotation matrix RR 和translation matrix dd 可以使用命令行工具或者可视化工具。

设海龟1相对于海龟2的齐次变换矩阵为 H12H_{1}^{2} ,则 H12=H10×H02H_{1}^{2}=H_{1}^{0}\times H_{0}^{2}

其中角标0代表世界的基础矩阵base frame。

  • 命令行:
1
2
# 查看两只海龟turtle1和turtle2的R和d
$ rosrun tf tf_echo turtle1 turtle2

​ 结果:

命令行查看齐次变换矩阵

  • 可视化工具:
1
$ rosrun rviz rviz -d `rospack find turtle_tf` /rviz/turtle_rviz.rviz

3.3 如何实现一个tf广播器

  • 定义TF广播器(TransformBroadcaster)
  • 创建坐标变换值
  • 发布坐标变换(SendTransform)

代码:

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
/**
* 该例程产生tf数据,并计算、发布对应乌龟的速度指令
*/

#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>

std::string turtle_name;

void poseCallback(const turtlesim::PoseConstPtr& msg)
{
// 创建tf的广播器
static tf::TransformBroadcaster br;

// 初始化tf数据
tf::Transform transform;
transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
tf::Quaternion q;
q.setRPY(0, 0, msg->theta);
transform.setRotation(q);

// 广播world与海龟坐标系之间的tf数据
br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
}

int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_tf_broadcaster");

// 输入参数作为海龟的名字
if (argc != 2)
{
ROS_ERROR("need turtle name as argument");
return -1;
}

turtle_name = argv[1];

// 订阅海龟的位姿话题
ros::NodeHandle node;
ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);

// 循环等待回调函数
ros::spin();

return 0;
};


3.4 如何实现一个tf监听器

  • 定义TF监听器;(TransformListener)
  • 查找坐标变换;(waitForTransform、lookupTransform)

代码:

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
/**
* 该例程监听tf数据,并计算、发布turtle2的速度指令
*/

#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>

int main(int argc, char** argv)
{
// 初始化ROS节点
ros::init(argc, argv, "my_tf_listener");

// 创建节点句柄
ros::NodeHandle node;

// 请求产生turtle2
ros::service::waitForService("/spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");
turtlesim::Spawn srv;
add_turtle.call(srv);

// 创建发布turtle2速度控制指令的发布者
ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel", 10);

// 创建tf的监听器
tf::TransformListener listener;

ros::Rate rate(10.0);
while (node.ok())
{
// 获取turtle1与turtle2坐标系之间的tf数据
tf::StampedTransform transform;
try
{
listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0));
listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform);
}
catch (tf::TransformException &ex)
{
ROS_ERROR("%s",ex.what());
ros::Duration(1.0).sleep();
continue;
}

// 根据turtle1与turtle2坐标系之间的位置关系,发布turtle2的速度控制指令
geometry_msgs::Twist vel_msg;
vel_msg.angular.z = 4.0 * atan2(transform.getOrigin().y(),
transform.getOrigin().x());
vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
pow(transform.getOrigin().y(), 2));
turtle_vel.publish(vel_msg);

rate.sleep();
}
return 0;
};

3.5 配置launch文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<launch>

<!-- Turtlesim Node-->
<node pkg="turtlesim" type="turtlesim_node" name="sim"/>
<node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen"/>
<!-- 这里一定要加name标签,让海龟1和海龟2生成的节点不一样 -->
<!-- 通过广播器分别广播turtle1和world、turtle2和world的齐次变换矩阵 -->
<node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle1" name="turtle1_tf_broadcaster" />
<node pkg="learning_tf" type="turtle_tf_broadcaster" args="/turtle2" name="turtle2_tf_broadcaster" />
<!-- 通过监听器先生成海龟2 -->
<!-- 再通过两个海龟相对世界的齐次变换矩阵计算出两个海龟之间的齐次变换矩阵 -->
<!-- 两个海龟之间的齐次变换矩阵计算线速度和角速度 -->
<node pkg="learning_tf" type="turtle_tf_listener" name="listener" />

</launch>


4. 可视化显示与仿真工具

  • 日志输出工具-- $ rqt_console
  • 计算图可视化工具-- $ rqt_graph
  • 数据绘图工具-- $ rqt_plot
  • 图像渲染工具-- $ rqt_image_view
1
2
3
4
5
6
7
8
# 日志输出工具 
$ rqt_console
# 计算图可视化工具
$ rqt_graph
# 数据绘图工具
$ rqt_plot
# 图像渲染工具
$ rqt_image_view

4.1 数据可视化界面Rviz

Rviz是一款三维可视化工具,可以很好的兼容基于ROS软件框架的机器人平台。

  • 在rviz中,可以使用可拓展标记语言XML对机器人、周围物体等任意实物进行尺寸、质量、位置、材质、关节等属性的描述,并且在界面中呈现出来。
  • 同时,rviz还可以通过图形化的方式,实时显示机器人传感器的信息、机器人的运动状态、周围环境的变化等信息。
  • 总而言之,rviz通过机器人模型参数、机器人发布的传感信息等数据,为用户进行所有可检测信息的图形化显示。用户和开发者也可以在rviz的控制界面下,通过按钮、滑动条、数值等方式,控制机器人的行为。
  • 启动:
1
$ rosrun rviz rviz
  • 界面:

Rviz

基于rviz可以自定义人机交互界面。rviz是一个数据显示平台(需要数据)。

4.2 数据仿真界面Gazebo

Gazebo是一个数据仿真平台(创造数据)。它是一个三维动态物理仿真器,能准确高效的仿真在复杂的室内外环境下机器人群体。与游戏引擎类似,Gazebo能对一整套传感器进行高度逼真的物理仿真、为程序和用户提供交互接口。它典型的应用场景包括:

  • 测试机器人算法
  • 机器人的设计
  • 现实情境下的回溯测试
  • 启动:
1
2
# roslaunch gazebo_ros+想要使用的模型
$ roslaunch gazebo_ros mud_world.launch
  • 界面:

Gazebo

  • 为了保证模型加载顺利,需要提前将模型文件库下载并放置到~/.gazebo/models下。下载地址