ROS是机器人操作系统,当然要给机器人使用啦,不过在使用之前,还得让ROS认识下我们使用的机器人,如何把一个机器人介绍给ROS呢?
为此,ROS专门提供了一种机器人建模方法——URDF,用来描述机器人外观、性能等各方面属性。
一、URDF语法
1.1 机器人的组成
建模描述机器人的过程中,我们自己需要先熟悉机器人的组成和参数,比如机器人一般是由硬件结构、驱动系统、传感器系统、控制系统四大部分组成,市面上一些常见的机器人,无论是移动机器人还是机械臂,我们都可以按照这四大组成部分进行分解。
其中:
- 硬件结构就是底盘、外壳、电机等实打实可以看到的设备;
- 驱动系统就是可以驱使这些设备正常使用的装置,比如电机的驱动器,电源管理系统等;
- 传感系统包括电机上的编码器、板载的
IMU、安装的摄像头、雷达等等,便于机器人感知自己的状态和外部的环境; - 控制系统就是我们开发过程的主要载体了,一般是树莓派、电脑等计算平台,以及里边的操作系统和应用软件。
机器人建模的过程,其实就是按照类似的思路,通过建模语言,把机器人每一个部分都描述清楚,再组合起来的过程。
1.2 什么是URDF
URDF(Unified Robot Description Format,统一机器人描述格式)是一种XML格式的文件,用于从物理和逻辑上描述一个机器人模型。
你可以把它理解为机器人的数字说明书或三维装配图,它告诉计算机:
- 机器人由哪些零件(连杆、关节)组成;
- 这些零件长什么样(形状、尺寸、颜色);
- 零件之间如何连接(位置、旋转、运动类型);
- 零件的物理属性(质量、惯性);
1.3URDF组成
URDF主要描述两大核心元素:
- 连杆(
Link):代表机器人的刚性部件,如机械臂的每一节臂杆、底盘、轮子、传感器支架等,包括:- 视觉属性:形状(立方体、圆柱体、网格模型)、尺寸、颜色、纹理;
- 碰撞属性:用于物理仿真的简化几何形状;
- 惯性属性:质量、转动惯量(对仿真至关重要)。
- 关节(
Joint):定义连杆之间的连接方式和运动关系,主要类型:- 固定关节(
fixed):完全固定连接; - 旋转关节(
revolute):绕单轴旋转,有角度限制(如机械臂关节); - 连续关节(
continuous):无限旋转(如轮子); - 平移关节(
prismatic):线性滑动; - 平面关节(
planar):在平面内运动; - 浮动关节(
floating):完全自由(6自由度)。
- 固定关节(
机器人描述由一组连杆元素和一组将连杆连接起来的关节元素组成;
因此,典型的机器人描述大致如下所示:
<?xml version="1.0"?> <?xml-model href="https://raw.githubusercontent.com/ros/urdfdom/master/xsd/urdf.xsd" ?> <robot name="pr2" xmlns="http://www.ros.org"> <link> ... </link> <link> ... </link> <link> ... </link> <joint> .... </joint> <joint> .... </joint> <joint> .... </joint> </robot>可以看到,URDF格式的根元素是一个<robot>元素。
1.3.1link元素
link元素描述了一个具有惯性、视觉特征和碰撞属性的刚体。以下是一个link元素的示例:
<link name="my_link"> <inertial> <origin xyz="0 0 0.5" rpy="0 0 0"/> <mass value="1"/> <inertia ixx="100" ixy="0" ixz="0" iyy="100" iyz="0" izz="100" /> </inertial> <visual> <origin xyz="0 0 0" rpy="0 0 0" /> <geometry> <box size="1 1 1" /> </geometry> <material name="Cyan"> <color rgba="0 1.0 1.0 1.0"/> </material> </visual> <collision> <origin xyz="0 0 0" rpy="0 0 0"/> <geometry> <cylinder radius="1" length="0.5"/> </geometry> </collision> </link>link元素有一个属性,即name(必选):用来描述连杆本身的名称.。此外,link包含若干部元素,接下来我们一一介绍。
1.3.1.1 惯性属性inertial
inertial(可选):用于描述连杆的质量、其质心的位置以及其中心惯性属性。
其下可以包含如下元素:
origin(可选):此位姿(平移、旋转)描述了连杆质心坐标系C相对于连杆坐标系L的位置和方向。xyz(可选):表示从Lo(连杆坐标系原点)到Co(连杆质心)的位置向量,形式为x L̂x + y L̂y + z L̂z,其中L̂x、L̂y、L̂z是连杆坐标系L的正交单位向量;rpy(可选):表示C的单位向量Ĉx、Ĉy、Ĉz相对于连杆坐标系L的方向,以欧拉旋转(横滚roll、俯仰pitch、偏航yaw)序列表示,单位为弧度。注意:Ĉx、Ĉy、Ĉz不需要与连杆的惯性主轴对齐;
mass:连杆的质量由该元素的value属性表示;inertia:此连杆关于Co(连杆质心)的惯性矩ixx、iyy、izz和惯性积ixy、ixz、iyz,这些值对应于固定在质心坐标系C中的单位向量Ĉx、Ĉy、Ĉz。注意:Ĉx、Ĉy、Ĉz相对于L̂x、L̂y、L̂z的方向由<origin>标签中的rpy值指定。
1.3.1.2 视觉属性visual
visual(可选):连杆的视觉属性,此元素指定了用于可视化目的的对象形状(长方体、圆柱体等)。
注意:同一个连杆可以存在多个<visual>标签实例。它们定义的几何体的并集形成了该连杆的视觉表示。
其下可以包含如下元素:
origin(可选):视觉元素相对于连杆坐标系的参考坐标系;xyz(可选):分别是x、y、z方向上的平移;py(可选):表示固定轴的横滚、俯仰和偏航角度,单位为弧度;
geometry(必选):表示几何形状,可以是以下之一:<box>size属性包含长方体的三个边长,长方体的原点位于其中心;
<cylinder>- 指定半径和长度。圆柱体的原点位于其中心;
<sphere>- 指定半径。球体的原点位于其中心;
<mesh>- 由文件名指定的三角网格元素,以及一个可选的缩放比例,用于缩放网格的轴对齐边界框。任何几何格式都可以接受,但具体应用程序的兼容性取决于实现。对于最佳纹理和颜色支持,推荐的格式是
Collada .dae文件。引用同一模型的机器之间不会传输网格文件。它必须是本地文件。在文件名前加上package://<packagename>/<path>可以使网格文件的路径相对于包<packagename>。
- 由文件名指定的三角网格元素,以及一个可选的缩放比例,用于缩放网格的轴对齐边界框。任何几何格式都可以接受,但具体应用程序的兼容性取决于实现。对于最佳纹理和颜色支持,推荐的格式是
material(可选):视觉元素的材质,其name属性可以用于指定材质的名称;<color>(可选)rgba由一组四个数字(代表红/绿/蓝/透明度alpha)指定的材质颜色,每个数字的范围为 [0,1];
<texture>(可选)- 材质的纹理由文件名指定。
1.3.1.3 碰撞属性collision
collision用于描述碰撞参数,里边的内容似乎和<visual>一样,也有<geometry>和<origin>,看似相同,其实区别还是比较大的;
origin(可选):碰撞元素相对于连杆坐标系的参考坐标系;xyz(可选):分别是x、y、z方向上的平移;py(可选):表示固定轴的横滚、俯仰和偏航角度,单位为弧度;
geometry:请参见上述视觉元素中的几何描述。
1.3.2joint元素
joint元素描述了关节的运动学和动力学特性,并指定了关节的安全限制。
以下是一个joint元素的示例:
<joint name="my_joint" type="floating"> <origin xyz="0 0 1" rpy="0 0 3.1416"/> <parent link="link1"/> <child link="link2"/> <calibration rising="0.0"/> <dynamics damping="0.0" friction="0.0"/> <limit effort="30" velocity="1.0" lower="-2.2" upper="0.7" /> <safety_controller k_velocity="10" k_position="15" soft_lower_limit="-2.0" soft_upper_limit="0.5" /> </joint>下面图中,蓝色关节表示出一个运动的轴,蓝色关节是可以围绕蓝色的轴旋转的,也就是child这个link是可以围绕joint上下旋转。
joint连接两个link,需要分一个主次关系,主关节是parent link,子关节是child link, 在xml形式的描述中这个两个link是必须存在的。
joint元素有两个属性:
name(必须):指定关节的唯一名称;type(必须):指定关节的类型,类型可以是以下之一;revolute:旋转关节,和continuous类型的区别在于不能无限旋转,而是带有角度限制,比如机械臂的两个连杆,就属于这种运动;continuous:旋转关节,可以围绕单轴无限旋转;比如小车的轮子,就属于这种类型;prismatic:滑动关节,可以沿某一个轴平移,也带有位置的极限,一般直线电机就是这种运动方式;fixed:固定关节,是唯一一种不允许运动的关节,不过使用还是比较频繁的,比如相机这个连杆,安装在机器人上,相对位置是不会变化的,此时使用的连接方式就是fixed;floating:浮动关节,允许进行平移、旋转运动;planar:平面关节,允许在平面正交方向上平移或者旋转;
此外,joint包含若干部元素,接下来我们挑选部分介绍介绍。
1.3.2.1origin
origin(可选):表示从父连杆到子连杆的变换,元素属性有:
xyz(可选):表示x、y、z偏移量,所有位置均以米为单位指定;rpy(可选):表示绕固定轴的旋转:先绕x轴横滚(roll),然后绕y轴俯仰(pitch),最后绕z轴偏航(yaw),所有角度均以弧度为单位指定。
1.3.2.2parent
parent描述父连杆名称,元素属性有:
link:在机器人树结构中作为此连杆父连杆的连杆名称。
1.3.2.3child
child描述子连杆名称,元素属性有:
link:作为子连杆的连杆名称。
1.3.2.4calibration
关节的参考位置,用来校准关节的绝对位置。
1.3.2.5dynamics
描述关节的物理属性,例如阻尼值、物理静摩擦力等,经常在动力学仿真中用到。
1.3.2.6limit
描述运动的一些极限值,包括关节运动的上下限位置、速度限制、力矩限制等。
1.3.2.7mimic
描述该关节与已有关节的关系。
1.3.2.8safety_controller
描述安全控制器参数。保护机器人关节的运动。
二、URDF案例
创建my_learning_urdf的Python版本的功能包;
pi@NanoPC-T6:~/dev_ws$ cd src pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_python my_learning_urdf在包中创建如下文件夹:
urdf:存放机器人模型的URDF或xacro文件;meshes:放置URDF中引用的模型渲染文件;launch:保存相关启动文件;rviz:保存rviz的配置文件。
我们需要修改setup.py文件,添加配置文件:
import os from glob import glob ... data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))), (os.path.join('share', package_name, 'urdf'), glob(os.path.join('urdf', '*.*'))), (os.path.join('share', package_name, 'urdf/sensors'), glob(os.path.join('urdf/sensors', '*.*'))), (os.path.join('share', package_name, 'meshes'), glob(os.path.join('meshes', '*.*'))), (os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.rviz'))), ], ...2.1 模型文件
在urdf下新建文件mbot_base.urdf;
<?xml version="1.0" ?> <robot name="mbot"> <link name="base_link"> <visual> <origin xyz=" 0 0 0" rpy="0 0 0" /> <!-- 机器人中心,坐标系原点 --> <geometry> <cylinder length="0.16" radius="0.20"/> <!-- 直径0.4m,高0.16m的圆柱 --> </geometry> <material name="yellow"> <color rgba="1 0.4 0 1"/> <!-- 橙黄色 --> </material> </visual> </link> <joint name="left_wheel_joint" type="continuous"> <origin xyz="0 0.19 -0.05" rpy="0 0 0"/> <!-- 位置:Y轴+0.19,Z轴-0.05 --> <parent link="base_link"/> <child link="left_wheel_link"/> <axis xyz="0 1 0"/> <!-- 绕Y轴旋转 --> </joint> <link name="left_wheel_link"> <visual> <origin xyz="0 0 0" rpy="1.5707 0 0" /> <!-- 旋转90度(π/2=1.5707) --> <geometry> <cylinder radius="0.06" length = "0.025"/> <!-- 半径0.06m,厚0.025m --> </geometry> <material name="white"> <color rgba="1 1 1 0.9"/> </material> </visual> </link> <joint name="right_wheel_joint" type="continuous"> <origin xyz="0 -0.19 -0.05" rpy="0 0 0"/> <parent link="base_link"/> <child link="right_wheel_link"/> <axis xyz="0 1 0"/> </joint> <link name="right_wheel_link"> <visual> <origin xyz="0 0 0" rpy="1.5707 0 0" /> <geometry> <cylinder radius="0.06" length = "0.025"/> </geometry> <material name="white"> <color rgba="1 1 1 0.9"/> </material> </visual> </link> <joint name="front_caster_joint" type="continuous"> <origin xyz="0.18 0 -0.095" rpy="0 0 0"/> <parent link="base_link"/> <child link="front_caster_link"/> <axis xyz="0 1 0"/> </joint> <link name="front_caster_link"> <visual> <origin xyz="0 0 0" rpy="0 0 0"/> <geometry> <sphere radius="0.015" /> </geometry> <material name="black"> <color rgba="0 0 0 0.95"/> </material> </visual> </link> <joint name="back_caster_joint" type="continuous"> <origin xyz="-0.18 0 -0.095" rpy="0 0 0"/> <parent link="base_link"/> <child link="back_caster_link"/> <axis xyz="0 1 0"/> </joint> <link name="back_caster_link"> <visual> <origin xyz="0 0 0" rpy="0 0 0"/> <geometry> <sphere radius="0.015" /> </geometry> <material name="black"> <color rgba="0 0 0 0.95"/> </material> </visual> </link> </robot>这将会创建一个两轮差分驱动机器人的URDF模型,包含若干个部分。
2.1.11个主体base_link
圆柱体(直径0.4米,高0.16米),橙黄色,机器人中心,坐标系原点。
2.1.22个驱动轮(左右轮)
分别为left_wheel_link、right_wheel_link:
left_wheel_link:- 圆柱体(半径
0.06米,厚度0.025米); - 在基座坐标系中
(0, 0.19, -0.05); - 关节类型:
continuous(连续旋转关节,无限旋转); - 旋转轴:绕
Y轴(0, 1, 0); - 视觉旋转:
rpy="1.5707 0 0"将圆柱旋转90度,使其直立(假设原始圆柱是平放的);
- 圆柱体(半径
right_wheel_link:与左轮对称布置,Y坐标为负;
2.1.32个万向轮(前后脚轮,用于支撑)
分别为front_caster_link、back_caster_link:
front_caster_link:- 形状:小球体(半径
0.015米),模拟万向轮; - 在基座坐标系中前方
(0.18, 0, -0.095); - 颜色:黑色;
- 形状:小球体(半径
back_caster_link:与前万向轮对称布置,X坐标为负(-0.18)。
2.2launch文件
在launch文件夹下创建display.launch.py文件;
from ament_index_python.packages import get_package_share_path 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.parameter_descriptions import ParameterValue def generate_launch_description(): urdf_tutorial_path = get_package_share_path('my_learning_urdf') # 设置默认的URDF文件和RViz配置文件路径 default_model_path = urdf_tutorial_path / 'urdf/mbot_base.urdf' default_rviz_config_path = urdf_tutorial_path / 'rviz/urdf.rviz' # 命令行参数:--gui true/false,控制是否使用GUI界面发布关节状态 gui_arg = DeclareLaunchArgument(name='gui', default_value='false', choices=['true', 'false'], description='Flag to enable joint_state_publisher_gui') # 命令行参数:--model <路径>,指定URDF文件路径 model_arg = DeclareLaunchArgument(name='model', default_value=str(default_model_path), description='Absolute path to robot urdf file') # 命令行参数:--rvizconfig <路径>,指定RViz配置文件 rviz_arg = DeclareLaunchArgument(name='rvizconfig', default_value=str(default_rviz_config_path), description='Absolute path to rviz config file') # 关键:使用xacro命令解析URDF文件(支持参数化、宏等高级特性) robot_description = ParameterValue(Command(['xacro ', LaunchConfiguration('model')]), value_type=str) # robot_state_publisher 节点 robot_state_publisher_node = Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'robot_description': robot_description}] ) # 关节状态发布器(二选一) # 文本版本(无GUI) joint_state_publisher_node = Node( package='joint_state_publisher', executable='joint_state_publisher', condition=UnlessCondition(LaunchConfiguration('gui')) ) # GUI版本(带滑动条控制) joint_state_publisher_gui_node = Node( package='joint_state_publisher_gui', executable='joint_state_publisher_gui', condition=IfCondition(LaunchConfiguration('gui')) ) # rviz2 可视化节点 rviz_node = Node( package='rviz2', executable='rviz2', name='rviz2', output='screen', arguments=['-d', LaunchConfiguration('rvizconfig')], ) return LaunchDescription([ gui_arg, model_arg, rviz_arg, joint_state_publisher_node, joint_state_publisher_gui_node, robot_state_publisher_node, rviz_node ])这个Launch文件主要做三件事:
- 加载机器人
URDF模型(支持xacro格式); - 发布机器人的状态变换(
TF); - 在
rviz2中可视化机器人。
2.2.1 节点
脚本运行会创建以下几个节点:
joint_state_publisher:发布每个joint(除fixed类型)的状态,一个无界面的、基础版的关节状态发布器;joint_state_publisher_gui:发布每个joint(除fixed类型)的状态,可以通过UI界面对joint进行控制;robot_state_publisher:将机器人各个links、joints之间的关系,通过TF的形式,整理成三维姿态信息发布。rviz2:在rviz2中可视化机器人;
joint_state_publisher这是一个官方ROS2包,主要功能:
输入:
读取
URDF中的关节定义;接收用户或程序指定的关节角度;
输出:
发布
/joint_states话题,消息类型为sensor_msgs/msg/JointState;包含所有关节的名称、位置、速度、力等信息。
2.2.2 数据流与节点关系
数据流与节点关系:
用户通过滑动条/GUI或程序 → joint_state_publisher(_gui) ↓ 发布/joint_states话题 robot_state_publisher ↓ 计算并发布TF变换 rviz2 和其他节点 ↓ 接收TF并可视化2.3urdf.rviz
在rviz目录下新建urdf.rviz文件;
Panels: - Class: rviz_common/Displays Name: Displays - Class: rviz_common/Views Name: Views Visualization Manager: Class: "" Displays: - Class: rviz_default_plugins/Grid Name: Grid Value: true - Alpha: 0.8 Class: rviz_default_plugins/RobotModel Description Source: Topic Description Topic: Value: /robot_description Enabled: true Name: RobotModel Value: true - Class: rviz_default_plugins/TF Name: TF Value: true Global Options: Fixed Frame: base_link Frame Rate: 30 Name: root Tools: - Class: rviz_default_plugins/MoveCamera Value: true Views: Current: Class: rviz_default_plugins/Orbit Distance: 1.7 Name: Current View Pitch: 0.33 Value: Orbit (rviz) Yaw: 5.5 Window Geometry: Height: 800 Width: 12002.4 编译运行
编译程序:
pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_urdf pi@NanoPC-T6:~/dev_ws$ source install/setup.sh2.4.1 可视化
启动终端,运行如下命令;
pi@NanoPC-T6:~/dev_ws$ ros2 launch my_learning_urdf display.launch.py很快就可以看到rviz中显示的机器人模型啦,大家可以使用鼠标拖拽观察;
从可视化的效果来看,这个机器人由5个link和4个joint组成。
2.4.2 查看URDF模型结构
我们分析的对不对呢,可以在模型文件的路径下,使用urdf_to_graphviz这个小工具来分析下;
pi@NanoPC-T6:~/dev_ws/src/my_learning_urdf/urdf$ urdf_to_graphviz mbot_base.urdf WARNING: OUTPUT not given. This type of usage is deprecated!Usage: urdf_to_graphviz input.xml [OUTPUT] Will create either $ROBOT_NAME.gv & $ROBOT_NAME.pdf in CWD or OUTPUT.gv & OUTPUT.pdf. Created file mbot.gv Created file mbot.pdf pi@NanoPC-T6:~/dev_ws/src/my_learning_urdf/urdf$ ls mbot_base.urdf mbot.gv mbot.pdf运行成功后会产生一个pdf文件,打开之后就可以看到URDF模型分析的结果啦,是不是和我们的猜测完全相同呢!
参考文章
[1] 古月居ROS2入门教程学习笔记
[2]ROS中阶笔记(二):机器人系统设计—URDF机器人建模
[3]XML Robot Description Format(URDF)