设置 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 安装目录的环境变量:
source /opt/ros/<ros2-distro>/setup.bash然后安装所需包:
sudo apt install ros-$ROS_DISTRO-joint-state-publisher-guisudo apt install ros-$ROS_DISTRO-xacro接下来,为项目创建目录,初始化一个 ROS 2 工作空间,并为机器人命名。本教程中机器人命名为 sam_bot。
ros2 pkg create --build-type ament_cmake sam_bot_description编写 URDF
Section titled “编写 URDF”注意:本节旨在提供构建机器人 URDF 的入门级介绍。如需了解更多关于 URDF 和 Xacro 的信息,可参考官方的 URDF 文档。
项目工作空间已准备就绪,下面开始编写 URDF。以下是要构建的机器人示意图。

首先,在 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 LaunchDescriptionfrom launch.actions import DeclareLaunchArgumentfrom launch.conditions import IfCondition, UnlessConditionfrom launch.substitutions import Command, LaunchConfigurationfrom launch_ros.actions import Nodefrom launch_ros.substitutions import FindPackageShareimport 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 文件,内容如下:
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: 557Visualization 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) 行之上):
install( DIRECTORY src launch rviz DESTINATION share/${PROJECT_NAME})现在可以使用 colcon 构建项目了。进入项目根目录并执行以下命令。
colcon build. install/setup.bash构建成功后,执行以下命令启动项目。
ros2 launch sam_bot_description display.launch.pyROS 2 此时应该会启动一个机器人状态发布节点,并使用 URDF 启动 RViz。下一节中将使用 RViz 查看机器人。
使用 RViz 进行可视化
Section titled “使用 RViz 进行可视化”RViz 是一种机器人可视化工具,可以根据 URDF 查看机器人的 3D 模型。使用上一节的命令成功启动后,RViz 应显示在屏幕上,效果如下图所示。可以拖动视图来更好地观察机器人。

如图所示,已成功创建一个简单的差速驱动机器人并在 RViz 中可视化。虽然不一定非要在 RViz 中可视化机器人,但这是检查 URDF 是否定义正确的好方法,也有助于确认机器人状态发布器是否发布了正确的变换。
可能还注意到启动了另一个窗口——这是关节状态发布器(joint state publisher)的 GUI。关节状态发布器是另一个 ROS 2 包,用于发布非固定关节的状态。通过这个小 GUI 可以操纵该发布器,关节的新位姿(pose)会实时反映在 RViz 中。拖动任意一个轮子的滑条即可旋转对应关节。

注意:Nav2 中不会大量使用这个包。如需了解更多关于关节状态发布器的信息,可查看官方的 关节状态发布器文档。
至此,已经实现了为简单差速驱动机器人创建 URDF 的目标。机器人状态发布器现在正在发布从 URDF 派生出的变换,这些变换可被 Nav2 等其他包用来获取机器人的形状和结构信息。然而,要在仿真中正确使用此 URDF,还需要添加物理属性,使机器人像真实机器人一样对物理环境作出反应。目前定义的可视化字段仅用于显示,不参与碰撞计算,因此机器人会径直穿过障碍物。下一节将为 URDF 添加这些物理属性。
添加物理属性
Section titled “添加物理属性”作为本指南的附加内容,下面将修改当前的 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。
colcon build. install/setup.bashros2 launch sam_bot_description display.launch.py可以在左侧面板的 RobotModel 下启用 Collision Enabled 来验证碰撞区域是否正确设置(如果同时关闭 Visual Enabled,效果会更清晰)。在本教程中,定义的碰撞区域与视觉属性基本一致。不过实际项目中并非总是如此——根据机器人的外观,有时会选择更简化的碰撞区域。

目前的进度就到这里。要真正在 Gazebo 中模拟机器人,还需要设置更多组件。后续的设置指南会回到这个项目,在进入仿真部分时最终看到机器人在虚拟环境中移动。当前缺少的主要组件是模拟机器人控制器所需的仿真插件,相关内容将在适当的章节中介绍并添加到此 URDF 中。
至此,本教程完毕。在本教程中,已成功为一个简单的差速驱动机器人创建了 URDF,并设置了一个 ROS 2 项目来启动机器人状态发布节点,该节点使用 URDF 发布机器人的变换。同时使用 RViz 可视化了机器人模型,验证了 URDF 的正确性。最后,为 URDF 添加了物理属性,为仿真做好了准备。
欢迎将本教程作为自己机器人的模板。需要注意的是,主要目标是从 base_link 到所有传感器坐标系发布正确的变换。这些设置完成后,就可以继续其他设置指南了。