本文主要是介绍古月·ROS2入门21讲——学习笔记(二)常用工具部分15-21讲,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
上篇:古月·ROS2入门21讲——学习笔记(一)核心概念部分1-14讲-CSDN博客
目录
第十五讲:Launch:多节点启动与配置脚本
1. Launch文件
2. 多节点启动
运行效果
文件解析
3. 命令行参数配置
运行效果
文件解析
4. 资源重映射
运行效果
文件解析
5. ROS参数设置
运行效果
文件解析
加载参数文件
Launch文件包含
文件解析
功能包编译配置
第十六讲:TF:机器人坐标系管理神器
1. 机器人中的坐标系
2. TF命令行操作
小海龟跟随例程
查看TF树
查询坐标变换信息
坐标系可视化
3. 静态TF广播
运行效果
代码解析
4. TF监听
运行效果
代码解析
5. 海龟跟随功能解析
运行效果
Launch文件解析
坐标系动态广播
海龟跟随
第十七讲:URDF:机器人建模方法
1. 机器人的组成
2. URDF
连杆Link的描述
关节Joint描述
完整机器人模型
3. 创建机器人模型
功能包结构
模型可视化效果
查看URDF模型结构
模型文件解析
第十八讲:Gazebo:三维物理仿真平台
1. 介绍
安装
运行
2. XACRO机器人模型优化
常量定义
数学计算
宏定义
文件包含
3. 机器人仿真模型配置
完善物理参数
添加Gazebo标签
配置传动装置
添加控制器插件
4. 构建仿真环境
机器人运动仿真
Ignition:下一代Gazebo
第十九讲:Rviz:三维可视化显示平台
1. Rviz三维可视化平台
Rviz介绍
运行方法
2. 彩色相机仿真与可视化
仿真插件配置
运行仿真环境
图像数据可视化
3. 三维相机仿真与可视化
仿真插件配置
运行仿真环境
点云数据可视化
4. 激光雷达仿真与可视化
仿真插件配置
运行仿真环境
点云数据可视化
5. Rviz vs Gazebo
第二十讲:RQT:模块化可视化工具
rqt介绍
日志显示
图像显示
发布话题数据/调用服务请求
绘制数据曲线
数据包管理
节点可视化
第十五讲:Launch:多节点启动与配置脚本
到目前为止,每当我们运行一个ROS节点,都需要打开一个新的终端运行一个命令。机器人系统中节点很多,每次都这样启动好麻烦呀。有没有一种方式可以一次性启动所有节点呢?答案当然是肯定的,那就是Launch启动文件,它是ROS系统中多节点启动与配置的一种脚本。
1. Launch文件
这是一个完整的Launch文件,乍看上去,好像Python代码呀,没错,ROS2中的Launch文件就是基于Python描述的。
Launch的核心目的是启动节点,我们在命令行中输入的各种参数,在Launch文件中,通过类似这样的很多代码模版,也可以进行配置,甚至还可以使用Python原有的编程功能,大大丰富了启动过程中的多样化配置。
Launch文件在ROS系统中出现的频次相当之高,它就像粘合剂一样,可以自由组装和配置各个节点,那如何编写或者阅读一个Launch文件呢,我们通过一系列例程带领大家来了解。
2. 多节点启动
先来看看如何启动多个节点。
运行效果
启动终端,使用ros2中的launch命令来启动第一个launch文件示例:
$ ros2 launch learning_launch simple.launch.py
运行成功后,就可以在终端中看到发布者和订阅者两个节点的日志信息啦。
文件解析
这两个节点是如何启动的呢?我们来分析下这个launch文件。
learning_launch/simple.launch.py
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类def generate_launch_description(): # 自动生成launch文件的函数return LaunchDescription([ # 返回launch文件的描述信息Node( # 配置一个节点的启动package='learning_topic', # 节点所在的功能包executable='topic_helloworld_pub', # 节点的可执行文件),Node( # 配置一个节点的启动package='learning_topic', # 节点所在的功能包executable='topic_helloworld_sub', # 节点的可执行文件名),])
3. 命令行参数配置
我们使用ros2命令在终端中启动节点时,还可以在命令后配置一些传入程序的参数,使用launch文件一样可以做到。
运行效果
比如我们想要运行一个Rviz可视化上位机,并且加载某一个配置文件,使用命令行的话,是这样的:
$ ros2 run rviz2 rviz2 -d <PACKAGE-PATH>/rviz/turtle_rviz.rviz
命令后边还得跟一长串配置文件的路径,如果放在launch文件里,启动就优雅很多了:
$ ros2 launch learning_launch rviz.launch.py
文件解析
命令行后边的参数是如何通过launch传入节点的呢?来看下这个launch文件。
learning_launch/rviz.launch.py
import osfrom ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类def generate_launch_description(): # 自动生成launch文件的函数rviz_config = os.path.join( # 找到配置文件的完整路径get_package_share_directory('learning_launch'),'rviz','turtle_rviz.rviz')return LaunchDescription([ # 返回launch文件的描述信息Node( # 配置一个节点的启动package='rviz2', # 节点所在的功能包executable='rviz2', # 节点的可执行文件名name='rviz2', # 对节点重新命名arguments=['-d', rviz_config] # 加载命令行参数)])
4. 资源重映射
ROS社区中的资源非常多,当我们使用别人代码的时候,经常会发现通信的话题名称不太符合我们的要求,能否对类似的资源重新命名呢?
为了提高软件的复用性,ROS提供了资源重映射的机制,可以帮助我们解决类似的问题。
运行效果
启动一个终端,运行如下例程,很快会看到出现了两个小海龟仿真器界面;再打开一个终端,发布如下话题,让海龟1动起来,海龟2也会一起运动:
$ ros2 launch learning_launch rviz.launch.py
$ ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"
文件解析
为什么两个海龟都会动呢?这里要用到turtlesim功能包里另外一个节点,叫做mimic,它的功能是订阅某一个海龟的Pose位置,通过计算,变换成一个同样运动的速度指令,发布出去。
至于mimic节点订阅或者发布的话题名叫什么呢?我们就可以通过重映射修改成对应任意海龟的名字。
learning_launch/remapping.launch.py
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类def generate_launch_description(): # 自动生成launch文件的函数return LaunchDescription([ # 返回launch文件的描述信息Node( # 配置一个节点的启动package='turtlesim', # 节点所在的功能包namespace='turtlesim1', # 节点所在的命名空间executable='turtlesim_node', # 节点的可执行文件名name='sim' # 对节点重新命名),Node( # 配置一个节点的启动package='turtlesim', # 节点所在的功能包namespace='turtlesim2', # 节点所在的命名空间executable='turtlesim_node', # 节点的可执行文件名name='sim' # 对节点重新命名),Node( # 配置一个节点的启动package='turtlesim', # 节点所在的功能包executable='mimic', # 节点的可执行文件名name='mimic', # 对节点重新命名remappings=[ # 资源重映射列表('/input/pose', '/turtlesim1/turtle1/pose'), # 将/input/pose话题名修改为/turtlesim1/turtle1/pose('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'), # 将/output/cmd_vel话题名修改为/turtlesim2/turtle1/cmd_vel])])
5. ROS参数设置
ROS系统中的参数,也可以在Launch文件中设置。
运行效果
启动一个终端,运行如下命令:
$ ros2 launch learning_launch parameters.launch.py
在启动的海龟仿真器中,我们看到背景颜色被改变了,这个颜色参数的设置就是在launch文件中完成的。
文件解析
我们看下在launch文件中如何来设置参数的。
learning_launch/parameters.launch.py
from launch import LaunchDescription # launch文件的描述类
from launch.actions import DeclareLaunchArgument # 声明launch文件内使用的Argument类
from launch.substitutions import LaunchConfiguration, TextSubstitutionfrom launch_ros.actions import Node # 节点启动的描述类def generate_launch_description(): # 自动生成launch文件的函数background_r_launch_arg = DeclareLaunchArgument('background_r', default_value=TextSubstitution(text='0') # 创建一个Launch文件内参数(arg)background_r)background_g_launch_arg = DeclareLaunchArgument('background_g', default_value=TextSubstitution(text='84') # 创建一个Launch文件内参数(arg)background_g)background_b_launch_arg = DeclareLaunchArgument('background_b', default_value=TextSubstitution(text='122') # 创建一个Launch文件内参数(arg)background_b)return LaunchDescription([ # 返回launch文件的描述信息background_r_launch_arg, # 调用以上创建的参数(arg)background_g_launch_arg,background_b_launch_arg,Node( # 配置一个节点的启动package='turtlesim',executable='turtlesim_node', # 节点所在的功能包name='sim', # 对节点重新命名parameters=[{ # ROS参数列表'background_r': LaunchConfiguration('background_r'), # 创建参数background_r'background_g': LaunchConfiguration('background_g'), # 创建参数background_g'background_b': LaunchConfiguration('background_b'), # 创建参数background_b}]),])
Attention
launch文件中出现的argument和parameter,虽都译为“参数”,但含义不同: - argument:仅限launch文件内部使用,方便在launch中调用某些数值; - parameter:ROS系统的参数,方便在节点见使用某些数值。
加载参数文件
以上例程我们在launch文件中一个一个的设置参数,略显麻烦,当参数比较多的时候,建议使用参数文件进行加载。
learning_launch/parameters_yaml.launch.py
import osfrom ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类def generate_launch_description(): # 自动生成launch文件的函数config = os.path.join( # 找到参数文件的完整路径get_package_share_directory('learning_launch'),'config','turtlesim.yaml')return LaunchDescription([ # 返回launch文件的描述信息Node( # 配置一个节点的启动package='turtlesim', # 节点所在的功能包executable='turtlesim_node', # 节点的可执行文件名namespace='turtlesim2', # 节点所在的命名空间name='sim', # 对节点重新命名parameters=[config] # 加载参数文件)])
Launch文件包含
在复杂的机器人系统中,launch文件也会有很多,此时我们可以使用类似编程中的include机制,让launch文件互相包含。
文件解析
learning_launch/namespaces.launch.py
import osfrom ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法from launch import LaunchDescription # launch文件的描述类
from launch.actions import IncludeLaunchDescription # 节点启动的描述类
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import GroupAction # launch文件中的执行动作
from launch_ros.actions import PushRosNamespace # ROS命名空间配置def generate_launch_description(): # 自动生成launch文件的函数parameter_yaml = IncludeLaunchDescription( # 包含指定路径下的另外一个launch文件PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('learning_launch'), 'launch'),'/parameters_nonamespace.launch.py']))parameter_yaml_with_namespace = GroupAction( # 对指定launch文件中启动的功能加上命名空间actions=[PushRosNamespace('turtlesim2'),parameter_yaml])return LaunchDescription([ # 返回launch文件的描述信息parameter_yaml_with_namespace])
功能包编译配置
...data_files=[('share/ament_index/resource_index/packages',['resource/' + package_name]),('share/' + package_name, ['package.xml']),(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),(os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))),(os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.*'))),],...
第十六讲:TF:机器人坐标系管理神器
个人感觉这讲比较难懂,建议多听几遍
1. 机器人中的坐标系
机器人中都有哪些坐标系呢?
比如在机械臂形态的机器人中,机器人安装的位置叫做基坐标系Base Frame,机器人安装位置在外部环境下的参考系叫做世界坐标系World Frame,机器人末端夹爪的位置叫做工具坐标系,外部被操作物体的位置叫做工件坐标系,在机械臂抓取外部物体的过程中,这些坐标系之间的关系也在跟随变化。
在移动机器人系统中,坐标系一样至关重要,比如一个移动机器人的中心点是基坐标系Base Link,雷达所在的位置叫做雷达坐标系laser link,机器人要移动,里程计会累积位置,这个位置的参考系叫做里程计坐标系odom,里程计又会有累积误差和漂移,绝对位置的参考系叫做地图坐标系map。
一层一层坐标系之间关系复杂,有一些是相对固定的,也有一些是不断变化的,看似简单的坐标系也在空间范围内变得复杂,良好的坐标系管理系统就显得格外重要。
关于坐标系变换关系的基本理论,在每一本机器人学的教材中都会有讲解,可以分解为平移和旋转两个部分,通过一个四乘四的矩阵进行描述,在空间中画出坐标系,那两者之间的变换关系,其实就是向量的数学描述。
ROS中TF功能的底层原理,就是对这些数学变换进行了封装,详细的理论知识大家可以参考机器人学的教材,我们主要讲解TF坐标管理系统的使用方法。
2. TF命令行操作
ROS中的TF该如何使用呢?我们先通过两只小海龟的示例,了解下基于坐标系的一种机器人跟随算法。
小海龟跟随例程
这个示例需要我们先安装相应的功能包,然后就可以通过一个launch文件启动,之后我们可以控制其中的一只小海龟,另外一只小海龟会自动跟随运动。
$ sudo apt install ros-humble-turtle-tf2-py ros-humble-tf2-tools
$ sudo pip3 install transforms3d
具体运行的效果如何?我们来试一试。
$ ros2 launch turtle_tf2_py turtle_tf2_demo.launch.py
$ ros2 run turtlesim turtle_teleop_key
当我们控制一只海龟运动时,另外一只海龟也会跟随运动。
查看TF树
在当前运行的两只海龟中,有哪些坐标系呢,我们可以通过这个小工具来做查看。
$ ros2 run tf2_tools view_frames
默认在当前终端路径下生成了一个frames.pdf文件,打开之后,就可以看到系统中各个坐标系的关系了。
查询坐标变换信息
只看到坐标系的结构还不行,如果我们想要知道某两个坐标系之间的具体关系,可以通过tf2_echo这个工具查看:
$ ros2 run tf2_ros tf2_echo turtle2 turtle1
运行成功后,终端中就会循环打印坐标系的变换数值了,由平移和旋转两个部分组成,还有旋转矩阵。
坐标系可视化
看数值还不直观?可以试试用可视化软件来做显示:
$ ros2 run rviz2 rviz2 -d $(ros2 pkg prefix --share turtle_tf2_py)/rviz/turtle_rviz.rviz
再让小海龟动起来,Rviz中的坐标轴就会开始运动,这样是不是更加直观了呢!
3. 静态TF广播
我们说TF的主要作用是对坐标系进行管理,那就管理一个试试呗?
坐标变换中最为简单的应该是相对位置不发生变化的情况,比如你家的房子在哪个位置,只要房子不拆,这个坐标应该就不会变化。
在机器人系统中也很常见,比如激光雷达和机器人底盘之间的位置关系,安装好之后基本不会变化。
在TF中,这种情况也称之为静态TF变换,我们来看看在程序中该如何实现?
运行效果
启动终端,运行如下命令:
$ ros2 run learning_tf static_tf_broadcaster
$ ros2 run tf2_tools view_frames
可以看到当前系统中存在两个坐标系,一个是world,一个是house,两者之间的相对位置不会发生改变,通过一个静态的TF对象进行维护。
代码解析
来看下在代码中是如何创建坐标系并且发布静态变换的。
learning_tf/static_tf_broadcaster.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2 TF示例-广播静态的坐标变换
"""import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from geometry_msgs.msg import TransformStamped # 坐标变换消息
import tf_transformations # TF坐标变换库
from tf2_ros.static_transform_broadcaster import StaticTransformBroadcaster # TF静态坐标系广播器类class StaticTFBroadcaster(Node):def __init__(self, name):super().__init__(name) # ROS2节点父类初始化self.tf_broadcaster = StaticTransformBroadcaster(self) # 创建一个TF广播器对象static_transformStamped = TransformStamped() # 创建一个坐标变换的消息对象static_transformStamped.header.stamp = self.get_clock().now().to_msg() # 设置坐标变换消息的时间戳static_transformStamped.header.frame_id = 'world' # 设置一个坐标变换的源坐标系static_transformStamped.child_frame_id = 'house' # 设置一个坐标变换的目标坐标系static_transformStamped.transform.translation.x = 10.0 # 设置坐标变换中的X、Y、Z向的平移static_transformStamped.transform.translation.y = 5.0 static_transformStamped.transform.translation.z = 0.0quat = tf_transformations.quaternion_from_euler(0.0, 0.0, 0.0) # 将欧拉角转换为四元数(roll, pitch, yaw)static_transformStamped.transform.rotation.x = quat[0] # 设置坐标变换中的X、Y、Z向的旋转(四元数)static_transformStamped.transform.rotation.y = quat[1]static_transformStamped.transform.rotation.z = quat[2]static_transformStamped.transform.rotation.w = quat[3]self.tf_broadcaster.sendTransform(static_transformStamped) # 广播静态坐标变换,广播后两个坐标系的位置关系保持不变def main(args=None):rclpy.init(args=args) # ROS2 Python接口初始化node = StaticTFBroadcaster("static_tf_broadcaster") # 创建ROS2节点对象并进行初始化rclpy.spin(node) # 循环等待ROS2退出node.destroy_node() # 销毁节点对象rclpy.shutdown()
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={'console_scripts': ['static_tf_broadcaster = learning_tf.static_tf_broadcaster:main',],},
经过这段代码,两个坐标系的变化是描述清楚了,到了使用的时候,我们又该如何查询呢?
4. TF监听
我们再来学习下如何查询两个坐标系之间的位置关系。
运行效果
启动一个终端,运行如下节点,就可以在终端中看到周期显示的坐标关系了。
$ ros2 run learning_tf tf_listener
代码解析
这个节点中是如何查询坐标关系的,我们来看下代码。
learning_tf/tf_listener.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2 TF示例-监听某两个坐标系之间的变换
"""import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import tf_transformations # TF坐标变换库
from tf2_ros import TransformException # TF左边变换的异常类
from tf2_ros.buffer import Buffer # 存储坐标变换信息的缓冲类
from tf2_ros.transform_listener import TransformListener # 监听坐标变换的监听器类class TFListener(Node):def __init__(self, name):super().__init__(name) # ROS2节点父类初始化self.declare_parameter('source_frame', 'world') # 创建一个源坐标系名的参数self.source_frame = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值'source_frame').get_parameter_value().string_valueself.declare_parameter('target_frame', 'house') # 创建一个目标坐标系名的参数self.target_frame = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值'target_frame').get_parameter_value().string_valueself.tf_buffer = Buffer() # 创建保存坐标变换信息的缓冲区self.tf_listener = TransformListener(self.tf_buffer, self) # 创建坐标变换的监听器self.timer = self.create_timer(1.0, self.on_timer) # 创建一个固定周期的定时器,处理坐标信息def on_timer(self):try:now = rclpy.time.Time() # 获取ROS系统的当前时间trans = self.tf_buffer.lookup_transform( # 监听当前时刻源坐标系到目标坐标系的坐标变换self.target_frame,self.source_frame,now)except TransformException as ex: # 如果坐标变换获取失败,进入异常报告self.get_logger().info(f'Could not transform {self.target_frame} to {self.source_frame}: {ex}')returnpos = trans.transform.translation # 获取位置信息quat = trans.transform.rotation # 获取姿态信息(四元数)euler = tf_transformations.euler_from_quaternion([quat.x, quat.y, quat.z, quat.w])self.get_logger().info('Get %s --> %s transform: [%f, %f, %f] [%f, %f, %f]' % (self.source_frame, self.target_frame, pos.x, pos.y, pos.z, euler[0], euler[1], euler[2]))def main(args=None):rclpy.init(args=args) # ROS2 Python接口初始化node = TFListener("tf_listener") # 创建ROS2节点对象并进行初始化rclpy.spin(node) # 循环等待ROS2退出node.destroy_node() # 销毁节点对象rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={'console_scripts': ['static_tf_broadcaster = learning_tf.static_tf_broadcaster:main','tf_listener = learning_tf.tf_listener:main',],},
5. 海龟跟随功能解析
还是之前小海龟跟随的示例,我们自己通过代码来实现一下。
运行效果
先看下实现的效果,启动终端后,通过如下命令启动例程:
$ ros2 launch learning_tf turtle_following_demo.launch.py
$ ros2 run turtlesim turtle_teleop_key
看到的效果和ROS自带的例程相同。
原理解析
在两只海龟的仿真器中,我们可以定义三个坐标系,比如仿真器的全局参考系叫做world,turtle1和turtle2坐标系在两只海龟的中心点,这样,turtle1和world坐标系的相对位置,就可以表示海龟1的位置,海龟2也同理。
要实现海龟2向海龟1运动,我们在两者中间做一个连线,再加一个箭头,怎么样,是不是有想起高中时学习的向量计算?我们说坐标变换的描述方法就是向量,所以在这个跟随例程中,用TF就可以很好的解决。
向量的长度表示距离,方向表示角度,有了距离和角度,我们随便设置一个时间,不就可以计算得到速度了么,然后就是速度话题的封装和发布,海龟2也就可以动起来了。
所以这个例程的核心就是通过坐标系实现向量的计算,两只海龟还会不断运动,这个向量也得按照某一个周期计算,这就得用上TF的动态广播与监听了。
我们一起看下代码该如何实现。
Launch文件解析
先来看下刚才运行的launch文件,里边启动了四个节点,分别是:
- 小海龟仿真器
- 海龟1的坐标系广播
- 海龟2的坐标系广播
- 海龟跟随控制
其中,两个坐标系的广播复用了turtle_tf_broadcaster节点,通过传入的参数名修改维护的坐标系名称。
learning_tf/launch/turtle_following_demo.launch.py
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',executable='turtlesim_node',name='sim'),Node(package='learning_tf',executable='turtle_tf_broadcaster',name='broadcaster1',parameters=[{'turtlename': 'turtle1'}]),DeclareLaunchArgument('target_frame', default_value='turtle1',description='Target frame name.'),Node(package='learning_tf',executable='turtle_tf_broadcaster',name='broadcaster2',parameters=[{'turtlename': 'turtle2'}]),Node(package='learning_tf',executable='turtle_following',name='listener',parameters=[{'target_frame': LaunchConfiguration('target_frame')}]), ])
坐标系动态广播
海龟1和海龟2在world坐标系下的坐标变换,在turtle_tf_broadcaster节点中实现,除了海龟坐标系的名字不同之外,针对两个海龟的功能是一样的。
learning_tf/turtle_tf_broadcaster.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2 TF示例-广播动态的坐标变换
"""import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
from geometry_msgs.msg import TransformStamped # 坐标变换消息
import tf_transformations # TF坐标变换库
from tf2_ros import TransformBroadcaster # TF坐标变换广播器
from turtlesim.msg import Pose # turtlesim小海龟位置消息class TurtleTFBroadcaster(Node):def __init__(self, name):super().__init__(name) # ROS2节点父类初始化self.declare_parameter('turtlename', 'turtle') # 创建一个海龟名称的参数self.turtlename = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值'turtlename').get_parameter_value().string_valueself.tf_broadcaster = TransformBroadcaster(self) # 创建一个TF坐标变换的广播对象并初始化self.subscription = self.create_subscription( # 创建一个订阅者,订阅海龟的位置消息Pose,f'/{self.turtlename}/pose', # 使用参数中获取到的海龟名称self.turtle_pose_callback, 1)def turtle_pose_callback(self, msg): # 创建一个处理海龟位置消息的回调函数,将位置消息转变成坐标变换transform = TransformStamped() # 创建一个坐标变换的消息对象transform.header.stamp = self.get_clock().now().to_msg() # 设置坐标变换消息的时间戳transform.header.frame_id = 'world' # 设置一个坐标变换的源坐标系transform.child_frame_id = self.turtlename # 设置一个坐标变换的目标坐标系transform.transform.translation.x = msg.x # 设置坐标变换中的X、Y、Z向的平移transform.transform.translation.y = msg.ytransform.transform.translation.z = 0.0q = tf_transformations.quaternion_from_euler(0, 0, msg.theta) # 将欧拉角转换为四元数(roll, pitch, yaw)transform.transform.rotation.x = q[0] # 设置坐标变换中的X、Y、Z向的旋转(四元数)transform.transform.rotation.y = q[1]transform.transform.rotation.z = q[2]transform.transform.rotation.w = q[3]# Send the transformationself.tf_broadcaster.sendTransform(transform) # 广播坐标变换,海龟位置变化后,将及时更新坐标变换信息def main(args=None):rclpy.init(args=args) # ROS2 Python接口初始化node = TurtleTFBroadcaster("turtle_tf_broadcaster") # 创建ROS2节点对象并进行初始化rclpy.spin(node) # 循环等待ROS2退出node.destroy_node() # 销毁节点对象rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={'console_scripts': ['static_tf_broadcaster = learning_tf.static_tf_broadcaster:main','turtle_tf_broadcaster = learning_tf.turtle_tf_broadcaster:main','tf_listener = learning_tf.tf_listener:main',],},
海龟跟随
坐标系都正常广播了,接下来我们就可以订阅两只海龟的位置关系,并且变换成速度指令进行控制啦。
learning_tf/turtle_following.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2 TF示例-通过坐标变化实现海龟跟随功能
"""import math
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import tf_transformations # TF坐标变换库
from tf2_ros import TransformException # TF左边变换的异常类
from tf2_ros.buffer import Buffer # 存储坐标变换信息的缓冲类
from tf2_ros.transform_listener import TransformListener # 监听坐标变换的监听器类
from geometry_msgs.msg import Twist # ROS2 速度控制消息
from turtlesim.srv import Spawn # 海龟生成的服务接口
class TurtleFollowing(Node):def __init__(self, name):super().__init__(name) # ROS2节点父类初始化self.declare_parameter('source_frame', 'turtle1') # 创建一个源坐标系名的参数self.source_frame = self.get_parameter( # 优先使用外部设置的参数值,否则用默认值'source_frame').get_parameter_value().string_valueself.tf_buffer = Buffer() # 创建保存坐标变换信息的缓冲区self.tf_listener = TransformListener(self.tf_buffer, self) # 创建坐标变换的监听器self.spawner = self.create_client(Spawn, 'spawn') # 创建一个请求产生海龟的客户端self.turtle_spawning_service_ready = False # 是否已经请求海龟生成服务的标志位self.turtle_spawned = False # 海龟是否产生成功的标志位self.publisher = self.create_publisher(Twist, 'turtle2/cmd_vel', 1) # 创建跟随运动海龟的速度话题self.timer = self.create_timer(1.0, self.on_timer) # 创建一个固定周期的定时器,控制跟随海龟的运动def on_timer(self):from_frame_rel = self.source_frame # 源坐标系to_frame_rel = 'turtle2' # 目标坐标系if self.turtle_spawning_service_ready: # 如果已经请求海龟生成服务if self.turtle_spawned: # 如果跟随海龟已经生成try:now = rclpy.time.Time() # 获取ROS系统的当前时间trans = self.tf_buffer.lookup_transform( # 监听当前时刻源坐标系到目标坐标系的坐标变换to_frame_rel,from_frame_rel,now)except TransformException as ex: # 如果坐标变换获取失败,进入异常报告self.get_logger().info(f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}')returnmsg = Twist() # 创建速度控制消息scale_rotation_rate = 1.0 # 根据海龟角度,计算角速度msg.angular.z = scale_rotation_rate * math.atan2(trans.transform.translation.y,trans.transform.translation.x)scale_forward_speed = 0.5 # 根据海龟距离,计算线速度msg.linear.x = scale_forward_speed * math.sqrt(trans.transform.translation.x ** 2 +trans.transform.translation.y ** 2)self.publisher.publish(msg) # 发布速度指令,海龟跟随运动else: # 如果跟随海龟没有生成if self.result.done(): # 查看海龟是否生成self.get_logger().info(f'Successfully spawned {self.result.result().name}')self.turtle_spawned = True else: # 依然没有生成跟随海龟self.get_logger().info('Spawn is not finished')else: # 如果没有请求海龟生成服务if self.spawner.service_is_ready(): # 如果海龟生成服务器已经准备就绪request = Spawn.Request() # 创建一个请求的数据request.name = 'turtle2' # 设置请求数据的内容,包括海龟名、xy位置、姿态request.x = float(4)request.y = float(2)request.theta = float(0)self.result = self.spawner.call_async(request) # 发送服务请求self.turtle_spawning_service_ready = True # 设置标志位,表示已经发送请求else:self.get_logger().info('Service is not ready') # 海龟生成服务器还没准备就绪的提示def main(args=None):rclpy.init(args=args) # ROS2 Python接口初始化node = TurtleFollowing("turtle_following") # 创建ROS2节点对象并进行初始化rclpy.spin(node) # 循环等待ROS2退出node.destroy_node() # 销毁节点对象rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={'console_scripts': ['static_tf_broadcaster = learning_tf.static_tf_broadcaster:main','turtle_tf_broadcaster = learning_tf.turtle_tf_broadcaster:main','tf_listener = learning_tf.tf_listener:main','turtle_following = learning_tf.turtle_following:main',],},
第十七讲:URDF:机器人建模方法
ROS是机器人操作系统,当然要给机器人使用啦,不过在使用之前,还得让ROS认识下我们使用的机器人,如何把一个机器人介绍给ROS呢?
为此,ROS专门提供了一种机器人建模方法——URDF,用来描述机器人外观、性能等各方面属性。
1. 机器人的组成
建模描述机器人的过程中,我们自己需要先熟悉机器人的组成和参数,比如机器人一般是由硬件结构、驱动系统、传感器系统、控制系统四大部分组成,市面上一些常见的机器人,无论是移动机器人还是机械臂,我们都可以按照这四大组成部分进行分解。
- 硬件结构就是底盘、外壳、电机等实打实可以看到的设备;
- 驱动系统就是可以驱使这些设备正常使用的装置,比如电机的驱动器,电源管理系统等;
- 传感系统包括电机上的编码器、板载的IMU、安装的摄像头、雷达等等,便于机器人感知自己的状态和外部的环境;
- 控制系统就是我们开发过程的主要载体了,一般是树莓派、电脑等计算平台,以及里边的操作系统和应用软件。
机器人建模的过程,其实就是按照类似的思路,通过建模语言,把机器人每一个部分都描述清楚,再组合起来的过程。
2. URDF
ROS中的建模方法叫做URDF,全称是统一机器人描述格式,不仅可以清晰描述机器人自身的模型,还可以描述机器人的外部环境,比如这里的桌子,也可以算作一个模型。
URDF模型文件使用的是XML格式,右侧就是一个机器人的URDF描述,乍看上去,有点像网页开发的源代码,都是由一系列尖括号包围的标签和其中的属性组合而成。
如何使用这样一个文件描述机器人呢?比如这个机械臂,大家可以看下自己的手臂,我们的手臂是由大臂和小臂组成,他们独自是无法运动的,必须通过一个手肘关节连接之后,才能通过肌肉驱动,产生相对运动。
在建模中,大臂和小臂就类似机器人的这些独立的刚体部分,称为连杆Link,手肘就类似于机器人电机驱动部分,称为关节joint。
所以在URDF建模过程中,关键任务就是通过这里的<link>和<joint>,理清楚每一个连杆和关节的描述信息。
连杆Link的描述
<link>标签用来描述机器人某个刚体部分的外观和物理属性,外观包括尺寸、颜色、形状,物理属性包括质量、惯性矩阵、碰撞参数等。
以这个机械臂连杆为例,它的link描述如下:
link标签中的name表示该连杆的名称,我们可以自定义,未来joint连接link的时候,会使用到这个名称。
link里边的<visual>部分用来描述机器人的外观,比如:
- <geometry>表示几何形状,里边使用<mesh>调用了一个在三维软件中提前设计好的蓝色外观,就是这个stl文件,看上去和真实机器人是一致的
- <origin>表示坐标系相对初始位置的偏移,分别是x、y、z方向上的平移,和roll、pitch、raw旋转,不需要偏移的话,就全为0。
第二个部分<collision>,描述碰撞参数,里边的内容似乎和<visual>一样,也有<geometry>和<origin>,看似相同,其实区别还是比较大的。
- <visual>部分重在描述机器人看上去的状态,也就是视觉效果;
- <collision>部分则是描述机器人运动过程中的状态,比如机器人与外界如何接触算作碰撞。
在这个机器人模型中,蓝色部分是通过<visual>来描述的,在实际控制过程中,这样复杂的外观在计算碰撞检测时,要求的算力较高,为了简化计算,我们将碰撞检测用的模型简化为了绿色框的圆柱体,也就是<collision>里边<geometry>描述的形状。<origin>坐标系偏移也是类似,可以描述刚体质心的偏移。
如果是移动机器人的话,link也可以用来描述小车的车体、轮子等部分。
关节Joint描述
机器人模型中的刚体最终要通过关节joint连接之后,才能产生相对运动。
URDF中的关节有六种运动类型。
- continuous,描述旋转运动,可以围绕某一个轴无限旋转,比如小车的轮子,就属于这种类型。
- revolute,也是旋转关节,和continuous类型的区别在于不能无限旋转,而是带有角度限制,比如机械臂的两个连杆,就属于这种运动。
- prismatic,是滑动关节,可以沿某一个轴平移,也带有位置的极限,一般直线电机就是这种运动方式。
- fixed,固定关节,是唯一一种不允许运动的关节,不过使用还是比较频繁的,比如相机这个连杆,安装在机器人上,相对位置是不会变化的,此时使用的连接方式就是Fixed。
- Floating是浮动关节,第六种planar是平面关节,这两种使用相对较少。
在URDF模型中,每一个link都使用这样一段xml内容描述,比如关节的名字叫什么,运动类型是哪一种。
- parent标签:描述父连杆;
- child标签:描述子连杆,子连杆会相对父连杆发生运动;
- origin:表示两个连杆坐标系之间的关系,也就是图中红色的向量,可以理解为这两个连杆该如何安装到一起;
- axis表示关节运动轴的单位向量,比如z等于1,就表示这个旋转运动是围绕z轴的正方向进行的;
- limit就表示运动的一些限制了,比如最小位置,最大位置,和最大速度等。
ROS中关于平移的默认单位是m,旋转是弧度(不是度),所以这里的3.14就表示可以在-180度到180度之间运动,线速度是m/s,角速度是rad/s。
完整机器人模型
最终所有的link和joint标签完成了对机器人每个部分的描述和组合,全都放在一个robot标签中,就形成了完整的机器人模型。
所以大家在看某一个URDF模型时,先不着急看每一块代码的细节,先来找link和joint,看下这个机器人是由哪些部分组成的,了解完全局之后,再看细节。
3. 创建机器人模型
好啦,讲了这么多,还是要看一个完整的示例。
我们以这款移动机器人模型为例,一起看下它的URDF建模过程。
功能包结构
机器人的模型放置在learning_urdf功能包中,功能包中包含的文件夹如下:
- urdf:存放机器人模型的URDF或xacro文件
- meshes:放置URDF中引用的模型渲染文件
- launch:保存相关启动文件
- rviz:保存rviz的配置文件
模型可视化效果
我们先来看下这个模型的效果,尝试逆向分下一下机器人的结构。
$ ros2 launch learning_urdf display.launch.py
从可视化的效果来看,这个机器人由五个link和4个joint组成。
查看URDF模型结构
我们分析的对不对呢,可以在模型文件的路径下,使用urdf_to_graphviz这个小工具来分析下。
$ urdf_to_graphviz mbot_base.urdf # 在模型文件夹下运行
模型文件解析
具体URDF模型什么样的,还是要打开模型来研究。
learning_urdf/urdf/mbot_base.urdf
<?xml version="1.0" ?>
<robot name="mbot"><link name="base_link"><visual><origin xyz=" 0 0 0" rpy="0 0 0" /><geometry><cylinder length="0.16" radius="0.20"/></geometry><material name="yellow"><color rgba="1 0.4 0 1"/></material></visual></link><joint name="left_wheel_joint" type="continuous"><origin xyz="0 0.19 -0.05" rpy="0 0 0"/><parent link="base_link"/><child link="left_wheel_link"/><axis xyz="0 1 0"/></joint><link name="left_wheel_link"><visual><origin xyz="0 0 0" rpy="1.5707 0 0" /><geometry><cylinder radius="0.06" length = "0.025"/></geometry><material name="white"><color rgba="1 1 1 0.9"/></material></visual></link><joint name="right_wheel_joint" type="continuous"><origin xyz="0 -0.19 -0.05" rpy="0 0 0"/><parent link="base_link"/><child link="right_wheel_link"/><axis xyz="0 1 0"/></joint><link name="right_wheel_link"><visual><origin xyz="0 0 0" rpy="1.5707 0 0" /><geometry><cylinder radius="0.06" length = "0.025"/></geometry><material name="white"><color rgba="1 1 1 0.9"/></material></visual></link><joint name="front_caster_joint" type="continuous"><origin xyz="0.18 0 -0.095" rpy="0 0 0"/><parent link="base_link"/><child link="front_caster_link"/><axis xyz="0 1 0"/></joint><link name="front_caster_link"><visual><origin xyz="0 0 0" rpy="0 0 0"/><geometry><sphere radius="0.015" /></geometry><material name="black"><color rgba="0 0 0 0.95"/></material></visual></link><joint name="back_caster_joint" type="continuous"><origin xyz="-0.18 0 -0.095" rpy="0 0 0"/><parent link="base_link"/><child link="back_caster_link"/><axis xyz="0 1 0"/></joint><link name="back_caster_link"><visual><origin xyz="0 0 0" rpy="0 0 0"/><geometry><sphere radius="0.015" /></geometry><material name="black"><color rgba="0 0 0 0.95"/></material></visual></link></robot>
后续我将learning_urdf文件夹复制到一个新的工作空间中并改名为learning_urdf_2,然后需要将所有learning_urdf的位置都改为learning_urdf_2,重新使用colcon build编译,并检查install文件夹中的setup.sh文件中的名字是否正确,然后使用source setup.sh命令使得修改生效,才成功运行的
第十八讲:Gazebo:三维物理仿真平台
1. 介绍
Gazebo是ROS系统中最为常用的三维物理仿真平台,支持动力学引擎,可以实现高质量的图形渲染,不仅可以模拟机器人及周边环境,还可以加入摩擦力、弹性系数等物理属性。
比如我们要开发一个火星车,那就可以在Gazebo中模拟火星表面的环境,再比如我们做无人机,续航和限飞都导致我们没有办法频繁用实物做实验,此时不妨使用Gazebo先做仿真,等算法开发的差不多了,再部署到实物上来运行。
所以类似Gazebo这样的仿真平台,可以帮助我们验证机器人算法、优化机器人设计、测试机器人场景应用,为机器人开发提供更多可能。
安装
Gazebo如何使用呢?我们不妨先把它给跑起来,互相认识一下。
为了确保系统中已经完整安装了Gazebo相关的功能包,大家可以通过这样一个命令,简单直接的把和gazebo相关的包都给装上:
$ sudo apt install ros-humble-gazebo-*
运行
通过这句命令就可以启动啦:
$ ros2 launch gazebo_ros gazebo.launch.py
这行命令输入没有反应的话参考:ros2 launch gazebo_ros gazebo.launch.py无法启动-CSDN博客
为保证模型顺利加载,请将离线模型下载并放置到~/.gazebo/models路径下
下载链接如下:https://github.com/osrf/gazebo_models
2. XACRO机器人模型优化
我们之前设计好的URDF模型此时还不能直接放到Gazebo中,需要我们做一些优化。这里给大家介绍一个URDF文件格式的升级版本——XACRO文件。
同样也是对机器人URDF模型的创建,XACRO文件加入了更多编程化的实现方法,可以让模型创建更友好。
比如:
-
宏定义,一个小车有4个轮子,每个轮子都一样,我们就没必要创建4个一样的link,像函数定义一样,做一个可重复使用的模块就可以了。
-
文件包含,复杂机器人的模型文件可能会很长,为了切分不同的模块,比如底盘、传感器,我们还可以把不同模块的模型放置在不同的文件中,然后再用一个总体文件做包含调用。
-
可编程接口,比如在XACRO模型文件中,定义一些常量,描述机器人的尺寸,定义一些变量,在调用宏定义的时候传递数据,还可以在模型中做数据计算,甚至加入条件语句,比如你的机器人叫A,就有摄像头,如果叫B,就没有摄像头。
XACRO建模过程就像写代码一样,功能更为丰富了。
接下来,我们就通过XACRO文件对移动机器人的模型做一下优化,大家先要使用这句命令安装必要的功能包。
$ sudo apt install ros-humble-xacro
以下是一些常用的XACRO文件语法,大家了解下。
常量定义
<xacro:property>标签用来定义一些常量,比如这样定义一个PI的常量名为“M_PI”,值为“3.14159”,在调用的时候,通过$加大括号,里边就可以使用定义好的常量了。
针对原本移动机器人的URDF文件,我们就可以把底盘的质量、尺寸,轮子的质量、尺寸、安装位置,这些不会变化的数据,都通过常量定义,未来需要修改的时候也很方便,就不需要在模型文件中一行一行找了。
数学计算
如果需要做数学计算,同样是在“${}”中进行,比如某一个位置,我们可以通过这两个常量做运算得到,就加入了加法和除法运算。
在移动机器人的模型中,很多有相对关系的数据,我们尽量都改成公式计算,如果直接写结果的数值,未来修改的时候,可能根本想不起来这个数据是怎么来的。
所有数学运算都会转换成浮点数进行,以保证运算精度
宏定义
机器人的轮子我们也做成宏定义,定义方式是通过这个<xacro:macro>标签描述的,还可以像函数一样,设置里边会用到的一些参数,比如这里的A、B、C。
当需要使用这个宏的时候,就可以像这样,通过宏名字的标签,来调用,同时要记得把几个参数设置好。
比如在模型中,轮子的宏定义是这样的,包含了link描述和joint关节设置,link的名称和关节的位置,是通过输入的参数来区分的,在使用的时候,通过这两句调用,两个轮子就出现了。
这里的1和-1,是设置关节位置的,刚好是一个镜像关系。
文件包含
宏定义是可以嵌套的,于是我们把机器人的底盘也做成了一个宏,然后使用另外一个模型文件,对底盘宏定义的文件做了一个包含,然后再调用。
这种流程是不是似曾相识,很像C语言中的include文件包含,然后再去调用里边的某些函数。
到这里为止,仿真使用的模型优化还没有结束,接下来我们还得加入一些仿真必备的模块和参数。
3. 机器人仿真模型配置
完善物理参数
第一步是确保每一个link都有惯性参数和碰撞属性,因为Gazebo是物理仿真平台,必要的物理参数是一定需要的。
添加Gazebo标签
第二步是为link添加gazebo标签,主要是为了可以在gazebo中渲染每一个link的颜色,因为URDF中的颜色系统和gazebo中的不同,所以得做一步这样的冗余配置。
配置传动装置
第三步是要给运动的joint配置传动装置,可以理解为仿真了一个电机。
添加控制器插件
第四步,要添加一个gazebo的控制器插件,小车是差速控制的,那就添加差速控制器插件,这样在不同角度下两个电机的速度分配,就可以交给控制器插件来完成了。
4. 构建仿真环境
接下来就考虑如何把模型加载到Gazebo中了,需要用到一个gazebo提供的功能节点spwan_entity。
learning_gazebo/launch/load_urdf_into_gazebo.launch.py
import osfrom ament_index_python.packages import get_package_share_directoryfrom launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSourcefrom launch_ros.actions import Nodedef generate_launch_description():# Include the robot_state_publisher launch file, provided by our own package. Force sim time to be enabled# !!! MAKE SURE YOU SET THE PACKAGE NAME CORRECTLY !!!package_name='learning_gazebo' #<--- CHANGE MEworld_file_path = 'worlds/neighborhood.world'pkg_path = os.path.join(get_package_share_directory(package_name))world_path = os.path.join(pkg_path, world_file_path) # Pose where we want to spawn the robotspawn_x_val = '0.0'spawn_y_val = '0.0'spawn_z_val = '0.0'spawn_yaw_val = '0.0'mbot = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory(package_name),'launch','mbot.launch.py')]), launch_arguments={'use_sim_time': 'true', 'world':world_path}.items())# Include the Gazebo launch file, provided by the gazebo_ros packagegazebo = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')]),)# Run the spawner node from the gazebo_ros package. The entity name doesn't really matter if you only have a single robot.spawn_entity = Node(package='gazebo_ros', executable='spawn_entity.py',arguments=['-topic', 'robot_description','-entity', 'mbot','-x', spawn_x_val,'-y', spawn_y_val,'-z', spawn_z_val,'-Y', spawn_yaw_val],output='screen')# Launch them all!return LaunchDescription([mbot,gazebo,spawn_entity,])
机器人运动仿真
万事俱备,接下来就是见证奇迹的时刻。
我们需要运行两句命令,第一句启动仿真环境,第二句启动键盘控制节点。
$ ros2 launch learning_gazebo load_urdf_into_gazebo.launch.py
$ ros2 run teleop_twist_keyboard teleop_twist_keyboard
虚拟机中运行时需要关闭硬件加速:echo " export SVGA_VGPU10=0" >> ~/.bashrc
通过键盘上的“i、j、,、l”几个按键,就可以控制机器人前后左右运动啦。
Ignition:下一代Gazebo
随着技术的进步,Gazebo仿真平台也在不断迭代,新一代的Gazebo命名为Ignition,从渲染效果和仿真流畅度上都有较大的变化,我们不妨也来试一下。
$ sudo apt install ros-humble-ros-ign
$ ros2 launch ros_ign_gazebo_demos rgbd_camera_bridge.launch.py
运行成功后,会打开Ignition的仿真界面和Rviz上位机,我们可以看到RGBD相机仿真后发布的图像数据。
更多新版本仿真器的信息,大家也可以参考官方网站:
www.ignitionrobotics.org/
第十九讲:Rviz:三维可视化显示平台
1. Rviz三维可视化平台
机器人开发过程中,各种各样的功能,如果我们只是从数据层面去做分析,很难快速理解数据的效果,比如给你一堆0到255的数字,问这幅图像描述的内容是什么?你肯定一脸懵。但如果我们把这些数字通过颜色渲染出来,岂不就一目了然么?
类似的场景还有很多,比如机器人模型,我们需要知道自己设计的模型长啥样,还有模型内部众多坐标系在运动过程中都在哪些位置。
再比如机械臂运动规划和移动机器人自主导航,我们希望可以看到机器人周边的环境、规划的路径,当然还有传感器的信息,摄像头、三维相机、激光雷达等等,数据是用来做计算的,可视化的效果才是给人看的。
所以,数据可视化可以大大提高开发效率,Rviz就是这样一款机器人开发过程中的数据可视化软件,机器人模型、传感器信息、环境信息等等,全都可以在这里搞定。
Rviz介绍
一句话说明Rviz的功能,只要有数据,它就可以可视化,只有我们想不到的,没有Rviz做不到的。
Rviz的核心框架是基于Qt可视化工具打造的一个开放式平台,官方出厂就自带了很多机器人常用的可视化显示插件,只要我们按照ROS中的消息发布对应的话题,就可以看到图形化的效果了。如果我们对显示的效果不满意,或者想添加某些新的显示项,也可以在Rviz这个平台中,开发更多可视化效果,方便打造我们自己的上位机。
运行方法
启动一个终端,使用如下命令即可启动:
$ ros2 run rviz2 rviz2
2. 彩色相机仿真与可视化
摄像头肯定是最为常用的一种传感器了,我们先来给机器人装上摄像头。
仿真插件配置
关于传感器的仿真,都需要使用Gazebo提供的插件,摄像头对应的插件叫做libgazebo_ros_camera.so,我们对照模型的代码给大家介绍这个插件的使用方法。
learning_gazebo/urdf/sensers/camera_gazebo.xacro
<gazebo reference="${prefix}_link"><sensor type="camera" name="camera_node"><update_rate>30.0</update_rate><camera name="head"><horizontal_fov>1.3962634</horizontal_fov><image><width>1280</width><height>720</height><format>R8G8B8</format></image><clip><near>0.02</near><far>300</far></clip><noise><type>gaussian</type><mean>0.0</mean><stddev>0.007</stddev></noise></camera><plugin name="gazebo_camera" filename="libgazebo_ros_camera.so"><ros><!-- <namespace>stereo</namespace> --><remapping>~/image_raw:=image_raw</remapping><remapping>~/camera_info:=camera_info</remapping></ros><camera_name>${prefix}</camera_name><frame_name>${prefix}_link</frame_name><hack_baseline>0.2</hack_baseline></plugin></sensor>
</gazebo>
主要配置项如下:
- <sensor>标签:描述传感器
type:传感器类型,camera
name:摄像头命名,自由设置
- <camera>标签:描述摄像头参数
分辨率,编码格式,图像范围,噪音参数等
- <plugin>标签:加载摄像头仿真插件
运行仿真环境
$ ros2 launch learning_gazebo load_mbot_camera_into_gazebo.launch.py
可以使用命令行看下仿真出来的图像话题:
图像数据可视化
我们使用Rviz可视化显示图像信息,先来启动Rviz:
$ ros2 run rviz2 rviz2
启动成功后,在左侧Displays窗口中点击“Add”,找到Image显示项,OK确认后就可以加入显示列表啦,然后配置好该显示项订阅的图像话题,就可以顺利看到机器人的摄像头图像啦。
3. 三维相机仿真与可视化
二维摄像头不过瘾,想不想试试三维相机,比如我们常用的Kinect体感传感器,或者Intel的Realsense,可以获取外部环境的点云数据。这种相机的价格比usb摄像头可贵不少,不过我们也可以通过仿真,一分钱不用,就可以玩起来。
仿真插件配置
三维相机使用的Gazebo插件也是libgazebo_ros_camera.so,配置方法如下:
learning_gazebo/urdf/sensers/kinect_gazebo.xacro
<gazebo reference="${prefix}_link"><sensor type="depth" name="${prefix}"><always_on>true</always_on><update_rate>15.0</update_rate><pose>0 0 0 0 0 0</pose><camera name="kinect"><horizontal_fov>${60.0*M_PI/180.0}</horizontal_fov><image><format>R8G8B8</format><width>640</width><height>480</height></image><clip><near>0.05</near><far>8.0</far></clip></camera><plugin name="${prefix}_controller" filename="libgazebo_ros_camera.so"><ros><!-- <namespace>${prefix}</namespace> --><remapping>${prefix}/image_raw:=rgb/image_raw</remapping><remapping>${prefix}/image_depth:=depth/image_raw</remapping><remapping>${prefix}/camera_info:=rgb/camera_info</remapping><remapping>${prefix}/camera_info_depth:=depth/camera_info</remapping><remapping>${prefix}/points:=depth/points</remapping></ros><camera_name>${prefix}</camera_name><frame_name>${prefix}_frame_optical</frame_name><hack_baseline>0.07</hack_baseline><min_depth>0.001</min_depth><max_depth>300.0</max_depth></plugin></sensor>
</gazebo>
运行仿真环境
使用如下命令启动仿真环境:
$ ros2 launch learning_gazebo load_mbot_rgbd_into_gazebo.launch.py
点云数据可视化
运行Rviz:
$ ros2 run rviz2 rviz2
同样的流程,点击Add,添加PointCloud2,设置订阅的点云话题,还要配置Rviz的参考系是odom,就可以看到点云数据啦,每一个点都是由xyz位置和rgb颜色组成。
4. 激光雷达仿真与可视化
除了摄像头和三维相机,激光雷达也是很多移动机器人常备的传感器,包括自动驾驶汽车,我们也来试一试。
仿真插件配置
雷达使用的Gazebo插件是libgazebo_ros_ray_sensor.so,配置方法如下:
learning_gazebo/urdf/sensers/lidar_gazebo.xacro
<gazebo reference="${prefix}_link"><sensor type="ray" name="rplidar"><update_rate>20</update_rate><ray><scan><horizontal><samples>360</samples><resolution>1</resolution><min_angle>-3</min_angle><max_angle>3</max_angle></horizontal></scan><range><min>0.10</min><max>30.0</max><resolution>0.01</resolution></range><noise><type>gaussian</type><mean>0.0</mean><stddev>0.01</stddev></noise></ray><plugin name="gazebo_rplidar" filename="libgazebo_ros_ray_sensor.so"><ros><namespace>/</namespace><remapping>~/out:=scan</remapping></ros><output_type>sensor_msgs/LaserScan</output_type></plugin></sensor>
</gazebo>
运行仿真环境
使用如下命令启动仿真环境:
$ ros2 launch learning_gazebo load_mbot_laser_into_gazebo.launch.py
在话题列表中也可以看到激光雷达啦。
点云数据可视化
启动Rviz:
$ ros2 run rviz2 rviz2
点击Add,选择Laserscan,然后配置订阅的话题名,rviz的固定坐标系依然是odom,此时就可以看到激光点啦。
5. Rviz vs Gazebo
好啦,通过这几个案例,相信大家对Rviz可视化平台的使用流程已经非常熟悉了,也了解了常用传感器的仿真方法。
讲到这里,Gazebo和Rviz这两个软件的具体功能,大家是不是会有一些混淆。
我们再来强调下:
- Gazebo是仿真平台,核心功能是创造数据,我们没有机器人或者传感器,它可以帮我们做一个虚拟的;
- Rviz是可视化平台,核心功能是显示数据,如果没有数据,它也巧妇难为无米之炊。
所以在很多时候,我们使用Gazebo做机器人仿真的时候,也会启动Rviz来显示仿真环境的信息,如果自己手上有真实机器人的话,Gazebo就用不到了,不过还是会用Rviz显示真实机器人传感器的信息。
第二十讲:RQT:模块化可视化工具
ROS中的Rviz功能已经很强大了,不过有些场景下,我们可能更需要一些简单的模块化的可视化工具,比如只显示一个摄像头的图像,使用Rviz的话,难免会觉得操作有点麻烦。
此时,我们就会用到ROS提供的另外一种模块化可视化工具——rqt。
rqt介绍
正如RQT的命名,它和Rviz一样,也是基于QT可视化工具开发而来,在使用前,我们需要通过这样一句指令进行安装,然后就可以通过rqt这个命令启动使用了。
$ sudo apt install ros-humble-rqt
$ rqt
类似这个界面一样,里边可以加载很多小模块,每个模块都可以实现一个具体的小功能,一些常用的功能如下:
日志显示
图像显示
发布话题数据/调用服务请求
绘制数据曲线
数据包管理
节点可视化
这篇关于古月·ROS2入门21讲——学习笔记(二)常用工具部分15-21讲的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!