塞巴斯蒂安
一、说明
在我的机器人项目中,我从 ROS1 开始,并提供了一个可以使用 RVIZ 可视化的基本机器人模型。然后,我构建了一个实用的原型,完全基于Arduino,不涉及ROS。在项目的当前阶段,我致力于构建一个与视觉传感器兼容的 Gazebo 模型。
本文继续该系列,使用 Gazebo 快速启动 ROS2 模拟。我们将了解创建模拟的基本工具 - ROS启动文件和Gazebo - 并学习应用基本步骤 - 选择3D模型,将它们放置在世界上。
本文的技术背景是 Ubuntu 20.04、ROS2 Foxy、Gazebo11,但它也应该适用于较新的版本。
二、基本机器人仿真术语
本段简要列出了理解物理仿真方面所必需的所有术语。如果您对词汇量很坚定,请跳过本节。
- 运动学:运动学是物理学的一个分支,它控制着身体在时间、位置、速度和加速度方面的运动方式,而不考虑身体的重量或向外力,如重力
- 动力学:物理学的另一个分支,它考虑当物体有质量并受到力影响时物体的位置、速度和加速度如何变化。(在机器人中,这也称为机器人动力学)
- 里程计:一种通过连续记录运动数据来估计物体位置并计算物体移动的距离和轨迹的方法
- 惯性:这种力是运动物体对任何其他会改变其方向、速度或加速度的力的阻力
- 摩擦力:当两个物体靠近另一个物体移动并抵抗该移动时施加的力
- 关节:关节是连接两个物体的机械元件。关节有不同的类型来描述连接的物体如何移动。
来源:ROS机器人编程书
- RPY 值:此首字母缩略词代表横滚、俯仰、偏航,用于描述物体在 3D 空间中的运动。对象可以根据下图在这三个轴上移动:
来源: wikipedia.org
三、循序渐进:如何创建模拟世界
Gazebo是在ROS之外开始的,但后来完全整合了。它更侧重于机器人和世界的完整物理模拟。特别是,世界通过物理引擎提供正确的物理模拟:机器人可以撞到物体,这些物体会移动,最终甚至撞到你的机器人身上。Gazebo 带有预定义的世界模型,您甚至可以定义自己的模型。
在本教程中,我们将启动一个带有空世界的凉亭模拟,然后在里面生成一个机器人。简而言之,基本步骤是:
- 创建新的 ROS 包并设置目录结构
- 创建一个空的坐标定位文件
- 创建启动文件
- 在机器人 URDF 模型中添加其他物理属性
- 参数化机器人 URDF 模型以使用 Gazebo 或 RVIZ 运行。
- 用启动文件开始空的世界
以下各节详细介绍了这些任务。
3.1 第 1 步:包创建和目录结构
我们将创建一个如下所示的包结构:
radu_gazebo/
├── config
│ └── rviz.config
├── launch
│ └── launch.py
├── radu_bot
│ └── __init__.py
├── resource
├── scripts
├── test
├── urdf
│ └── core.xacro
└── worlds
│ └── room.world
├── package.xml
├── setup.cfg
├── setup.py
为方便起见,只需运行以下命令:
ros2 pkg create --build-type python radu_gazebo
mkdir radu_gazebo/launch radu_gazebo/world
mkdir radu_gazebo/launch radu_gazebo/world/room.world
touch radu_gazebo/launch/room.launch
3.2 第 2 步:清空坐标定位文件
该文件是一个SDF文件,将包含我们想要模拟的所有内容的标签:墙壁,窗户和家具等对象。我们将从一个简单的空白世界开始,并逐步添加新对象。room.world
<model>
<!-- FILE: world/room.world -->
<?xml version='1.0'?>
<sdf version="1.6">
<world name="room">
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://ground_plane</uri>
</include>
</world>
</sdf>
您可以手动将此文件加载到凉亭中。但是由于我们最终也将在这个模型中生成一个机器人,因此最好继续直接启动文件。
3.3 步骤 3:启动文件
正如我们在上一篇文章中了解到的,ROS2 不再支持 XML 启动文件,而是使用 Python 文件。
我们使用的启动文件包装包中的启动命令,并提供一个世界参数。gazebo_ros
#!/usr/bin/python3
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
package_name = 'radu_bot'
world_file = 'room.world'
def generate_launch_description():
pkg_gazebo_ros = get_package_share_directory('gazebo_ros')
pkg_radu_simulation = get_package_share_directory(package_name)
# launch Gazebo by including its definition
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gazebo_ros, 'launch', 'gazebo.launch.py'),
)
)
# load the world file
world_arg = DeclareLaunchArgument(
'world',
default_value=[os.path.join(pkg_radu_simulation, 'worlds', world_file), ''],
description='SDF world file')
return LaunchDescription([
gazebo,
world_arg
])
3.4 第 4 步:使用 Gazebo 标签扩展机器人模型
为了在凉亭中使用RViz的URDF模型,我们需要对模型进行一些更改。
首先,我们需要为机器人提供额外的物理方面,以便它在模拟中正确运行。其次,机器人的视觉外观不同。RVIZ 模型的颜色和文本定义不适用。如果使用网格,也需要更改它们。第三,我们还需要额外的插件,以便 Gazebo 工具正常工作
让我们逐步查看这些更改。
3.5 物理模拟属性
惯性
物体的惯性是当其当前运动受到另一个物体影响时施加的反作用力。在凉亭模型中,标签用于表示这一方面。<inertial>
下面是一个示例:
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.25" />
<inertia ixx="0.000486" ixy="0.0" ixz="0.0" iyy="0.000486" iyz="0.0" izz="0.000729"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.09" length="0.09"/>
</geometry>
</visual>
</link>
标签相对于其父链接定位链接,在这里您可以更改值以移动估计的质量中心。通过您指定此链接质量(以千克为单位)。最后,该元素是 x、y 和 z 上的力如何影响链接的矩阵。您可以在维基百科上阅读物理文章,或使用这个方便的python脚本。<origin>
xyz
<mass>
<inertial>
摩擦
另一组变量控制机器人中链接的摩擦。你用四个值来表达这一点。首先,提供静态和动态接触刚度。在这里,我们使用值和,这是许多ROS项目中使用的默认值。其次,值和是静态和动态摩擦系数,您可以根据链接的材料在维基百科上查找。<kp>
<kd>
100000
1.0
<mu1>
<mu2>
<gazebo reference="base_link">
<kp>100000.0</kp>
<kd>1.0</kd>
<mu1>10.0</mu1>
<mu2>10.0</mu2>
</gazebo>
碰撞
这定义了机器人的硬材料边界——它会影响在模拟中将重力和其他力施加到机器人上的方式。这些属性由链接中的标记表示。它的属性非常简单:只需复制原始链接和值,如下所示。<collision>
<geometry>
<origin>
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.25" />
<inertia ixx="0.000486" ixy="0.0" ixz="0.0" iyy="0.000486" iyz="0.0" izz="0.000729"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.09" length="0.09"/>
</geometry>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.09" length="0.09"/>
</geometry>
</collision>
</link>
关节特性
机器人中的关节应进一步建模,以表达其真实世界的行为。
- 对于所有非静态、非连续接头,集合和值
<upper>
<lower>
- 对于所有连续接头,添加和限制
<effort>
<velocity>
<joint name="camera_joint">
<limit upper="0.5" lower="-0.5"/>
</joint>
<joint name="left_wheel_joint">
<limit effort="0.1" velocity="0.005"/>
</joint>
3.6 可视化模拟属性
要更改机器人的视觉效果,您有以下选项:
简单的颜色
这与 RViz 中的工作方式相同:在链接标签中,您可以引用一个元素。<visual>
<color>
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
<link name="camera">
<visuals>
<material name="blue">
</visuals>
</link>
预定义网格
Gazebo 提供了一组内置网格,这些网格在此源代码文件中列出。应用它们,在标签内添加标签,如下所示。<material>
<gazebo>
<gazebo reference="base_link">
<material>Gazebo/Grey</material>
</gazebo>
自定义网格
使用自定义网格表示链接时,只需在链接的标记中引用它们,如下所示:<geometry>
<link name="camera">
<mesh filename:"package://radu_bot/model/meshes/camera.dae" />
</link>
重要提示:建议不要在标签中使用自定义网格文件,因为这会影响仿真性能。相反,请根据可用的盒子、圆柱体和球体类型定义值。<collision>
<geometry>
3.7 第 5 步:参数化机器人 URDF 模型以使用 Gazebo 或 RViz 跑步
如您所见,所需的更改是根本性的。而且它们不向后兼容:Gazebo 所需的所有更改都无法被 RVIZ 解析。
由于这些原因,复杂的机器人项目将URDF方面分成不同的XACRO文件。经过一些实验,我想出了以下层次结构。
core
- 包含用于渲染机器人链接和关节的核心宏rviz
- RVIZ主文件,它定义参数并导入其他文件rviz_viusals
- 定义机器人在RViz中的可视化方式gazebo
- 凉亭核心文件,就像定义参数和导入其他文件一样gazebo_visuals
- 定义如何在凉亭中可视化机器人gazebo_physics
- 计算链接和标签的附加宏<inertial>
<collision>
gazebo_sensor
- 添加传感器数据gazebo_controll
- 添加了 ROS 控制插件并定义用于渲染标签的宏<transmission>
让我们看看这种方法在实践中是如何工作的。运行 Xacro 渲染模型时,将使用命令 -o radu_rviz_compiled.urdf'。此文件将...xacro rviz.xacro
- 导入其他必需的文件
<xacro:include filename="$(find radu_bot)/urdf2/core.xacro"/>
<xacro:include filename="$(find radu_bot)/urdf2/visuals.xacro"/>
2. 定义控制宏执行的基本参数
<xacro:property name="gazebo_build" value="false" />
<xacro:property name="rviz_build" value="true" />
3. 执行宏以创建 URDF 模型
<xacro:box_link name="base_link" size="0.6 0.3 0.05" color="${torso_color_name}" color_rgb="${torso_color_rgb}" />
<xacro:wheel_link name="right_wheel_frontside" />
<xacro:wheel_joint name="base_link_right_wheel_frontside" parent="base_link" child="right_wheel_frontside" xyz="0.2 -0.2 -0.05" />
<xacro:wheel_link name="right_wheel_backside" />
<xacro:wheel_joint name="base_link_right_wheel_backside" parent="base_link" child="right_wheel_backside" xyz="-0.2 -0.2 -0.05" />
<xacro:wheel_link name="left_wheel_frontside" />
<xacro:wheel_joint name="base_link_left_wheel_frontside" parent="base_link" child="left_wheel_frontside" xyz="0.2 0.2 -0.05" />
<xacro:wheel_link name="left_wheel_backside" />
<xacro:wheel_joint name="base_link_left_wheel_backside" parent="base_link" child="left_wheel_backside" xyz="-0.2 0.2 -0.05" />
在使用这种方法一段时间后,我意识到处理可变性的核心逻辑在文件内部:用于呈现链接和关节的宏具有将由主文件触发的不同块。请参阅以下定义。在第 3 行中,评估条件以添加特定于 RViz 的视觉对象。在第 14 行中,另一个条件检查凉亭物理属性并将其应用于模型。core.xacro
<link>
<xacro:if>
<xacro:macro name="box_link" params="name size color color_rgb" >
<link name="${name}">
<xacro:if value="${rviz_build}">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${color}">
<color rgba="${color_rgb}"/>
</material>
</visual>
</xacro:if>
<xacro:if value="${gazebo_build}">
<pose>0 0 0 0 0 0</pose>
<xacro:box_inertia m="0.6" x="0.7" y="0.4" z="0.2"/>
<collision name="collision_${name}">
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="${size}"/>
</geometry>
</collision>
</xacro:if>
</link>
</xacro:macro>
3.8 第 6 步:生成机器人
Gazebo 节点通过启动文件启动,但机器人需要生成到节点中。感谢博客文章如何在 ROS2 中生成机器人,我创建了以下启动文件。
#!/usr/bin/python3
import os
import sys
import rclpy
from gazebo_msgs.srv import SpawnEntity
from ament_index_python.packages import get_package_share_directory
package_name = 'radu_bot'
def main(args=None):
rclpy.init(args=args)
node = rclpy.create_node('minimal_client')
cli = node.create_client(SpawnEntity, '/spawn_entity')
sdf_file_path = (os.path.join(get_package_share_directory(package_name), 'urdf', 'radu_gazebo_compiled.urdf')),
model = open(sdf_file_path[0], 'r').read()
print("MODEL %s" %model)
req = SpawnEntity.Request(
name = "radu_bot",
xml = model,
robot_namespace = "radu",
reference_frame = "world",
)
while not cli.wait_for_service(timeout_sec=1.0):
node.get_logger().info('service not available, waiting again...')
future = cli.call_async(req)
rclpy.spin_until_future_complete(node, future)
if future.result() is not None:
node.get_logger().info(
'Result ' + str(future.result().success) + " " + future.result().status_message)
else:
node.get_logger().info('Service call failed %r' % (future.exception(),))
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
启动文件还可以在启动期间转换 Xacro 文件,如diff_bot示例中所示。例如,要加载 Gazebo 配置,您需要执行以下命令:
import xacro
def generate_launch_description():
pkg_radu_simulation = get_package_share_directory(package_name)
robot_description_path = os.path.join(
pkg_radu_simulation,
"urdf",
"gazebo.xacro",
)
robot_description = {"robot_description": xacro.process_file(robot_description_path).toxml()}
四、启动机器人
首先,我们构建当前工作区。
$> colcon build --symlink-install --cmake-clean-first --event-handlers console_direct+ --packages-up-to radu_bot
然后我们推出凉亭。
$> ros2 launch radu_bot gazebo.launch.py
[INFO] [launch]: All log files can be found below /home/devcon/.ros/log/2021-05-30-09-07-30-541933-giga-36879
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [gzserver-1]: process started with pid [36886]
[INFO] [gzclient -2]: process started with pid [36889]
然后生成机器人。
$> ros2 run radu_bot spawn_radu
[INFO] [1622358479.722919377] [minimal_client]: Result True SpawnEntity: Successfully spawned entity [radu_bot]
最后,渲染机器人:
五、结论
本文向您展示了如何从头开始在 ROS2 中创建与 Gazebo 兼容的模拟。事实证明,这是一个漫长的 过程,您需要 (1) 创建一个包,(2) 创建坐标定位文件,(3) 创建启动文件,(4) 将 Gazebo 特定标签添加到我们的机器人模型中,(5) 参数化您的机器人模型以与 Gazebo 和 RVIZ 兼容,以及 (6) 在模拟中生成机器人实体。形成所有这些步骤。添加 Gazebo 物理是学习和应用的时间密集型,我希望您也能获得宝贵的见解。最后,我们可以使用自定义启动脚本文件在 Gazebo 和 RViz 中生成 RADU。从这里,我们可以在模拟中移动机器人。