# ROS2基础教程 - 13 URDF机器人建模

URDF(Unified Robot Description Format)是 ROS 2 中用于描述机器人模型的一种 XML 格式文件,它提供了一种标准化的方式来表示机器人的结构、关节、链接以及各种属性。

我们为什么要进行机器人建模呢?

  • 在仿真环境中,我们可以定义机器人模型,来模拟真实的机器人进行开发(仿真环境后面再介绍)。
  • 通过定义机器人模型,可以建立机器人各个部件或传感器的关系,实现 TF 坐标系的转换。

URDF 文件就是一个 XML 文件,其中核心组件包括:

  • Link(链接):表示机器人的一个部分,定义了其几何形状、质量、惯性等,例如用来定义底盘、轮子、摄像头。
  • Joint(关节):连接两个链接的方式,定义了它们之间的关系及运动模式(如固定、旋转、滑动)。


下面就来介绍如何使用 URDF 来定义一个机器人模型,最终显示如下:

# 13.1 URDF的使用

# 1 新建功能包

ros2 pkg create --build-type ament_python robot_model
1

# 2 创建URDF模型

功能包/urdf 目录下(没有就创建),创建一个 robot.urdf 文件(名称自定义),内容如下:

首先从机器人的身体开始定义,先不添加轮子。

<?xml version="1.0" encoding="utf-8"?>

<robot name="robot">

    <link name="base_footprint">
        <!-- visual描述外观 -->
        <visual>
            <!-- geometry 设置连杆的形状 -->
            <geometry>
                <!-- sphere球体 -->
                <sphere radius="0.001" />
            </geometry>
        </visual>
    </link>
  
  	<link name="base_link">
      <visual>
        <geometry>
          <cylinder length="0.12" radius="0.14"/>
        </geometry>
        <origin rpy="0 0 0" xyz="0 0 0"/>
        
        <!-- metrial 设置材料属性 -->
        <material name="black_color">
          <color rgba="0 0 0 1"/>
        </material>
      </visual>
    </link>

    <joint name="base_link2base_footprint" type="fixed">
      <parent link="base_footprint"/>
      <child link="base_link"/>
      <origin xyz="0 0 0.1"/>
    </joint>
    
</robot>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

解释一下上面的定义:

  • 首先使用 <robot> 标签定义机器人,这里的 name 属性是自定义的,然后在 <robot> 标签中定义各个组件。

  • 在上面使用 <link> 标签定义了两个组件,一个是 base_link 表示的是机器人的身体,这里还定义了一个 base_footprint ,为什么要定义 base_footprint 呢,因为如果使用 base_link 坐标系作为机器人的根坐标系,那么因为轮子和机器人身体体积的原因,那么 base_link 中心点肯定不在地面上,为了简化坐标转换,所以将添加了一个 base_footprint 节点,就相当于是机器人在地面上的投影,它位于机器人的底部或与地面齐平,而且将其体积设置为很小,看不见。这样与全局地图 map 和 里程计 odm 坐标系的转换会方便一些。所以一般情况下,创建机器人模型,都是添加一个 base_footprint ,在 base_footprint 节点上构建机器人模型。

  • <link> 标签中,使用 <visual> 标签描述外观,使用 <geometry> 标签定义形状。

    <!-- 形状 -->
    <geometry>
      	<!-- 球体,半径-->
        <sphere radius="0.01" />
      
        <!-- 长方体的长宽高 -->
        <!-- <box size="0.4 0.2 0.1" /> -->
      
        <!-- 圆柱,半径和长度 -->
        <!-- <cylinder radius="0.5" length="0.1" /> -->
    </geometry>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 使用 <joint> 标签,定义各个组件如何连接的,指定 父link子link,并使用 <origin> 定义 子link 相对于 父 link 的偏移和旋转,joint 在模型中是不显示的。

  • <link> 标签和 <joint> 标签中都有 <origin> 标签,可以定义偏移和旋转,<link><origin> 改变的是 <link> 的坐标系相对于默认坐标系的姿态位置,例如部件内部的某个东西(比如“外壳”或“重心”)相对于这个部件的中心点的位姿。而 <joint><origin> 是改变 <link> 在父坐标系的位置,所以设置相对于父组件的位置和角度,通过 <joint> 中的 <origin> 来设置,设置偏移和旋转弧度(ROS中用到的单位都是米和弧度)。上面设置了 <origin xyz="0 0 0.1"/> ,也就是 base_link 基于 base_footprint 中心在 Z 轴偏移 0.1base_link 高度的一般是 0.06,所以 base_link 离地面高度是 0.04 米,用于后面安装轮子。

  • <material> 用来设置材料属性,上面通过 <color> 设置RGB值和透明度。

  • <joint> 标签中 type 有如下的值:

continuous : 允许绕指定轴的无限制旋转(如车轮)。
revolute : 允许绕指定轴的有限旋转运动,机械臂的肘部或肩部,不能无限旋转。
prismatic : 滑动关节,允许沿指定轴的线性运动。
fixed : 将两个链接固定在一起,不允许任何相对运动。
floating : 允许6个自由度的运动(3个旋转和3个平移),用于定义完全自由的运动,一般较少使用。
planar : 允许在一个平面内的两个平移和一个旋转运动。
1
2
3
4
5
6

# 3 创建launch文件

下面使用 rviz 将模型显示出来。

功能包/launch 目录下创建一个 robot_model_launch.py 文件,内容如下:

import os
from launch import LaunchDescription
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
    package_name = 'robot_model'
    urdf_name = "robot_model.urdf"

    # 获取功能包路径
    pkg_share = FindPackageShare(package=package_name).find(package_name) 
    urdf_model_path = os.path.join(pkg_share, f'urdf/{urdf_name}')

    robot_state_publisher = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        arguments=[urdf_model_path]
    )

    joint_state_publisher = Node(
        package='joint_state_publisher',
        executable='joint_state_publisher',
        name='joint_state_publisher',
        arguments=[urdf_model_path]
    )

    rviz_config_path = os.path.join(
        get_package_share_directory(package_name),
        'rviz',
        'model.rviz'
    )

    rviz = Node(
        package='rviz2',
        executable='rviz2',
        name='rviz2',
        #arguments=[   # 在第一次运行后,保存配置后,指定配置文件的路径
        #    '-d', rviz_config_path
        #],
        output='screen'
    )

    return LaunchDescription([
        robot_state_publisher,
        joint_state_publisher,
        rviz
    ])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

上面启动了三个节点、 ros-humble-joint-state-publisherros-humble-robot-state-publisherrviz

首先需要安装一下 ros-humble-joint-state-publisherros-humble-robot-state-publisher

sudo apt install ros-humble-joint-state-publisher   # 我这里是humble版本
sudo apt install ros-humble-robot-state-publisher
1
2
  • joint_state_publisher :负责发布机器人关节数据信息,通过 joint_states 话题发布

  • robot_state_publisher :负责发布机器人模型信息 robot_description ,并将 joint_states 数据转换 TF 信息发布。

  • rviz :负责数据可视化,可以显示机器人模型

# 4 配置launch文件(重要)

setup.py新增 launch 相关配置:

from setuptools import find_packages, setup
import os
from glob import glob

package_name = 'robot_model'

setup(
    # ...其他配置
    data_files=[
        # ...其他配置
      
        (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', '*.*'))),
        (os.path.join('share', package_name, 'urdf'), glob(os.path.join('urdf', '*.*'))),  # urdf
    ],
    # ...其他配置
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

上面新增了三行配置,主要作用是安装 launch、config、rviz、urdf 目录中的文件到编译的 install 目录中,这样在运行的时候,能找到这些目录下的文件。(否则运行launch文件找不到

注意,后面使用了通配符进行了文件的匹配,你需要按照规则命令launch文件,我这里launch文件需要以 _launch.py 结尾。

# 5 构建项目

现在代码已经编写完成了,需要构建项目。

工作空间下执行如下命令:

colcon build
1

构建完成,会在 install 目录下生成文件。

# 6 运行节点

首先执行 source 命令,在工作空间下执行:

source install/local_setup.sh
1

上面的命令是让 ROS 找到我们的功能包,已经在 HelloWorld 章节说过了。


启动 launch :

ros2 launch robot_model robot_model_launch.py
1

运行后,会打开 rviz,打开后,Global Options -> Fixed Frame 选择 base_footprint

然后添加 RobotModel :


选择RobotModel后,Description Topic 选择 /robot_description ,就可以显示刚才 urdf 中定义的模型了。

为了看的清楚,可以不显示 TF

上面会显示 base_footprintbase_link

每次打开 rviz,都需要手动添加 RobotModel,可以保存配置,然后在 launch 文件中启动 rviz 的时候,指定配置文件的路径。

# 13.2 继续完善模型

下面继续在上面的 urdf 的基础上,继续完善机器人模型,包括左右两个驱动轮、前后两个支撑轮,摄像头,雷达支架、雷达。

# 1 添加左右驱动轮

驱动轮是圆柱体,需要旋转90度(弧度),然后与 base_link 连接。

<!-- 左轮 -->
<link name="left_wheel">
  <visual>
    <geometry>
      <cylinder length="0.03" radius="0.04"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="yellow_color">
      <color rgba="1 1 0 1"/>
    </material>
  </visual>
</link>
<joint name="left_wheel2base_link" type="continuous">
  <parent link="base_link"/>
  <child link="left_wheel"/>
  <origin xyz="0 -0.155 -0.06" rpy='1.57 0 0' />
</joint>

<!-- 右轮 -->
<link name="right_wheel">
  <visual>
    <geometry>
      <cylinder length="0.03" radius="0.04"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="yellow_color">
      <color rgba="1 1 0 1"/>
    </material>
  </visual>
</link>
<joint name="right_wheel2base_link" type="continuous">
  <parent link="base_link"/>
  <child link="right_wheel"/>
  <origin xyz="0 0.155 -0.06" rpy='-1.57 0 0' />
</joint>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

轮子是基于 base_link 中心点进行偏移的,base_link 的半径是 0.14 ,加上轮子的厚度的一半 0.015,所以在 Y 轴上平移 0.155base_link 高度是 0.12,一半是 0.06,所以设置为 <origin xyz="0 0.155 -0.06" rpy='-1.57 0 0' />-1.57 表示旋转90度。

添加完成,重新编译项目运行,显示效果:

# 2 添加前后支撑轮

前后支撑轮使用一个球体来代替。

轮子都定义成黄色,那么好多地方用到,那么可以在全局使用 <material> 标签用于定义材质和颜色,然后在 <visual> 标签中引用即可。

<material name="yellow_color">
  <color rgba="1 1 0 1"/>
</material>

<!-- 前支撑轮,定义为一个圆球 -->
<link name="front_wheel">
  <visual>
    <geometry>
      <sphere radius="0.02"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="yellow_color" />
  </visual>
</link>
<joint name="front_wheel2base_link" type="continuous">
  <parent link="base_link"/>
  <child link="front_wheel"/>
  <origin xyz="0.12 0 -0.08"/>
</joint>

<!-- 前支撑轮,定义为一个圆球 -->
<link name="back_wheel">
  <visual>
    <geometry>
      <sphere radius="0.02"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="yellow_color" />
  </visual>
</link>
<joint name="back_wheel2base_link" type="continuous">
  <parent link="base_link"/>
  <child link="back_wheel"/>
  <origin xyz="-0.12 0 -0.08"/>
</joint>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

base_link 离地 0.04,所以设置了支撑轮的半径为 0.02,并设置到 base_link 的前方和后方。因为是相对于 base_link 中心移动的,所以 Z 轴向下移动 base_link 高度的一半 加上 支撑轮的半径,也就是 0.08

添加完成,重新编译项目运行,显示效果:

# 3 添加雷达

我们先添加一个支架,然后将雷达安装在支架上。

所以雷达支架是相对于 base_link,雷达是相对于雷达支架进行定位。

<!-- 雷达支架 -->
<link name="laser_bracket_link">
  <visual>
    <geometry>
      <cylinder length="0.1" radius="0.01"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="gray_color">
      <color rgba="0.5 0.5 0.5 1"/>
    </material>
  </visual>
</link>

<joint name="laser_bracket_link2base_link" type="fixed">
  <parent link="base_link"/>
  <child link="laser_bracket_link"/>
  <origin xyz="0 0 0.11"/>
</joint>

<!-- 雷达 -->
<link name="laser_link">
  <visual>
    <geometry>
      <cylinder length="0.05" radius="0.03"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="red_color">
      <color rgba="1 0 0 1"/>
    </material>
  </visual>
</link>

<joint name="laser_link2laser_bracket_link" type="fixed">
  <parent link="laser_bracket_link"/>
  <child link="laser_link"/>
  <origin xyz="0 0 0.075"/>
</joint>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

雷达基于支架向上移动,所以是支架高度的一半加上雷达高度的一半,0.05 + 0.025 = 0.075

添加完成,重新编译项目运行,显示效果:

# 4 添加摄像头

在底盘上方的前面,添加一个摄像头,使用一个长方体表示。

<!-- 摄像头 -->
<link name="camera_link">
  <visual>
    <geometry>
      <box size="0.02 0.05 0.02"/>
    </geometry>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <material name="green_color">
      <color rgba="0 1 0 1"/>
    </material>
  </visual>
</link>

<joint name="camera_link2base_link" type="fixed">
  <parent link="base_link"/>
  <child link="camera_link"/>
  <origin xyz="0.13 0 0.07"/>
</joint>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

添加完成,重新编译项目运行,显示效果:

最终的 robot_model.urdf 内容如下:

<?xml version="1.0" ?>
<robot name="robot">

  <material name="yellow_color">
    <color rgba="1 1 0 1"/>
  </material>

  <!-- base_footprint -->
  <link name="base_footprint">
    <visual>
      <geometry>
        <sphere radius="0.001"/>
      </geometry>
    </visual>
  </link>

  <!-- 底盘 -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.12" radius="0.14"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="black_color">
        <color rgba="0 0 0 1"/>
      </material>
    </visual>
  </link>

  <joint name="base_link2base_footprint" type="fixed">
    <parent link="base_footprint"/>
    <child link="base_link"/>
    <origin xyz="0 0 0.1"/>
  </joint>

  <!-- 左轮 -->
  <link name="left_wheel">
    <visual>
      <geometry>
        <cylinder length="0.03" radius="0.04"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>

      <material name="yellow_color" />
    </visual>
  </link>
  <joint name="left_wheel2base_link" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 -0.155 -0.06" rpy='1.57 0 0' />
  </joint>

  <!-- 右轮 -->
  <link name="right_wheel">
    <visual>
      <geometry>
        <cylinder length="0.03" radius="0.04"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="yellow_color" />
    </visual>
  </link>
  <joint name="right_wheel2base_link" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel"/>
    <origin xyz="0 0.155 -0.06" rpy='-1.57 0 0' />
  </joint>

  <!-- 前支撑轮,定义为一个圆球 -->
  <link name="front_wheel">
    <visual>
      <geometry>
        <sphere radius="0.02"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="yellow_color" />
    </visual>
  </link>
  <joint name="front_wheel2base_link" type="continuous">
    <parent link="base_link"/>
    <child link="front_wheel"/>
    <origin xyz="0.12 0 -0.08"/>
  </joint>

  <!-- 前支撑轮,定义为一个圆球 -->
  <link name="back_wheel">
    <visual>
      <geometry>
        <sphere radius="0.02"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="yellow_color" />
    </visual>
  </link>
  <joint name="back_wheel2base_link" type="continuous">
    <parent link="base_link"/>
    <child link="back_wheel"/>
    <origin xyz="-0.12 0 -0.08"/>
  </joint>
  
  <!-- 雷达支架 -->
  <link name="laser_bracket_link">
    <visual>
      <geometry>
        <cylinder length="0.1" radius="0.01"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="gray_color">
        <color rgba="0.5 0.5 0.5 1"/>
      </material>
    </visual>
  </link>

  <joint name="laser_bracket_link2base_link" type="fixed">
    <parent link="base_link"/>
    <child link="laser_bracket_link"/>
    <origin xyz="0 0 0.11"/>
  </joint>

  <!-- 雷达 -->
  <link name="laser_link">
    <visual>
      <geometry>
        <cylinder length="0.05" radius="0.03"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="red_color">
        <color rgba="1 0 0 1"/>
      </material>
    </visual>
  </link>

  <joint name="laser_link2laser_bracket_link" type="fixed">
    <parent link="laser_bracket_link"/>
    <child link="laser_link"/>
    <origin xyz="0 0 0.075"/>
  </joint>

  <!-- 摄像头 -->
  <link name="camera_link">
    <visual>
      <geometry>
        <box size="0.02 0.05 0.02"/>
      </geometry>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <material name="green_color">
        <color rgba="0 1 0 1"/>
      </material>
    </visual>
  </link>

  <joint name="camera_link2base_link" type="fixed">
    <parent link="base_link"/>
    <child link="camera_link"/>
    <origin xyz="0.13 0 0.07"/>
  </joint>
</robot>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157