1. 概述
-
机器人操作系统学习、开发与测试过程中,会遇到诸多问题,比如:
- 场景1:机器人一般价格不菲,学习 ROS 要购买一台机器人吗?
- 场景2:机器人与之交互的外界环境具有多样性,如何实现复杂的环境设计?
- 场景3:测试时,直接将未经验证的程序部署到实体机器人运行,安全吗?
在诸如此类的场景中,ROS 中的仿真就显得尤为重要
1.1 概念
- 机器人系统仿真,是通过计算机对实体机器人系统进行模拟的技术,在 ROS 中,仿真实现涉及的内容主要有三个:对机器人建模 (URDF)、创建仿真环境 (Gazebo) 以及感知环境 (rviz) 等系统性实现
1.2 作用
-
仿真优势
- 低成本:当前机器人成本居高不下,动辄几十万,仿真可以大大降低成本,减小风险
- 高效:搭建的环境更为多样且灵活,可以提高测试效率以及测试覆盖率
- 高安全性:仿真环境下,无需考虑耗损问题
-
仿真缺陷
- 仿真器所使用的物理引擎目前还不能够完全精确模拟真实世界的物理情况
- 仿真器构建的是关节驱动器(电机 & 齿轮箱)、传感器与信号通信的绝对理想情况,目前不支持模拟实际硬件缺陷或者一些临界状态等情形
1.3 相关组件
-
URDF
- URDF 是 Unified Robot Description Format 的首字母缩写,直译为统一(标准化)机器人描述格式,可以以一种 XML 的方式描述机器人的部分结构,比如底盘、摄像头、激光雷达、机械臂以及不同关节的自由度,该文件可以被 C++ 内置的解释器转换成可视化的机器人模型,是 ROS 中实现机器人仿真的重要组件
-
rviz
- rviz 是 ROS Visualization Tool 的首字母缩写,直译为 ROS 的三维可视化工具。它的主要目的是以三维方式显示 ROS 消息,可以将数据进行可视化表达。例如:可以显示机器人模型,可以无需编程就能表达激光测距仪(LRF)传感器中的传感器到障碍物的距离,RealSense、Kinect 或 Xtion 等三维距离传感器的点云数据(PCD,Point Cloud Data),从相机获取的图像值等
-
Gazebo
- Gazebo 是一款 3D 动态模拟器,用于显示机器人模型并创建仿真环境,能够在复杂的室内和室外环境中准确有效地模拟机器人。与游戏引擎提供高保真度的视觉模拟类似,Gazebo 提供高保真度的物理模拟,其提供一整套传感器模型,以及对用户和程序非常友好的交互方式
1.4 总结
- 机器人的系统仿真是一种集成实现,主要包含三部分:
- URDF 用于创建机器人模型
- Gzebo 用于搭建仿真环境
- rviz 图形化的显示机器人各种传感器感知到的环境信息- 三者应用中,只是创建 URDF 意义不大,一般需要结合 Gazebo 或 rviz 使用,在 Gazebo 或 rviz 中可以将 URDF 文件解析为图形化的机器人模型,一般的使用组合为:
- 如果非仿真环境,那么使用 URDF 结合 rviz 直接显示感知的真实环境信息
- 如果是仿真环境,那么需要使用 URDF 结合 Gazebo 搭建仿真环境,并结合 rviz 显示感知的虚拟环境信息
2. URDF 集成 rviz 基本流程
-
需求描述:在 rviz 中显示一个盒状机器人
-
实现流程
-
创建新的工作空间与功能包,并导入依赖包 urdf 和 xacro
$ mkdir -p demo05_ws/src $ cd demo05_ws $ catkin_make $ code .
-
在功能包的 src 下新建如下几个目录
- urdf:存储 urdf 文件的目录
- meshes:机器人模型渲染文件(暂不使用)
- config:配置文件
- launch:存储 launch 启动文件
-
编写 urdf 文件
- 新建一个子级文件夹:urdf(可选),文件夹中添加 demo01_helloworld.urdf
<robot name = "mycar"> <link name = "base_link"> <visual> <geometry> <box size = "0.5 0.2 0.1" /> </geometry> </visual> </link> </robot>
-
在 launch 文件中集成 urdf 与 rviz
- 在 launch 目录下新建一个 demo01_helloworld.launch 文件,该 launch 文件需要启动 rviz并导入 urdf 文件,rviz 启动后可以自动载入解析 urdf 文件,并显示机器人模型。在 ROS 中,可以将 urdf 文件的路径设置到参数服务器,使用的参数名是:robot_description
<launch> <!-- 设置参数 --> <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/urdf/demo01_helloworld.urdf" /> <!-- 启动 rviz --> <node pkg = "rviz" type = "rviz" name = "rviz" /> </launch>
-
在 rviz 中显示机器人模型
- rviz 启动后,会发现并没有盒装的机器人模型,这是因为默认情况下没有添加机器人显示组件,需要手动添加
$ source ./devel/setup.bash $ roslaunch urdf01_rviz demo01_helloworld.launch ```
-
-
优化 rviz 启动
重复启动 launch 文件时,rviz 之前的组件配置信息不会自动保存,需要重复执行上一步的操作,为方便使用可以使用如下方式优化,再启动时,就可以包含之前的组件配置了,使用更方便快捷
- 首先,将当前配置保存进 config 目录,命名为 show_mycar.rviz
- 然后在 launch 文件中 rviz 的启动配置添加参数:args
<launch> <!-- 设置参数 --> <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/urdf/demo01_helloworld.urdf" /> <!-- 启动 rviz --> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz" /> </launch>
- 首先,将当前配置保存进 config 目录,命名为 show_mycar.rviz
3. URDF 语法详解
- robot 根标签,类似于 launch 文件中的 launch 标签
- link 连杆标签
- joint 关节标签
- gazebo 集成 gazebo 需要使用的标签
3.1 robot
- urdf 中为了保证 xml 语法的完整性,使用了 robot 标签作为根标签,所有的 link 和 joint 以及其他标签都必须包含在 robot 标签内,在该标签内可以通过 name 属性设置机器人模型的名称
3.2 link(连杆)
-
urdf 中的 link 标签用于描述机器人某个部件(也即刚体部分)的外观和物理属性,比如:机器人底座、轮子、激光雷达、摄像头。每一个部件都对应一个 link, 在 link 标签内,可以设计该部件的形状、尺寸、颜色、惯性矩阵、碰撞参数等一系列属性
-
属性
- name:为连杆命名
-
子标签
-
visual:描述外观(对应的数据是可视的)
-
geometry 设置连杆的形状
标签1:box(盒状) 属性:size = 长(x) 宽(y) 高(z) 标签2:cylinder(圆柱) 属性:radius = 半径 length = 高度 标签3:sphere(球体) 属性:radius = 半径 标签4:mesh(为连杆添加皮肤) 属性:filename = 资源路径(格式:package://<packagename>/<path>/文件)
-
origin:设置偏移量与倾斜弧度
属性1:xyz = x偏移 y便宜 z偏移 属性2:rpy = x翻滚 y俯仰 z偏航 (单位是弧度)
-
metrial:设置材料属性(颜色)
属性:name 标签:color 属性: rgba=红绿蓝权重值与透明度 (每个权重值以及透明度取值[0,1])
-
-
collision:连杆的碰撞属性
-
Inertial:连杆的惯性矩阵
-
-
示例:分别生成长方体、圆柱与球体的机器人部件
-
demo02_link.urdf
<robot name = "mycar"> <link name = "base_link"> <visual> <geometry> <!-- <box size = "0.3 0.2 0.1" /> --> <!-- <cylinder radius = "0.5" length = "0.1" /> --> <!-- <sphere radius = "0.3" /> --> <mesh filename = "package://urdf01_rviz/meshes/autolabor_mini.stl" /> </geometry> <!-- xyz 坐标 rpy 翻滚俯仰与偏航角度(3.14 = 180度 1.57 = 90度) --> <origin xyz = "0 0 0" rpy = "1.57 0 1.57" /> <!-- 颜色: r = red g = green b = blue a = alpha --> <material name = "car_color"> <color rgba = "0 1 0 0.5" /> </material> </visual> </link> </robot>
-
demo02_link.launch
<launch> <!-- 设置参数 --> <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/urdf/demo02_link.urdf" /> <!-- 启动 rviz --> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz" /> </launch>
-
3.3 joint(关节)
- urdf 中的 joint 标签用于描述机器人关节的运动学和动力学属性,还可以指定关节运动的安全极限,机器人的两个部件 (分别称之为 parent link 与 child link) 以"关节"的形式相连接,不同的关节有不同的运动形式:旋转、滑动、固定、旋转速度、旋转角度限制等。比如:安装在底座上的轮子可以 360 度旋转,而摄像头则可能是完全固定在底座上
joint 标签对应的数据在模型中是不可见的
-
属性
-
name:为关节命名
-
type:关节运动形式
- continuous:旋转关节,可以绕单轴无限旋转
- revolute:旋转关节,类似于 continues,但是有旋转角度限制
- prismatic:滑动关节,沿某一轴线移动的关节,有位置极限
- planer:平面关节,允许在平面正交方向上平移或旋转
- floating:浮动关节,允许进行平移、旋转运动
- fixed:固定关节,不允许运动的特殊关节
-
-
子标签
-
parent (必需的)
parent link 的名字是一个强制的属性:- link:父级连杆的名字,是这个 link 在机器人结构树中的名字
-
child (必需的)
child link的名字是一个强制的属性:- link:子级连杆的名字,是这个 link 在机器人结构树中的名字
-
origin
- 属性: xyz = 各轴线上的偏移量 rpy = 各轴线上的偏移弧度
-
axis
- 属性:xyz 用于设置围绕哪个关节轴运动
-
-
示例:创建机器人模型,底盘为长方体,在长方体的前面添加一摄像头,摄像头可以沿着 z 轴 360 度旋转
-
demo03_joint.urdf
<robot name = "mycar"> <!-- 底盘 --> <link name = "base_link"> <visual> <geometry> <box size = "0.3 0.2 0.1" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "car_color"> <color rgb = "0.8 0.5 0 0.5" /> </material> </visual> </link> <!-- 摄像头 --> <link name = "camera"> <visual> <geometry> <box size = "0.02 0.05 0.05" /> </geometry> <origin xyz = "0 0 0.025" rpy = "0 0 0" /> <material name = "camera_color"> <color rgba = "0 0 1 0.5" /> </material> </visual> </link> <!-- 关节 --> <joint name = "camera2base" type = "continuous"> <parent link = "base_link"/> <child link = "camera" /> <!-- 需要计算两个 link 的物理中心之间的偏移量 --> <origin xyz = "0.12 0 0.05" rpy = "0 0 0" /> <axis xyz = "0 0 1" /> </joint> </robot>
-
demo03_joint.launch
<launch> <!-- 设置参数 --> <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/urdf/demo03_joint.urdf" /> <!-- 启动 rviz --> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz" /> <!-- 关节状态发布节点 --> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher" /> <!-- 机器人状态发布节点 --> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher" /> <!-- 可选:用于控制关节运动的节点,会生成关节控制的UI,用于测试关节运动是否正常 --> <node pkg = "joint_state_publisher_gui" type = "joint_state_publisher_gui" name = "joint_state_publisher_gui" /> </launch>
-
-
base_footprint 优化 urdf
前面实现的机器人模型是半沉到地下的,因为默认情况下:底盘的中心点位于地图原点上,所以会导致这种情况产生,可以使用的优化策略,将初始 link 设置为一个尺寸极小的 link (比如半径为 0.001m 的球体,或边长为 0.001m 的立方体),然后再在初始 link 上添加底盘等刚体,这样实现,虽然仍然存在初始 link 半沉的现象,但是基本可以忽略了,这个初始 link 称为 base_footprint(启动 rviz 后记得将 fixed frame 改为 base_footprint)
-
demo04_base_footprint.urdf
<robot name = "mycar"> <!-- 添加一个尺寸极小的 link ,再关联初始 link 和 base_link,关节的高度刚好和 base_link 下沉的高度一致 --> <link name = "base_footprint"> <visual> <geometry> <box size = "0.001 0.001 0.001" /> </geometry> </visual> </link> <!-- 底盘 --> <link name = "base_link"> <visual> <geometry> <box size = "0.3 0.2 0.1" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "car_color"> <color rgb = "0.8 0.5 0 0.5" /> </material> </visual> </link> <!-- 摄像头 --> <link name = "camera"> <visual> <geometry> <box size = "0.02 0.05 0.05" /> </geometry> <origin xyz = "0 0 0.025" rpy = "0 0 0" /> <material name = "camera_color"> <color rgba = "0 0 1 0.5" /> </material> </visual> </link> <!-- 关联 base_footprint 和 base_link --> <joint name = "link2footprint" type = "fixed"> <parent link = "base_footprint"/> <child link = "base_link" /> <!-- 设置偏移量,z 方向为半个 base_link 的高度 --> <origin xyz = "0 0 0.05" rpy = "0 0 0" /> </joint> <!-- 关节 --> <joint name = "camera2base" type = "continuous"> <parent link = "base_link"/> <child link = "camera" /> <!-- 需要计算两个 link 的物理中心之间的偏移量 --> <origin xyz = "0.12 0 0.05" rpy = "0 0 0" /> <!-- 设置关节旋转参考的坐标轴 --> <axis xyz = "0 0 1" /> </joint> </robot>
-
demo04_base_footprint.launch
<launch> <!-- 设置参数 --> <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/urdf/demo04_base_footprint.urdf" /> <!-- 启动 rviz --> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz" /> <!-- 关节状态发布节点 --> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher" /> <!-- 机器人状态发布节点 --> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher" /> </launch>
-
-
问题1:命令行输出如下错误提示
UnicodeEncodeError: 'ascii' codec can't encode characters in position 463-464: ordinal not in range(128) [joint_state_publisher-3] process has died [pid 4443, exit code 1, cmd /opt/ros/melodic/lib/joint_state_publisher/joint_state_publisher __name:=joint_state_publisher __log:=/home/rosmelodic/.ros/log/b38967c0-0acb-11eb-aee3-0800278ee10c/joint_state_publisher-3.log]. log file: /home/rosmelodic/.ros/log/b38967c0-0acb-11eb-aee3-0800278ee10c/joint_state_publisher-3*.log
-
rviz中提示坐标变换异常,导致机器人部件显示结构异常,原因是编码问题导致的
-
解决方案:去除 URDF 中的中文注释
-
-
问题2:[ERROR] [1584370263.037038]: Could not find the GUI, install the ‘joint_state_publisher_gui’ package
- 解决方案
$ sudo apt install ros-melodic-joint-state-publisher-gui
- 解决方案
3.4 URDF 练习
-
需求
创建一个四轮圆柱状机器人模型,机器人参数如下:底盘为圆柱状,半径 10cm,高 8cm,四轮由两个驱动轮和两个万向支撑轮组成,两个驱动轮半径为 3.25cm,轮胎宽度1.5cm,两个万向轮为球状,半径 0.75cm,底盘离地间距为 1.5cm(与万向轮直径一致)
-
demo05_test.urdf
<robot name = "mycar"> <!-- 设置 base_footprint --> <link name = "base_footprint"> <visual> <geometry> <sphere radius = "0.001" /> </geometry> </visual> </link> <!-- 添加底盘 --> <link name = "base_link"> <visual> <geometry> <cylinder radius = "0.1" length = "0.08" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "baselink_color"> <color rgba = "1.0 0.5 0.2 0.5" /> </material> </visual> </link> <joint name = "link2footprint" type = "fixed"> <parent link = "base_footprint" /> <child link = "base_link"/> <!-- 关节 z 上设置 = 车体高度(0.08)/2 + 离地间隙(0.015)--> <origin xyz = "0 0 0.055" rpy = "0 0 0" /> </joint> <!-- 添加驱动轮 --> <link name = "left_wheel"> <visual> <geometry> <cylinder radius = "0.0325" length = "0.015" /> </geometry> <origin xyz = "0 0 0" rpy = "1.5708 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> </link> <link name = "right_wheel"> <visual> <geometry> <cylinder radius = "0.0325" length = "0.015" /> </geometry> <origin xyz = "0 0 0" rpy = "1.5708 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> </link> <joint name = "left2link" type = "continuous"> <parent link = "base_link" /> <child link = "left_wheel" /> <!-- z = 车体高度 / 2 + 离地间隙 - 车轮半径 --> <origin xyz = "0 0.1 -0.0225" rpy = "0 0 0" /> <axis xyz = "0 1 0" /> </joint> <joint name = "right2link" type = "continuous"> <parent link = "base_link" /> <child link = "right_wheel" /> <origin xyz = "0 -0.1 -0.0225" rpy = "0 0 0" /> <axis xyz = "0 1 0" /> </joint> <!-- 添加万向轮(支撑轮) --> <link name = "front_wheel"> <visual> <geometry> <sphere radius = "0.0075" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> </link> <link name = "back_wheel"> <visual> <geometry> <sphere radius = "0.0075" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> </link> <joint name = "front2link" type = "continuous"> <parent link = "base_link" /> <child link = "front_wheel" /> <origin xyz = "0.08 0 -0.0475" rpy = "0 0 0" /> <axis xyz = "1 1 1" /> </joint> <joint name = "back2link" type = "continuous"> <parent link = "base_link" /> <child link = "back_wheel" /> <origin xyz = "-0.08 0 -0.0475" rpy = "0 0 0" /> <axis xyz = "1 1 1" /> </joint> </robot>
-
demo05_test.launch
<launch> <!-- 在参数服务器中载入 urdf --> <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/urdf/demo05_test.urdf" /> <!-- 启动 rviz --> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz" /> <!-- 关节状态发布节点 --> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher" /> <!-- 机器人状态发布节点 --> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher" /> <!-- 可选:用于控制关节运动的节点,会生成关节控制的UI,用于测试关节运动是否正常 --> <node pkg = "joint_state_publisher_gui" type = "joint_state_publisher_gui" name = "joint_state_publisher_gui" /> </launch>
3.5 URDF 工具
-
在 ROS 中,提供了一些工具来方便 URDF 文件的编写,比如:
-
check_urdf 命令可以检查复杂的 urdf 文件是否存在语法问题
-
urdf_to_graphiz 命令可以查看 urdf 模型结构,显示不同 link 的层级关系
-
当然,要使用工具之前,首先需要安装,安装命令
$ sudo apt install liburdfdom-tools
-
-
check_urdf 语法检查
- 进入 urdf 文件所属目录,调用:check_urdf demo05_test.urdf 文件,如果不抛出异常,说明文件合法,否则非法
-
urdf_to_graphiz 结构查看
- 进入 urdf 文件所属目录,调用:urdf_to_graphiz demo05_test.urdf 文件,当前目录下会生成 pdf 文件
$ evince mycar.pdf
- 进入 urdf 文件所属目录,调用:urdf_to_graphiz demo05_test.urdf 文件,当前目录下会生成 pdf 文件
4. URDF 优化:Xacro
-
前面 URDF 文件构建机器人模型的过程中,存在若干问题
-
问题1:在设计关节的位置时,需要按照一定的公式计算,公式是固定的,但是在 URDF 中依赖于人工计算,存在不便且容易计算失误,且当某些参数发生改变时,还需要重新计算
-
问题2:URDF 中的部分内容是高度重复的,驱动轮与支撑轮的设计实现,不同轮子只是部分参数不同,形状、颜色、翻转量都是一致的,在实际应用中,构建复杂的机器人模型时,更是易于出现高度重复的设计,按照一般的编程涉及到重复代码应该考虑封装
如果在编程语言中,可以通过变量结合函数直接解决上述问题,在 ROS 中,已经给出了类似编程的优化方案,称之为:Xacro
-
-
概念
- Xacro 是 XML Macros 的缩写,Xacro 是一种 XML 宏语言,是可编程的 XML
-
原理
- Xacro 可以声明变量,可以通过数学运算求解,使用流程控制执行顺序,还可以通过类似函数的实现,封装固定的逻辑,将逻辑中需要的可变的数据以参数的方式暴露出去,从而提高代码复用率以及程序的安全性
-
作用
- 较之于纯粹的 URDF 实现,可以编写更安全、精简、易读性更强的机器人模型文件,且可以提高编写效率
4.1 Xacro 语法详解
- xacro 提供了可编程接口,类似于计算机语言,包括变量声明调用、函数声明与调用等语法实现。在使用 xacro 生成 urdf 时,根标签robot中必须包含命名空间声明
xmlns:xacro = "http://wiki.ros.org/xacro"
4.1.1 属性(含算数运算)
- 用于封装 URDF 中的一些字段,比如:PAI 值,小车的尺寸,轮子半径
- 属性定义、调用和算术运算
<robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <!-- 属性定义 --> <xacro:property name = "PI" value = "3.1415926" /> <xacro:property name = "radius" value = "0.03" /> <!-- 属性调用 --> <myUsePropertyXxx name = "${PI}" /> <myUsePropertyXxx name = "${radius}" /> <!-- 算术运算 --> <myUsePropertyYyy result = "${PI / 2}" /> <myUsePropertyYyy result = "${radius * 2}" /> </robot>
4.1.2 宏
- 类似于函数实现,提高代码复用率,优化代码结构,提高安全性
- 宏定义与宏调用
<robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <!-- 宏定义:<xacro:macro name="宏名称" params="参数列表(多参数之间使用空格分隔)"></xacro:macro> --> <xacro:macro name = "getSum" params = "num1 num2"> <result value = "${num1 + num2}" /> </xacro:macro> <!-- 宏调用:<xacro:宏名称 参数1=xxx 参数2=xxx/> --> <xacro:getSum num1 = "1" num2 = "5" /> </robot>
4.1.3 文件包含
- 机器人由多部件组成,不同部件可能封装为单独的 xacro 文件,最后再将不同的文件集成,组合为完整机器人,可以使用文件包含实现
<robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "my_base.xacro" /> <xacro:include filename = "my_camera.xacro" /> <xacro:include filename = "my_laser.xacro" /> .... </robot>
4.2 Xacro 完整使用流程示例
- 需求:使用 Xacro 优化 URDF 版的小车底盘模型实现
- 实现
-
编写 xacro 文件:demo05_car_base.urdf.xacro
<robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:property name="footprint_radius" value="0.001" /> <link name="base_footprint"> <visual> <geometry> <sphere radius = "${footprint_radius}" /> </geometry> </visual> </link> <xacro:property name="base_radius" value="0.1" /> <xacro:property name="base_length" value="0.08" /> <xacro:property name="lidi" value="0.015" /> <xacro:property name="base_joint_z" value="${base_length / 2 + lidi}" /> <link name = "base_link"> <visual> <geometry> <cylinder radius = "${base_radius}" length = "${base_length}" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "baselink_color"> <color rgba = "1.0 0.5 0.2 0.5" /> </material> </visual> </link> <joint name = "link2footprint" type = "fixed"> <parent link = "base_footprint" /> <child link = "base_link"/> <origin xyz = "0 0 ${base_joint_z}" rpy = "0 0 0" /> </joint> <xacro:property name = "wheel_radius" value = "0.0325" /> <xacro:property name = "wheel_length" value = "0.015" /> <xacro:property name = "PI" value = "3.1415927" /> <xacro:property name = "wheel_joint_z" value = "${(base_length / 2 + lidi - wheel_radius) * -1}" /> <xacro:macro name = "wheel_func" params = "wheel_name flag"> <link name = "${wheel_name}_wheel"> <visual> <geometry> <cylinder radius = "${wheel_radius}" length = "${wheel_length}" /> </geometry> <origin xyz = "0 0 0" rpy = "${PI / 2} 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> </link> <joint name = "${wheel_name}2link" type = "continuous"> <parent link = "base_link" /> <child link = "${wheel_name}_wheel" /> <origin xyz = "0 ${0.1 * flag} ${wheel_joint_z}" rpy = "0 0 0" /> <axis xyz = "0 1 0" /> </joint> </xacro:macro> <xacro:wheel_func wheel_name = "left" flag = "1" /> <xacro:wheel_func wheel_name = "right" flag = "-1" /> <xacro:property name = "small_wheel_radius" value = "0.0075" /> <xacro:property name = "small_joint_z" value = "${(base_length / 2 + lidi - small_wheel_radius) * -1}" /> <xacro:macro name = "small_wheel_func" params = "small_wheel_name flag"> <link name = "${small_wheel_name}_wheel"> <visual> <geometry> <sphere radius = "${small_wheel_radius}" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> </link> <joint name = "${small_wheel_name}2link" type = "continuous"> <parent link = "base_link" /> <child link = "${small_wheel_name}_wheel" /> <origin xyz = "${0.08 * flag} 0 ${small_joint_z}" rpy = "0 0 0" /> <axis xyz = "1 1 1" /> </joint> </xacro:macro> <xacro:small_wheel_func small_wheel_name = "front" flag = "1" /> <xacro:small_wheel_func small_wheel_name = "back" flag = "-1" /> </robot>
-
集成 launch 文件
<launch> <!-- <param name = "robot_description" textfile = "$(find urdf01_rviz)/urdf/xacro/demo05_car_base.urdf" /> --> <param name = "robot_description" command = "$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/demo05_car_base.urdf.xacro"/> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz"/> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher"/> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher"/> <node pkg = "joint_state_publisher_gui" type = "joint_state_publisher_gui" name = "joint_state_publisher_gui"/> </launch>
-
4.3 Xacro 实操
- 需求:在前面小车底盘基础之上,添加摄像头和雷达传感器
- 实现
-
摄像头和雷达 Xacro 文件实现:demo06_car_camera.urdf.xacro & demo07_car_laser.urdf.xacro
<!-- demo06_car_camera.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:property name = "camera_length" value = "0.02" /> <xacro:property name = "camera_width" value = "0.05" /> <xacro:property name = "camera_height" value = "0.05" /> <xacro:property name = "joint_camera_x" value = "0.08" /> <xacro:property name = "joint_camera_y" value = "0" /> <xacro:property name = "joint_camera_z" value = "${base_length / 2 + camera_height / 2}" /> <link name = "camera"> <visual> <geometry> <box size = "${camera_length} ${camera_width} ${camera_height}" /> </geometry> <material name = "black"> <color rgba = "0 0 0 0.8" /> </material> </visual> </link> <joint name = "camera2base" type = "fixed"> <parent link = "base_link" /> <child link = "camera" /> <origin xyz = "${joint_camera_x} ${joint_camera_y} ${joint_camera_z}" rpy = "0 0 0" /> </joint> </robot>
<!-- demo07_car_laser.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:property name = "support_radius" value = "0.01" /> <xacro:property name = "support_length" value = "0.15" /> <xacro:property name = "laser_radius" value = "0.03" /> <xacro:property name = "laser_length" value = "0.05" /> <xacro:property name = "joint_support_x" value = "0" /> <xacro:property name = "joint_support_y" value = "0" /> <xacro:property name = "joint_support_z" value = "${base_length / 2 + support_length / 2}" /> <xacro:property name = "joint_laser_x" value = "0" /> <xacro:property name = "joint_laser_y" value = "0" /> <xacro:property name = "joint_laser_z" value = "${support_length / 2 + laser_length / 2}" /> <link name = "support"> <visual> <geometry> <cylinder radius = "${support_radius}" length = "${support_length}" /> </geometry> <material name = "yellow"> <color rgba = "0.8 0.5 0.0 0.5" /> </material> </visual> </link> <joint name = "support2base" type = "fixed"> <parent link = "base_link" /> <child link = "support" /> <origin xyz = "${joint_support_x} ${joint_support_y} ${joint_support_z}" rpy = "0 0 0" /> </joint> <link name = "laser"> <visual> <geometry> <cylinder radius = "${laser_radius}" length = "${laser_length}" /> </geometry> <material name = "black"> <color rgba = "0 0 0 0.5" /> </material> </visual> </link> <joint name = "laser2support" type = "fixed"> <parent link = "support" /> <child link = "laser" /> <origin xyz = "${joint_laser_x} ${joint_laser_y} ${joint_laser_z}" rpy = "0 0 0" /> </joint> </robot>
-
组合底盘摄像头与雷达的 xacro 文件:car.urdf.xacro
<robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "demo05_car_base.urdf.xacro" /> <xacro:include filename = "demo06_car_camera.urdf.xacro" /> <xacro:include filename = "demo07_car_laser.urdf.xacro" /> </robot>
-
launch 文件:car.launch
<launch> <param name = "robot_description" command = "$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/car.urdf.xacro"/> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz"/> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher"/> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher"/> <node pkg = "joint_state_publisher_gui" type = "joint_state_publisher_gui" name = "joint_state_publisher_gui"/> </launch>
-
5. rviz 中控制机器人模型运动
-
Arbotix:Arbotix 是一款控制电机、舵机的控制板,并提供相应的 ros 功能包,这个功能包的功能不仅可以驱动真实的 Arbotix 控制板,它还提供一个差速控制器,通过接受速度控制指令更新机器人的 joint 状态,从而帮助我们实现机器人在 rviz 中的运动。
-
这个差速控制器在 arbotix_python 程序包中,完整的 arbotix 程序包还包括多种控制器,分别对应 dynamixel 电机、多关节机械臂以及不同形状的夹持器
5.1 Arbotix 使用流程
- 需求:控制机器人模型在 rviz 中做圆周运动
- 使用流程
-
安装 Arbotix
$ sudo apt install ros-melodic-arbotix
-
创建新功能包,准备机器人 urdf、xacro
- urdf 和 xacro 调用上一讲实现即可
-
添加 arbotix 所需的配置文件
# 该文件是控制器配置,一个机器人模型可能有多个控制器,比如: 底盘、机械臂、夹持器(机械手) # 因此,根 name 是 controller controllers: { # 单控制器设置 base_controller: { # 类型: 差速控制器 type: diff_controller, # 参考坐标 base_frame_id: base_footprint, # 两个轮子之间的间距 base_width: 0.2, # 控制频率 Hz ticks_meter: 2000, # PID 控制参数,使机器人车轮快速达到预期速度 Kp: 12, Kd: 12, Ki: 0, Ko: 50, # 加速限制 m/s accel_limit: 1.0 } }
-
launch 文件中配置 arbotix 节点
<!-- demo07_control.launch --> <launch> <param name = "robot_description" command = "$(find xacro)/xacro $(find urdf01_rviz)/urdf/xacro/car.urdf.xacro"/> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz"/> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher"/> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher"/> <node pkg = "arbotix_python" type = "arbotix_driver" name = "driver" output = "screen"> <rosparam command = "load" file = "$(find urdf01_rviz)/config/control.yaml" /> <param name = "sim" value = "true" /> </node> </launch> <!-- <node> 调用了 arbotix_python 功能包下的 arbotix_driver 节点 <rosparam> arbotix 驱动机器人运行时,需要获取机器人信息,可以通过 file 加载配置文件 <param> 在仿真环境下,需要配置 sim 为 true -->
-
启动 launch 文件并控制机器人模型运动
$ cd demo05_ws $ source ./devel/setup.bash $ roslaunch urdf01_rviz demo07_control.launch
-
配置 rviz
-
控制小车运动
-
此时调用 rostopic list 会发现一个熟悉的话题: /cmd_vel
-
也就说我们可以发布 cmd_vel 话题消息控制小车运动了,该实现策略有多种,可以另行编写节点,或者更简单些可以直接通过如下命令发布消息
$ rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '{linear: {x: 1, y: 0, z: 0}, angular: {x: 0, y: 0, z: 1}}'
-
-
6. URDF 集成 Gazebo
6.1 URDF 与 Gazebo 基本集成流程
-
创建功能包
- 创建新功能包 urdf02_gazebo,导入依赖包:urdf、xacro、gazebo_ros、gazebo_ros_control、gazebo_plugins
-
在功能包下新建 urdf 和 launch 文件夹并分别建立下列文件
<!-- demo01_helloworld.urdf --> <robot name = "mycar"> <link name = "base_link"> <visual> <geometry> <box size = "0.5 0.3 0.1" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "yellow"> <color rgba = "0.5 0.3 0 0.5" /> </material> </visual> <collision> <geometry> <box size = "0.5 0.3 0.1" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> </collision> <inertial> <origin xyz = "0 0 0" /> <mass value = "2" /> <inertia ixx = "1" ixy = "0" ixz = "0" iyy = "0" iyz = "1" izz = "1" /> </inertial> </link> <gazebo reference = "base_link"> <material>Gazebo/Red</material> </gazebo> </robot>
<!-- demo01_helloworld.launch --> <launch> <param name = "robot_description" textfile = "$(find urdf02_gazebo)/urdf/demo01_helloworld.urdf"/> <!-- 启动 Gazebo 的仿真环境,当前环境为空环境 --> <include file = "$(find gazebo_ros)/launch/empty_world.launch" /> <!-- 在 gazebo 中显示机器人模型 --> <node pkg = "gazebo_ros" type = "spawn_model" name = "spawn_model" args = "-urdf -model car -param robot_description" /> <!-- 在 Gazebo 中加载一个机器人模型,该功能由 gazebo_ros 下的 spawn_model 提供: -urdf 加载的是 urdf 文件 -model car 模型名称是 car -param robot_description 从参数 robot_description 中载入模型 -x 模型载入的 x 坐标 -y 模型载入的 y 坐标 -z 模型载入的 z 坐标 --> </launch>
当 URDF 需要与 Gazebo 集成时,和 rviz 有明显区别:
- 必须使用 collision 标签,因为既然是仿真环境,那么必然涉及到碰撞检测,collision 提供碰撞检测的依据
- 必须使用 inertial 标签,此标签标注了当前机器人某个刚体部分的惯性矩阵,用于一些力学相关的仿真计算
- 颜色设置,也需要重新使用 gazebo 标签标注,因为之前的颜色设置为了方便调试包含透明度,仿真环境下没有此选项
- 启动 Gazebo 并显示模型
$ cd demo05_ws $ roslaunch urdf02_gazebo demo01_helloworld.launch
6.2 URDF 集成 Gazebo 相关设置
-
collision
- 如果机器人 link 是标准的几何体形状,和 link 的 visual 属性设置一致即可
-
inertial
-
惯性矩阵的设置需要结合 link 的质量与外形参数动态生成,标准的球体、圆柱与立方体的惯性矩阵公式如下(已经封装为 xacro 实现)
-
球体惯性矩阵
<xacro:macro name = "sphere_inertial_matrix" params = "m r"> <inertial> <mass value = "${m}" /> <inertia ixx = "${2*m*r*r/5}" ixy = "0" ixz = "0" iyy = "${2*m*r*r/5}" iyz = "0" izz = "${2*m*r*r/5}" /> </inertial> </xacro:macro>
-
圆柱惯性矩阵
<xacro:macro name = "cylinder_inertial_matrix" params = "m r h"> <inertial> <mass value = "${m}" /> <inertia ixx = "${m*(3*r*r+h*h)/12}" ixy = "0" ixz = "0" iyy = "${m*(3*r*r+h*h)/12}" iyz = "0" izz = "${m*r*r/2}" /> </inertial> </xacro:macro>
-
立方体惯性矩阵
<xacro:macro name = "Box_inertial_matrix" params = "m l w h"> <inertial> <mass value = "${m}" /> <inertia ixx = "${m*(h*h + l*l)/12}" ixy = "0" ixz = "0" iyy = "${m*(w*w + l*l)/12}" iyz = "0" izz = "${m*(w*w + h*h)/12}" /> </inertial> </xacro:macro>
需要注意的是,原则上,除了 base_footprint 外,机器人的每个刚体部分都需要设置惯性矩阵,且惯性矩阵必须经计算得出,如果随意定义刚体部分的惯性矩阵,那么可能会导致机器人在 Gazebo 中出现抖动,移动等现象
-
-
颜色设置
- 在 gazebo 中显示 link 的颜色,必须要使用指定的标签
<gazebo reference = "link节点名称"> <material>Gazebo/Blue</material> </gazebo>
material 标签中,设置的值区分大小写,颜色可以设置为 Red Blue Green Black
- 在 gazebo 中显示 link 的颜色,必须要使用指定的标签
6.3 URDF 集成 Gazebo 实操
-
需求:将之前的机器人模型(xacro版)显示在 gazebo 中
-
实现流程
- 编写封装惯性矩阵算法的 head.xacro 文件
<robot name="base" xmlns:xacro="http://wiki.ros.org/xacro"> <!-- Macro for inertia matrix --> <xacro:macro name="sphere_inertial_matrix" params="m r"> <inertial> <mass value="${m}" /> <inertia ixx="${2*m*r*r/5}" ixy="0" ixz="0" iyy="${2*m*r*r/5}" iyz="0" izz="${2*m*r*r/5}" /> </inertial> </xacro:macro> <xacro:macro name="cylinder_inertial_matrix" params="m r h"> <inertial> <mass value="${m}" /> <inertia ixx="${m*(3*r*r+h*h)/12}" ixy = "0" ixz = "0" iyy="${m*(3*r*r+h*h)/12}" iyz = "0" izz="${m*r*r/2}" /> </inertial> </xacro:macro> <xacro:macro name="Box_inertial_matrix" params="m l w h"> <inertial> <mass value="${m}" /> <inertia ixx="${m*(w*w + h*h)/12}" ixy = "0" ixz = "0" iyy="${m*(h*h + l*l)/12}" iyz= "0" izz="${m*(w*w + l*l)/12}" /> </inertial> </xacro:macro> </robot>
- 复制相关 xacro 文件,并设置 collision inertial 以及 color 等参数
<!-- demo05_car_base.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:property name="footprint_radius" value="0.001" /> <link name="base_footprint"> <visual> <geometry> <sphere radius = "${footprint_radius}" /> </geometry> </visual> </link> <xacro:property name="base_radius" value="0.1" /> <xacro:property name="base_length" value="0.08" /> <xacro:property name="base_mass" value="2" /> <xacro:property name="lidi" value="0.015" /> <xacro:property name="base_joint_z" value="${base_length / 2 + lidi}" /> <link name = "base_link"> <visual> <geometry> <cylinder radius = "${base_radius}" length = "${base_length}" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "baselink_color"> <color rgba = "1.0 0.5 0.2 0.5" /> </material> </visual> <collision> <geometry> <cylinder radius = "${base_radius}" length = "${base_length}" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> </collision> <xacro:cylinder_inertial_matrix m = "${base_mass}" r = "${base_radius}" h = "${base_length}" /> </link> <gazebo reference = "base_link"> <material>Gazebo/Yellow</material> </gazebo> <joint name = "link2footprint" type = "fixed"> <parent link = "base_footprint" /> <child link = "base_link"/> <origin xyz = "0 0 ${base_joint_z}" rpy = "0 0 0" /> </joint> <xacro:property name = "wheel_radius" value = "0.0325" /> <xacro:property name = "wheel_length" value = "0.015" /> <xacro:property name = "wheel_mass" value = "0.05" /> <xacro:property name = "PI" value = "3.1415927" /> <xacro:property name = "wheel_joint_z" value = "${-base_length / 2 - lidi + wheel_radius}" /> <xacro:macro name = "wheel_func" params = "wheel_name flag"> <link name = "${wheel_name}_wheel"> <visual> <geometry> <cylinder radius = "${wheel_radius}" length = "${wheel_length}" /> </geometry> <origin xyz = "0 0 0" rpy = "${PI / 2} 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> <collision> <geometry> <cylinder radius = "${wheel_radius}" length = "${wheel_length}" /> </geometry> <origin xyz = "0 0 0" rpy = "${PI / 2} 0 0" /> </collision> <xacro:cylinder_inertial_matrix m = "${wheel_mass}" r = "${wheel_radius}" h = "${wheel_length}" /> </link> <gazebo reference = "${wheel_name}_wheel"> <material>Gazebo/Red</material> </gazebo> <joint name = "${wheel_name}2link" type = "continuous"> <parent link = "base_link" /> <child link = "${wheel_name}_wheel" /> <origin xyz = "0 ${0.1 * flag} ${wheel_joint_z}" rpy = "0 0 0" /> <axis xyz = "0 1 0" /> </joint> </xacro:macro> <xacro:wheel_func wheel_name = "left" flag = "1" /> <xacro:wheel_func wheel_name = "right" flag = "-1" /> <xacro:property name = "small_wheel_radius" value = "0.0075" /> <xacro:property name = "small_wheel_mass" value = "0.01" /> <xacro:property name = "small_joint_z" value = "${(base_length / 2 + lidi - small_wheel_radius) * -1}" /> <xacro:macro name = "small_wheel_func" params = "small_wheel_name flag"> <link name = "${small_wheel_name}_wheel"> <visual> <geometry> <sphere radius = "${small_wheel_radius}" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> <material name = "wheel_color"> <color rgba = "0.0 0.0 0.0 0.3" /> </material> </visual> <collision> <geometry> <sphere radius = "${small_wheel_radius}" /> </geometry> <origin xyz = "0 0 0" rpy = "0 0 0" /> </collision> <xacro:sphere_inertial_matrix m = "${small_wheel_mass}" r = "${small_wheel_radius}" /> </link> <gazebo reference = "${small_wheel_name}_wheel"> <material>Gazebo/Red</material> </gazebo> <joint name = "${small_wheel_name}2link" type = "continuous"> <parent link = "base_link" /> <child link = "${small_wheel_name}_wheel" /> <origin xyz = "${0.08 * flag} 0 ${small_joint_z}" rpy = "0 0 0" /> <axis xyz = "1 1 1" /> </joint> </xacro:macro> <xacro:small_wheel_func small_wheel_name = "front" flag = "1" /> <xacro:small_wheel_func small_wheel_name = "back" flag = "-1" /> </robot>
<!-- demo06_car_camera.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:property name = "camera_length" value = "0.02" /> <xacro:property name = "camera_width" value = "0.05" /> <xacro:property name = "camera_height" value = "0.05" /> <xacro:property name = "camera_mass" value = "0.01" /> <xacro:property name = "joint_camera_x" value = "0.08" /> <xacro:property name = "joint_camera_y" value = "0" /> <xacro:property name = "joint_camera_z" value = "${base_length / 2 + camera_height / 2}" /> <link name = "camera"> <visual> <geometry> <box size = "${camera_length} ${camera_width} ${camera_height}" /> </geometry> <material name = "black"> <color rgba = "0 0 0 0.8" /> </material> </visual> <collision> <geometry> <box size = "${camera_length} ${camera_width} ${camera_height}" /> </geometry> </collision> <xacro:Box_inertial_matrix m = "${camera_mass}" l = "${camera_length}" w = "${camera_width}" h = "${camera_height}" /> </link> <gazebo reference = "camera"> <material>Gazebo/Blue</material> </gazebo> <joint name = "camera2base" type = "fixed"> <parent link = "base_link" /> <child link = "camera" /> <origin xyz = "${joint_camera_x} ${joint_camera_y} ${joint_camera_z}" rpy = "0 0 0" /> </joint> </robot>
<!-- demo07_car_laser.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:property name = "support_radius" value = "0.01" /> <xacro:property name = "support_length" value = "0.15" /> <xacro:property name = "support_mass" value = "0.1" /> <xacro:property name = "laser_radius" value = "0.03" /> <xacro:property name = "laser_length" value = "0.05" /> <xacro:property name = "laser_mass" value = "0.15" /> <xacro:property name = "joint_support_x" value = "0" /> <xacro:property name = "joint_support_y" value = "0" /> <xacro:property name = "joint_support_z" value = "${base_length / 2 + support_length / 2}" /> <xacro:property name = "joint_laser_x" value = "0" /> <xacro:property name = "joint_laser_y" value = "0" /> <xacro:property name = "joint_laser_z" value = "${support_length / 2 + laser_length / 2}" /> <link name = "support"> <visual> <geometry> <cylinder radius = "${support_radius}" length = "${support_length}" /> </geometry> <material name = "yellow"> <color rgba = "0.8 0.5 0.0 0.5" /> </material> </visual> <collision> <geometry> <cylinder radius = "${support_radius}" length = "${support_length}" /> </geometry> </collision> <xacro:cylinder_inertial_matrix m = "${support_mass}" r = "${support_radius}" h = "${support_length}" /> </link> <gazebo reference = "support"> <material>Gazebo/Gray</material> </gazebo> <joint name = "support2base" type = "fixed"> <parent link = "base_link" /> <child link = "support" /> <origin xyz = "${joint_support_x} ${joint_support_y} ${joint_support_z}" rpy = "0 0 0" /> </joint> <link name = "laser"> <visual> <geometry> <cylinder radius = "${laser_radius}" length = "${laser_length}" /> </geometry> <material name = "black"> <color rgba = "0 0 0 0.5" /> </material> </visual> <collision> <geometry> <cylinder radius = "${laser_radius}" length = "${laser_length}" /> </geometry> </collision> <xacro:cylinder_inertial_matrix m = "${laser_mass}" r = "${laser_radius}" h = "${laser_length}" /> </link> <gazebo reference = "laser"> <material>Gazebo/Black</material> </gazebo> <joint name = "laser2support" type = "fixed"> <parent link = "support" /> <child link = "laser" /> <origin xyz = "${joint_laser_x} ${joint_laser_y} ${joint_laser_z}" rpy = "0 0 0" /> </joint> </robot>
-
组合底盘、摄像头与雷达的 car.urdf.xacro 文件
<robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "head.xacro" /> <xacro:include filename = "demo05_car_base.urdf.xacro" /> <xacro:include filename = "demo06_car_camera.urdf.xacro" /> <xacro:include filename = "demo07_car_laser.urdf.xacro" /> </robot>
-
在 Gazebo 中执行:demo02_car.launch
<launch> <param name = "robot_description" command = "$(find xacro)/xacro $(find urdf02_gazebo)/urdf/car.urdf.xacro" /> <include file = "$(find gazebo_ros)/launch/empty_world.launch" /> <node pkg = "gazebo_ros" type = "spawn_model" name = "spawn_model" args = "-urdf -model car -param robot_description" /> </launch>
6.4 Gazebo 仿真环境搭建
Gazebo 中创建仿真实现方式有两种(也还可以直接下载使用官方或第三方提高的仿真环境插件)
6.4.1 方式1:直接添加内置组件创建仿真环境
- 启动 Gazebo 并添加组件
$ roscore
$ rosrun gazebo_ros gazebo
- 保存仿真环境
- 添加完毕后,选择 file —> Save World as 选择保存路径(功能包下:worlds 目录),文件名自定义,后缀名设置为 .world(提前在功能包下新建 worlds 文件夹)
- 启动:demo03_env.launch
<launch> <param name = "robot_description" command = "$(find xacro)/xacro $(find urdf02_gazebo)/urdf/car.urdf.xacro" /> <!-- 启动 empty_world 后,再根据 arg 加载自定义的仿真环境 --> <include file = "$(find gazebo_ros)/launch/empty_world.launch"> <arg name = "world_name" value = "$(find urdf02_gazebo)/worlds/box_house.world" /> <!-- 此为教程提供的自定义仿真环境 box_house.world --> </include> <node pkg = "gazebo_ros" type = "spawn_model" name = "spawn_model" args = "-urdf -model car -param robot_description" /> </launch>
6.4.2 方式2:手动绘制仿真环境(更为灵活)
-
启动 gazebo 打开构建面板,绘制仿真环境
-
保存构建的环境
- 点击左上角 file —> Save (保存路径功能包下的: models)
- 然后 file —> Exit Building Editor
-
保存为 world 文件
- 可以像方式 1 一样再添加一些插件,然后保存为 world 文件(保存路径功能包下的:worlds)
-
启动:同方式 1
6.4.3 使用官方提供的插件
-
下载官方模型库
$ git clone https://github.com/osrf/gazebo_models
-
将模型库复制进 gazebo
- 将得到的 gazebo_models 文件夹内容复制到 /usr/share/gazebo-*/models
-
应用
- 重启 Gazebo,选择左侧菜单栏的 insert 可以选择并插入相关道具了
7. URDF、Gazebo 与 rviz 综合应用
关于 URDF(Xacro)、rviz 和 Gazebo 三者的关系,前面已有阐述:URDF 用于创建机器人模型、rviz 可以显示机器人感知到的环境信息,Gazebo 用于仿真,可以模拟外界环境,以及机器人的一些传感器
官方参考实现
7.1 机器人运动控制以及里程计信息显示
gazebo 中已经可以正常显示机器人模型了,那么如何像在 rviz 中一样控制机器人运动呢?在此,需要涉及到 ros 中的组件:ros_control
7.1.1 ros_control 简介
-
场景:同一套 ROS 程序,如何部署在不同的机器人系统上,比如:开发阶段为了提高效率是在仿真平台上测试的,部署时又有不同的实体机器人平台,不同平台的实现是有差异的,如何保证 ROS 程序的可移植性?ROS 内置的解决方式是 ros_control
-
ros_control:是一组软件包,它包含了控制器接口,控制器管理器,传输和硬件接口。ros_control 是一套机器人控制的中间件,是一套规范,不同的机器人平台只要按照这套规范实现,那么就可以保证 与ROS 程序兼容,通过这套规范,实现了一种可插拔的架构设计,大大提高了程序设计的效率与灵活性
gazebo 已经实现了 ros_control 的相关接口,如果需要在 gazebo 中控制机器人运动,直接调用相关接口即可
7.1.2 运动控制实现流程 (Gazebo)
-
为 joint 添加传动装置以及控制器(两轮差速配置)
<!-- 在 urdf 文件夹下新建 gazebo 文件夹 --> <!-- 然后新建 move.xacro 文件,内容如下 --> <robot name="my_car_move" xmlns:xacro="http://wiki.ros.org/xacro"> <xacro:macro name="joint_trans" params="joint_name"> <transmission name="${joint_name}_trans"> <type>transmission_interface/SimpleTransmission</type> <joint name="${joint_name}"> <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface> </joint> <actuator name="${joint_name}_motor"> <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface> <mechanicalReduction>1</mechanicalReduction> </actuator> </transmission> </xacro:macro> <!-- 每一个驱动轮都需要配置传动装置 --> <xacro:joint_trans joint_name="left2link" /> <xacro:joint_trans joint_name="right2link" /> <gazebo> <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so"> <rosDebugLevel>Debug</rosDebugLevel> <publishWheelTF>true</publishWheelTF> <robotNamespace>/</robotNamespace> <publishTf>1</publishTf> <publishWheelJointState>true</publishWheelJointState> <alwaysOn>true</alwaysOn> <updateRate>100.0</updateRate> <legacyMode>true</legacyMode> <leftJoint>left2link</leftJoint> <!-- 左轮 --> <rightJoint>right2link</rightJoint> <!-- 右轮 --> <wheelSeparation>${base_radius * 2}</wheelSeparation> <!-- 车轮间距 --> <wheelDiameter>${wheel_radius * 2}</wheelDiameter> <!-- 车轮直径 --> <broadcastTF>1</broadcastTF> <wheelTorque>30</wheelTorque> <wheelAcceleration>1.8</wheelAcceleration> <commandTopic>cmd_vel</commandTopic> <!-- 运动控制话题 --> <odometryFrame>odom</odometryFrame> <odometryTopic>odom</odometryTopic> <!-- 里程计话题 --> <robotBaseFrame>base_footprint</robotBaseFrame> <!-- 根坐标系 --> </plugin> </gazebo> </robot>
-
xacro 文件集成
<!-- car.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "head.xacro" /> <xacro:include filename = "demo05_car_base.urdf.xacro" /> <xacro:include filename = "demo06_car_camera.urdf.xacro" /> <xacro:include filename = "demo07_car_laser.urdf.xacro" /> <xacro:include filename = "gazebo/move.xacro" /> </robot>
-
启动 Gazebo 并控制机器人运动:demo03_env.launch
<launch> <param name = "robot_description" command = "$(find xacro)/xacro $(find urdf02_gazebo)/urdf/car.urdf.xacro" /> <include file = "$(find gazebo_ros)/launch/empty_world.launch"> <arg name = "world_name" value = "$(find urdf02_gazebo)/worlds/box_house.world" /> </include> <node pkg = "gazebo_ros" type = "spawn_model" name = "spawn_model" args = "-urdf -model car -param robot_description" /> </launch>
启动 launch 文件,使用 topic list 查看话题列表,会发现多了 /cmd_vel 然后发布 vmd_vel 消息控制即可
$ rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '{linear: {x: 0.2, y: 0, z: 0}, angular: {x: 0, y: 0, z: 0.5}}'
或者直接使用下列命令实现键盘操纵
$ rosrun teleop_twist_keyboard teleop_twist_keyboard.py
7.1.3 rviz 查看里程计信息
在 Gazebo 的仿真环境中,机器人的里程计信息以及运动朝向等信息是无法获取的,可以通过 rviz 显示机器人的里程计信息以及运动朝向
- 里程计:机器人相对出发点坐标系的位姿状态 (X 坐标 Y 坐标 Z 坐标以及朝向)
-
启动 rviz:demo04_sensor.launch
<launch> <node pkg = "rviz" type = "rviz" name = "rviz" args = "-d $(find urdf01_rviz)/config/show_mycar.rviz"/> <node pkg = "joint_state_publisher" type = "joint_state_publisher" name = "joint_state_publisher"/> <node pkg = "robot_state_publisher" type = "robot_state_publisher" name = "robot_state_publisher"/> </launch>
-
添加组件
- 执行 launch 文件后,在 rviz 中添加图示组件
- 执行 launch 文件后,在 rviz 中添加图示组件
7.2 雷达信息仿真以及显示
-
新建 laser.xacro 文件,配置雷达传感器信息
<robot name="my_sensors" xmlns:xacro="http://wiki.ros.org/xacro"> <gazebo reference="laser"> <sensor type="ray" name="rplidar"> <pose>0 0 0 0 0 0</pose> <visualize>true</visualize> <update_rate>5.5</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_laser.so"> <topicName>/scan</topicName> <frameName>laser</frameName> </plugin> </sensor> </gazebo> </robot>
-
xacro 文件集成
<!-- car.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "head.xacro" /> <xacro:include filename = "demo05_car_base.urdf.xacro" /> <xacro:include filename = "demo06_car_camera.urdf.xacro" /> <xacro:include filename = "demo07_car_laser.urdf.xacro" /> <xacro:include filename = "gazebo/move.xacro" /> <xacro:include filename = "gazebo/laser.xacro" /> </robot>
-
启动仿真环境
$ source ./devel/setup.bash $ roslaunch urdf02_gazebo demo03_env.launch
-
启动 rviz:demo04_sensor.launch 显示雷达数据
$ roslaunch urdf02_gazebo demo04_sensor.launch
7.3 摄像头信息仿真以及显示
-
新建 camera.xacro 文件,配置摄像头传感器信息
<robot name="my_sensors" xmlns:xacro="http://wiki.ros.org/xacro"> <gazebo reference="camera"> <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"> <alwaysOn>true</alwaysOn> <updateRate>0.0</updateRate> <cameraName>/camera</cameraName> <imageTopicName>image_raw</imageTopicName> <cameraInfoTopicName>camera_info</cameraInfoTopicName> <frameName>camera</frameName> <hackBaseline>0.07</hackBaseline> <distortionK1>0.0</distortionK1> <distortionK2>0.0</distortionK2> <distortionK3>0.0</distortionK3> <distortionT1>0.0</distortionT1> <distortionT2>0.0</distortionT2> </plugin> </sensor> </gazebo> </robot>
-
xacro 文件集成
<!-- car.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "head.xacro" /> <xacro:include filename = "demo05_car_base.urdf.xacro" /> <xacro:include filename = "demo06_car_camera.urdf.xacro" /> <xacro:include filename = "demo07_car_laser.urdf.xacro" /> <xacro:include filename = "gazebo/move.xacro" /> <xacro:include filename = "gazebo/laser.xacro" /> <xacro:include filename = "gazebo/camera.xacro" /> </robot>
-
启动仿真环境
$ source ./devel/setup.bash $ roslaunch urdf02_gazebo demo03_env.launch
-
启动 rviz:demo04_sensor.launch 显示摄像头数据
$ roslaunch urdf02_gazebo demo04_sensor.launch
7.4 kinect 信息仿真以及显示
-
新建 kinect.xacro 文件,配置 kinetic 传感器信息
<robot name="my_sensors" xmlns:xacro="http://wiki.ros.org/xacro"> <gazebo reference="support"> <sensor type="depth" name="camera"> <always_on>true</always_on> <update_rate>20.0</update_rate> <camera> <horizontal_fov>${60.0*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="kinect_camera_controller" filename="libgazebo_ros_openni_kinect.so"> <cameraName>camera</cameraName> <alwaysOn>true</alwaysOn> <updateRate>10</updateRate> <imageTopicName>rgb/image_raw</imageTopicName> <depthImageTopicName>depth/image_raw</depthImageTopicName> <pointCloudTopicName>depth/points</pointCloudTopicName> <cameraInfoTopicName>rgb/camera_info</cameraInfoTopicName> <depthImageCameraInfoTopicName>depth/camera_info</depthImageCameraInfoTopicName> <frameName>support</frameName> <baseline>0.1</baseline> <distortion_k1>0.0</distortion_k1> <distortion_k2>0.0</distortion_k2> <distortion_k3>0.0</distortion_k3> <distortion_t1>0.0</distortion_t1> <distortion_t2>0.0</distortion_t2> <pointCloudCutoff>0.4</pointCloudCutoff> </plugin> </sensor> </gazebo> </robot>
-
xacro 文件集成
<!-- car.urdf.xacro --> <robot name = "mycar" xmlns:xacro = "http://wiki.ros.org/xacro"> <xacro:include filename = "head.xacro" /> <xacro:include filename = "demo05_car_base.urdf.xacro" /> <xacro:include filename = "demo06_car_camera.urdf.xacro" /> <xacro:include filename = "demo07_car_laser.urdf.xacro" /> <xacro:include filename = "gazebo/move.xacro" /> <xacro:include filename = "gazebo/laser.xacro" /> <xacro:include filename = "gazebo/camera.xacro" /> <xacro:include filename = "gazebo/kinect.xacro" /> </robot>
-
启动仿真环境
$ source ./devel/setup.bash $ roslaunch urdf02_gazebo demo03_env.launch
-
启动 rviz:demo04_sensor.launch 显示 Kinect 数据
$ roslaunch urdf02_gazebo demo04_sensor.launch
补充:kinect 点云数据显示
-
在 kinect 中也可以以点云的方式显示感知周围环境,在 rviz 中操作如下
-
问题:在rviz中显示时错位
-
原因:在 kinect 中图像数据与点云数据使用了两套坐标系统,且两套坐标系统位姿并不一致
-
解决方法
- 在 kinect.xacro 中修改下行代码
<!-- 将 support 改为 support_depth --> <frameName>support_depth</frameName>
- 发布新设置的坐标系到 kinect 连杆的坐标变换关系,在启动 rviz 的 launch 中,添加
<node pkg="tf2_ros" type="static_transform_publisher" name="static_transform_publisher" args="0 0 0 -1.57 0 -1.57 /support /support_depth" />
-
8. 小结
- URDF 是用于描述机器人模型的 xml 文件,可以使用不同的标签具代表不同含义,URDF 编写机器人模型代码冗余,xacro 可以优化 URDF 实现,代码实现更为精简、高效、易读
- 容易混淆的是Rviz与Gazebo,在此我们着重比较以下二者的区别
- rviz 是三维可视化工具,强调把已有的数据可视化显示
- rviz 需要已有数据。rviz 提供了很多插件,这些插件可以显示图像、模型、路径等信息,但前提是这些数据已经以话题、参数的形式发布,rviz 做的事情就是订阅这些数据,并完成可视化的渲染,让开发者更容易理解数据的意义
- Gazebo 是三维物理仿真平台,强调的是创建一个虚拟的仿真环境
- Gazebo 不是显示工具,强调的是仿真,它不需要数据,而是创造数据。我们可以在 Gazebo 中免费创建一个机器人世界,不仅可以仿真机器人的运动功能,还可以仿真机器人的传感器数据。而这些数据就可以放到 rviz 中显示,所以使用 Gazebo 的时候,经常也会和 rviz 配合使用
- rviz 是三维可视化工具,强调把已有的数据可视化显示
- 综上,如果你手上已经有机器人硬件平台,并且在上边可以完成需要的功能,用 rviz 应该就可以满足开发需求
- 如果你手上没有机器人硬件,或者想在仿真环境中做一些算法、应用的测试,Gazebo + rviz 应该是你需要的