Skip to content

设置 URDF

注意:在后续教程中,还需要为 Gazebo 仿真设置一个 SDF 文件。URDF 用于建立机器人的坐标系(frame)并描述机器人结构,供硬件运行时及仿真使用。SDF 是 Gazebo 等仿真器专用的文件格式,用于描述仿真环境、模型(包括坐标系和 Gazebo 特定信息)以及相应插件。后续教程中制作的 SDF 面向 Gazebo,也可以替换为适用于 Open3D Engine 或 Isaac Sim 的对应文件。

本指南将为一个简单的差速驱动机器人创建统一机器人描述格式(URDF)文件,帮助你积累 URDF 实践经验。同时,我们会设置机器人状态发布器(robot state publisher)并在 RViz 中可视化模型。最后,为机器人 URDF 添加运动学属性,为仿真做好准备。这些步骤对于表达机器人的所有传感器、硬件和坐标变换(transform)都是必不可少的,导航功能依赖它们来运行。

注意:本教程的完整源代码可在 navigation2_tutorials 仓库的 sam_bot_description 包中找到。该仓库包含完成本系列所有教程后的完整代码。

URDF 与机器人状态发布器(Robot State Publisher)

Section titled “URDF 与机器人状态发布器(Robot State Publisher)”

正如上一个教程中所讨论的,Navigation2 的要求之一是建立从 base_link 到各种传感器和参考坐标系的变换。这个变换树可以很简单,比如只有一个从 base_link 到 laser_link 的连杆;也可以由位于不同位置的多个传感器组成,每个传感器都有自己的坐标系。为每个变换单独创建发布器会非常繁琐。因此,可以使用机器人状态发布器(Robot State Publisher)包来统一发布这些变换。

机器人状态发布器是 ROS 2 的一个包,它与 tf2 包交互,发布所有可以直接从机器人几何结构中推断出的必要变换。只需为其提供正确的 URDF,它就能自动处理变换的发布。这对于复杂的变换非常有用,对于较简单的变换树也同样推荐使用。

统一机器人描述格式(URDF)是一种表示机器人模型的 XML 文件。在本教程中,它主要用于构建与机器人几何相关的变换树,但它还有其他用途。例如,通过定义视觉组件(如材质和网格),可以在 RViz(ROS 的 3D 可视化工具)中可视化机器人模型。此外,URDF 还可以定义机器人的物理属性,供 Gazebo 等物理仿真器使用,以模拟机器人在环境中的交互行为。

URDF 的另一个主要特性是支持 Xacro(XML 宏),可以用来编写更简洁、可读性更强的 XML 来定义复杂的机器人模型。借助宏可以消除 URDF 中重复的 XML 代码块。Xacro 也适合定义可在整个 URDF 中重复使用的配置常量。

注意:如果想了解更多关于 URDF 和机器人状态发布器的信息,可参考官方的 URDF 文档 和 机器人状态发布器文档。

本指南假设读者已熟悉 ROS 2 及其开发环境的配置,因此本节将快速过一遍相关步骤。

首先,安装本教程所需的额外 ROS 2 包。为此,先加载 ROS 2 安装目录的环境变量:

Terminal window
source /opt/ros/<ros2-distro>/setup.bash

然后安装所需包:

Terminal window
sudo apt install ros-$ROS_DISTRO-joint-state-publisher-gui
sudo apt install ros-$ROS_DISTRO-xacro

接下来,为项目创建目录,初始化一个 ROS 2 工作空间,并为机器人命名。本教程中机器人命名为 sam_bot。

Terminal window
ros2 pkg create --build-type ament_cmake sam_bot_description

注意:本节旨在提供构建机器人 URDF 的入门级介绍。如需了解更多关于 URDF 和 Xacro 的信息,可参考官方的 URDF 文档。

项目工作空间已准备就绪,下面开始编写 URDF。以下是要构建的机器人示意图。

base-bot_1 base-bot_2

首先,在 src/description 下创建文件 sam_bot_description.urdf,输入以下内容作为初始模板。

<?xml version="1.0"?>
<robot name="sam_bot" xmlns:xacro="http://ros.org/wiki/xacro">
</robot>

注意:以下代码片段应放在 <robot> 标签内,建议按本教程介绍的顺序逐步添加。文中附有一些行号,帮助了解代码的大致位置。根据实际使用的空白字符,行号可能与你的文件略有差异;这些行号假定按本指南顺序输入代码。

接下来,使用 Xacro 属性定义一些将在整个 URDF 中重复使用的常量。

<!-- Define robot constants -->
<xacro:property name="base_width" value="0.31"/>
<xacro:property name="base_length" value="0.42"/>
<xacro:property name="base_height" value="0.18"/>
<xacro:property name="wheel_radius" value="0.10"/>
<xacro:property name="wheel_width" value="0.04"/>
<xacro:property name="wheel_ygap" value="0.025"/>
<xacro:property name="wheel_zoff" value="0.05"/>
<xacro:property name="wheel_xoff" value="0.12"/>
<xacro:property name="caster_xoff" value="0.14"/>

下面简要说明这些属性的含义。base_* 属性定义了机器人主底盘的尺寸。wheel_radius 和 wheel_width 定义了两个后轮的形状。wheel_ygap 调整轮子与底盘之间沿 y 轴的间隙,wheel_zoff 和 wheel_xoff 分别将后轮定位在合适的 z 轴和 x 轴位置。caster_xoff 则将前脚轮沿 x 轴定位。

接下来定义 base_link——这个连杆是一个大盒子,充当机器人的主底盘。在 URDF 中,link 元素描述机器人的刚性部件或组件。机器人状态发布器利用这些定义为每个连杆确定坐标系,并发布它们之间的变换。

同时为该连杆定义一些视觉属性,供 Gazebo 和 RViz 等工具展示机器人的 3D 模型。这些属性包括描述连杆形状的 <geometry> 和描述其颜色的 <material>。

在下面的代码块中,通过 ${property} 语法引用之前定义的 base_* 属性,并将主底盘的材质颜色设置为 Cyan。这些参数放在 <visual> 标签下,仅用于视觉显示,不影响碰撞或物理属性。

<!-- Robot Base -->
<link name="base_link">
<visual>
<geometry>
<box size="${base_length} ${base_width} ${base_height}"/>
</geometry>
<material name="Cyan">
<color rgba="0 1.0 1.0 1.0"/>
</material>
</visual>
</link>

接下来定义 base_footprint 连杆。base_footprint 是一个虚拟的(非物理的)连杆,没有尺寸或碰撞区域。它的主要用途是让各种包确定机器人投影到地面上的中心点。例如,Navigation2 使用这个连杆来确定避障算法中圆形足迹(footprint)的中心。因此将该连杆设置为无尺寸,并指明机器人中心投影到地面平面时的位置。

定义完 base_link 后,添加一个关节(joint)将 base_footprint 连接到 base_link。在 URDF 中,joint 元素描述坐标系之间的运动学和动力学属性。这里定义一个 fixed(固定)关节,并设置适当的偏移量,将 base_footprint 连杆放置在正确位置。由于希望 base_footprint 位于主底盘中心投影到地面的位置,因此取 wheel_radius 与 wheel_zoff 之和来确定沿 z 轴的偏移。

<!-- Robot Footprint -->
<link name="base_footprint"/>
<joint name="base_joint" type="fixed">
<parent link="base_link"/>
<child link="base_footprint"/>
<origin xyz="0.0 0.0 ${-(wheel_radius+wheel_zoff)}" rpy="0 0 0"/>
</joint>

接下来为机器人添加两个大型驱动轮。为使代码更简洁并避免重复,使用宏来定义一段以不同参数重复调用的代码。宏有 3 个参数:prefix 为连杆和关节名称添加前缀,x_reflect 和 y_reflect 分别控制轮子相对于 x 轴和 y 轴的位置翻转。在宏中定义单个轮子的视觉属性,以及一个 continuous(连续)关节,使轮子可以绕轴自由旋转。该关节将轮子连接到 base_link 的适当位置。

在代码块末尾,通过 xacro:wheel 标签实例化两个轮子,参数设置使两个轮子分别位于机器人后部的左右两侧。

<!-- Wheels -->
<xacro:macro name="wheel" params="prefix x_reflect y_reflect">
<link name="${prefix}_link">
<visual>
<origin xyz="0 0 0" rpy="${pi/2} 0 0"/>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_width}"/>
</geometry>
<material name="Gray">
<color rgba="0.5 0.5 0.5 1.0"/>
</material>
</visual>
</link>
<joint name="${prefix}_joint" type="continuous">
<parent link="base_link"/>
<child link="${prefix}_link"/>
<origin xyz="${x_reflect*wheel_xoff} ${y_reflect*(base_width/2+wheel_ygap)} ${-wheel_zoff}" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
</xacro:macro>
<xacro:wheel prefix="drivewhl_l" x_reflect="-1" y_reflect="1" />
<xacro:wheel prefix="drivewhl_r" x_reflect="-1" y_reflect="-1" />

接下来在机器人前部添加一个脚轮(caster wheel),为简单起见将其建模为球体。同样定义轮子的几何形状、材质以及将其连接到 base_link 合适位置的关节。

<!-- Caster Wheel -->
<link name="front_caster">
<visual>
<geometry>
<sphere radius="${(wheel_radius+wheel_zoff-(base_height/2))}"/>
</geometry>
<material name="Cyan">
<color rgba="0 1.0 1.0 1.0"/>
</material>
</visual>
</link>
<joint name="caster_joint" type="fixed">
<parent link="base_link"/>
<child link="front_caster"/>
<origin xyz="${caster_xoff} 0.0 ${-(base_height/2)}" rpy="0 0 0"/>
</joint>

至此,一个简单的差速驱动机器人的 URDF 就构建完成了。下一节将重点介绍如何构建包含 URDF 的 ROS 包、启动机器人状态发布器,并在 RViz 中可视化机器人。

注意:本教程中的启动文件改编自官方的 ROS 2 URDF 教程。

首先添加构建此项目所需的依赖。打开项目根目录下的 package.xml,添加以下行(建议放在 <buildtool_depend> 标签之后)。

<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>joint_state_publisher_gui</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>rviz</exec_depend>
<exec_depend>xacro</exec_depend>

接下来创建启动文件。ROS 2 使用启动文件来启动包所需的节点。在项目根目录下创建 launch 目录,在其中创建 display.launch.py 文件。该启动文件会在 ROS 2 中启动一个机器人状态发布节点,使用 URDF 发布机器人的变换,同时自动启动 RViz 以可视化 URDF 定义的机器人。将下面的代码片段复制到 display.launch.py 文件中。

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import Command, LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
import os
def generate_launch_description():
pkg_share = FindPackageShare(package='sam_bot_description').find('sam_bot_description')
default_model_path = os.path.join(pkg_share, 'src', 'description', 'sam_bot_description.urdf')
default_rviz_config_path = os.path.join(pkg_share, 'rviz', 'config.rviz')
robot_state_publisher_node = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': Command(['xacro ', LaunchConfiguration('model')])}]
)
joint_state_publisher_node = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
name='joint_state_publisher',
parameters=[{'robot_description': Command(['xacro ', default_model_path])}],
condition=UnlessCondition(LaunchConfiguration('gui'))
)
joint_state_publisher_gui_node = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
name='joint_state_publisher_gui',
condition=IfCondition(LaunchConfiguration('gui'))
)
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', LaunchConfiguration('rvizconfig')],
)
return LaunchDescription([
DeclareLaunchArgument(name='gui', default_value='True', description='Flag to enable joint_state_publisher_gui'),
DeclareLaunchArgument(name='model', default_value=default_model_path, description='Absolute path to robot model file'),
DeclareLaunchArgument(name='rvizconfig', default_value=default_rviz_config_path, description='Absolute path to rviz config file'),
joint_state_publisher_node,
joint_state_publisher_gui_node,
robot_state_publisher_node,
rviz_node
])

注意:有关 ROS 2 启动系统的更多信息,可参考官方的 ROS 2 启动系统文档。

为简化可视化流程,这里提供一个 RViz 配置文件,在启动包时会自动加载。该配置文件会以合适的设置初始化 RViz,启动后即可立即查看机器人。在项目根目录下创建 rviz 目录,在其中创建 config.rviz 文件,内容如下:

Terminal window
Panels:
- Class: rviz_common/Displays
Help Height: 78
Name: Displays
Property Tree Widget:
Expanded:
- /Global Options1
- /Status1
- /RobotModel1/Links1
- /TF1
Splitter Ratio: 0.5
Tree Height: 557
Visualization Manager:
Class: ""
Displays:
- Alpha: 0.5
Cell Size: 1
Class: rviz_default_plugins/Grid
Color: 160; 160; 164
Enabled: true
Name: Grid
- Alpha: 0.6
Class: rviz_default_plugins/RobotModel
Description Topic:
Depth: 5
Durability Policy: Volatile
History Policy: Keep Last
Reliability Policy: Reliable
Value: /robot_description
Enabled: true
Name: RobotModel
Visual Enabled: true
- Class: rviz_default_plugins/TF
Enabled: true
Name: TF
Marker Scale: 0.3
Show Arrows: true
Show Axes: true
Show Names: true
Enabled: true
Global Options:
Background Color: 48; 48; 48
Fixed Frame: base_link
Frame Rate: 30
Name: root
Tools:
- Class: rviz_default_plugins/Interact
Hide Inactive Objects: true
- Class: rviz_default_plugins/MoveCamera
- Class: rviz_default_plugins/Select
- Class: rviz_default_plugins/FocusCamera
- Class: rviz_default_plugins/Measure
Line color: 128; 128; 0
Transformation:
Current:
Class: rviz_default_plugins/TF
Value: true
Views:
Current:
Class: rviz_default_plugins/Orbit
Name: Current View
Target Frame: <Fixed Frame>
Value: Orbit (rviz)
Saved: ~

最后修改项目根目录下的 CMakeLists.txt 文件,在包安装过程中包含刚刚创建的文件。在 CMakeLists.txt 中添加以下代码片段(建议放在 if(BUILD_TESTING) 行之上):

Terminal window
install(
DIRECTORY src launch rviz
DESTINATION share/${PROJECT_NAME}
)

现在可以使用 colcon 构建项目了。进入项目根目录并执行以下命令。

Terminal window
colcon build
. install/setup.bash

构建成功后,执行以下命令启动项目。

Terminal window
ros2 launch sam_bot_description display.launch.py

ROS 2 此时应该会启动一个机器人状态发布节点,并使用 URDF 启动 RViz。下一节中将使用 RViz 查看机器人。

RViz 是一种机器人可视化工具,可以根据 URDF 查看机器人的 3D 模型。使用上一节的命令成功启动后,RViz 应显示在屏幕上,效果如下图所示。可以拖动视图来更好地观察机器人。

base-bot_3

如图所示,已成功创建一个简单的差速驱动机器人并在 RViz 中可视化。虽然不一定非要在 RViz 中可视化机器人,但这是检查 URDF 是否定义正确的好方法,也有助于确认机器人状态发布器是否发布了正确的变换。

可能还注意到启动了另一个窗口——这是关节状态发布器(joint state publisher)的 GUI。关节状态发布器是另一个 ROS 2 包,用于发布非固定关节的状态。通过这个小 GUI 可以操纵该发布器,关节的新位姿(pose)会实时反映在 RViz 中。拖动任意一个轮子的滑条即可旋转对应关节。

base-bot_4

注意:Nav2 中不会大量使用这个包。如需了解更多关于关节状态发布器的信息,可查看官方的 关节状态发布器文档。

至此,已经实现了为简单差速驱动机器人创建 URDF 的目标。机器人状态发布器现在正在发布从 URDF 派生出的变换,这些变换可被 Nav2 等其他包用来获取机器人的形状和结构信息。然而,要在仿真中正确使用此 URDF,还需要添加物理属性,使机器人像真实机器人一样对物理环境作出反应。目前定义的可视化字段仅用于显示,不参与碰撞计算,因此机器人会径直穿过障碍物。下一节将为 URDF 添加这些物理属性。

作为本指南的附加内容,下面将修改当前的 URDF,为其添加机器人的运动学属性。这些信息可供 Gazebo 等物理仿真器使用,以建模和模拟机器人在虚拟环境中的行为。

首先定义包含项目中使用的几何基元惯性属性的宏。将下面的代码片段放在 URDF 中常量定义之后:

<!-- Define inertial property macros -->
<xacro:macro name="box_inertia" params="m w h d">
<inertial>
<origin xyz="0 0 0" rpy="${pi/2} 0 ${pi/2}"/>
<mass value="${m}"/>
<inertia ixx="${(m/12) * (h*h + d*d)}" ixy="0.0" ixz="0.0" iyy="${(m/12) * (w*w + d*d)}" iyz="0.0" izz="${(m/12) * (w*w + h*h)}"/>
</inertial>
</xacro:macro>
<xacro:macro name="cylinder_inertia" params="m r h">
<inertial>
<origin xyz="0 0 0" rpy="${pi/2} 0 0" />
<mass value="${m}"/>
<inertia ixx="${(m/12) * (3*r*r + h*h)}" ixy = "0" ixz = "0" iyy="${(m/12) * (3*r*r + h*h)}" iyz = "0" izz="${(m/2) * (r*r)}"/>
</inertial>
</xacro:macro>
<xacro:macro name="sphere_inertia" params="m r">
<inertial>
<mass value="${m}"/>
<inertia ixx="${(2/5) * m * (r*r)}" ixy="0.0" ixz="0.0" iyy="${(2/5) * m * (r*r)}" iyz="0.0" izz="${(2/5) * m * (r*r)}"/>
</inertial>
</xacro:macro>

首先使用 <collision> 标签为 base_link 添加碰撞区域。将下面的代码片段放在 URDF 中 base_link 的 <link name="base_link"> 标签内。

<collision>
<geometry>
<box size="${base_length} ${base_width} ${base_height}"/>
</geometry>
</collision>

接下来使用之前定义的 box_inertia 宏为 base_link 添加惯性属性。 robot_state_publisher 中使用的 kdl_parser 包不支持在根连杆(此处为 base_link)中指定惯性属性,为规避这一问题,需将惯性属性放在 base_footprint 中。 将下面的代码片段放在 URDF 中 base_footprint 的 <link name="base_footprint"> 标签内。

<xacro:box_inertia m="15" w="${base_width}" d="${base_length}" h="${base_height}"/>

接下来对轮子宏做同样的处理。将下面的代码片段放在 URDF 中轮子宏的 <link name="${prefix}_link"> 标签内。

<collision>
<origin xyz="0 0 0" rpy="${pi/2} 0 0"/>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_width}"/>
</geometry>
</collision>
<xacro:cylinder_inertia m="0.5" r="${wheel_radius}" h="${wheel_width}"/>

最后为球形脚轮添加类似的属性。将以下内容放在 URDF 中脚轮的 <link name="front_caster"> 标签内。

<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<sphere radius="${(wheel_radius+wheel_zoff-(base_height/2))}"/>
</geometry>
</collision>
<xacro:sphere_inertia m="0.5" r="${(wheel_radius+wheel_zoff-(base_height/2))}"/>

注意:base_footprint 连杆是一个虚拟的、非物理的连杆,因此不需要添加任何惯性或碰撞属性。

使用上一节中相同的命令构建项目,然后启动 RViz。

Terminal window
colcon build
. install/setup.bash
ros2 launch sam_bot_description display.launch.py

可以在左侧面板的 RobotModel 下启用 Collision Enabled 来验证碰撞区域是否正确设置(如果同时关闭 Visual Enabled,效果会更清晰)。在本教程中,定义的碰撞区域与视觉属性基本一致。不过实际项目中并非总是如此——根据机器人的外观,有时会选择更简化的碰撞区域。

base-bot_5

目前的进度就到这里。要真正在 Gazebo 中模拟机器人,还需要设置更多组件。后续的设置指南会回到这个项目,在进入仿真部分时最终看到机器人在虚拟环境中移动。当前缺少的主要组件是模拟机器人控制器所需的仿真插件,相关内容将在适当的章节中介绍并添加到此 URDF 中。

至此,本教程完毕。在本教程中,已成功为一个简单的差速驱动机器人创建了 URDF,并设置了一个 ROS 2 项目来启动机器人状态发布节点,该节点使用 URDF 发布机器人的变换。同时使用 RViz 可视化了机器人模型,验证了 URDF 的正确性。最后,为 URDF 添加了物理属性,为仿真做好了准备。

欢迎将本教程作为自己机器人的模板。需要注意的是,主要目标是从 base_link 到所有传感器坐标系发布正确的变换。这些设置完成后,就可以继续其他设置指南了。