第3章 机器人模型URDF:从结构到可视化
各位同学,欢迎来到第三章。前两章我们把MoveIt的安装和环境搭好了,算是有了「厨房」和「灶台」。这一章,咱们得开始准备「食材」了——也就是机器人模型。
URDF,全称Unified Robot Description Format,统一机器人描述格式。说白了,它就是一套用XML写成的「机器人简历」。简历里写什么?写你机器人身上有几块骨头(link),关节怎么转(joint),长什么样,多重。MoveIt拿到这份简历,才知道怎么去规划你的机器人运动。
核心要点:URDF是MoveIt一切工作的基础。模型不对,后面所有调参都是白费功夫。我见过太多人花几周调参数,最后发现是URDF里一个joint的轴方向写反了——嗯,那种感觉,就像你追着一只猫跑了半天,结果发现它只是你的影子。
3.1 URDF文件结构:骨架与血肉
一个URDF文件,本质上就是一个XML树。根节点叫<robot>,里面包着两类核心元素:<link>和<joint>。
你可以这么理解:link是骨头,joint是关节。骨头不能动,关节负责让骨头之间产生相对运动。没有joint的link就是一块死肉,没有link的joint就是空中楼阁。
来看一个最简单的URDF骨架:
<robot name="my_robot">
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.1"/>
</geometry>
</visual>
</link>
<joint name="base_to_arm" type="fixed">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.05" rpy="0 0 0"/>
</joint>
<link name="arm_link">
<visual>
<geometry>
<cylinder length="0.3" radius="0.02"/>
</geometry>
</visual>
</link>
</robot>
这段代码描述了一个底盘(base_link)上立着一根柱子(arm_link)。注意joint里的parent和child——URDF里所有关节都是有方向的,从父link指向子link。这个方向决定了坐标变换的传递链。
我的习惯:命名link和joint时,尽量用有意义的英文。比如shoulder_pan_joint、elbow_flex_joint。别用j1、j2这种,调试的时候你会疯掉的。我在一个项目里接手过别人的URDF,所有joint叫joint_1到joint_12——嗯,那感觉就像读一本没有目录的书。
3.2 link的定义:不只是几何形状
一个完整的link定义,包含三个部分:
- visual:视觉模型,用来显示。你可以用box、cylinder、sphere,或者导入mesh文件(比如STL、DAE)。
- collision:碰撞模型,用来做碰撞检测。通常比visual模型更简化,用基本几何体代替复杂mesh,能大幅提升计算速度。
- inertial:惯性参数,包括质量、惯性矩阵。这个对动力学仿真至关重要。
来看一个带完整信息的link:
<link name="forearm_link">
<visual>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<geometry>
<mesh filename="package://my_robot/meshes/forearm.stl"/>
</geometry>
<material name="blue">
<color rgba="0 0 1 1"/>
</material>
</visual>
<collision>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<geometry>
<cylinder length="0.3" radius="0.04"/>
</geometry>
</collision>
<inertial>
<mass value="1.5"/>
<inertia ixx="0.01" ixy="0" ixz="0"
iyy="0.01" iyz="0"
izz="0.005"/>
</inertial>
</link>
避坑指南:我曾经在一个六轴机器人项目里,collision模型直接用了visual的精细STL文件。结果MoveIt做运动规划时,每算一步都要做几万次三角形碰撞检测,规划时间从0.5秒飙到了15秒。后来我把collision全换成圆柱体和长方体,速度直接回到0.3秒。记住:collision模型越简单越好。
3.3 joint的定义:运动的灵魂
joint定义了link之间的运动关系。URDF支持六种关节类型:
| 类型 | 说明 | 自由度 |
|---|---|---|
| fixed | 固定关节,两个link之间没有相对运动 | 0 |
| revolute | 旋转关节,绕一个轴旋转,有角度限制 | 1 |
| continuous | 连续旋转关节,没有角度限制(比如轮子) | 1 |
| prismatic | 平移关节,沿一个轴直线运动 | 1 |
| planar | 平面关节,在平面内运动 | 2 |
| floating | 浮动关节,6自由度完全自由 | 6 |
实际机器人上,最常用的是revolute和continuous。来看一个典型的旋转关节:
<joint name="shoulder_pan_joint" type="revolute">
<parent link="base_link"/>
<child link="shoulder_link"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="100" velocity="1.5"/>
<dynamics damping="0.1" friction="0.01"/>
</joint>
这里有几个关键点:
- origin:关节原点在父link坐标系中的位置和姿态。rpy是绕固定轴的旋转(roll, pitch, yaw)。
- axis:旋转轴在父link坐标系中的方向向量。对于revolute关节,这个必须归一化。
- limit:关节限位。lower和upper是弧度制,effort是最大力矩,velocity是最大速度。
- dynamics:动力学参数,阻尼和摩擦系数。这个在仿真中影响很大。
我建议:写joint的时候,origin的rpy尽量用0。除非你明确知道自己在做什么。我见过有人把rpy写成1.57 0 0,结果子link直接歪了90度,整个机器人姿态看起来像喝醉了酒。调试了半天才发现是这里的问题。
3.4 在Rviz中可视化模型:眼见为实
URDF写完了,怎么知道对不对?别靠猜,用Rviz看。
首先,你需要一个launch文件来启动Rviz并加载你的URDF。最简单的方式:
<launch>
<param name="robot_description"
textfile="$(find my_robot)/urdf/my_robot.urdf"/>
<node name="robot_state_publisher"
pkg="robot_state_publisher"
type="robot_state_publisher"/>
<node name="rviz" pkg="rviz" type="rviz"
args="-d $(find my_robot)/config/display.rviz"/>
</launch>
这里做了三件事:
- 把URDF文件内容加载到参数服务器,参数名叫
robot_description。 - 启动
robot_state_publisher节点,它会根据URDF和关节状态发布TF变换。 - 启动Rviz,加载一个预先配置好的显示配置文件。
在Rviz中,你需要添加一个RobotModel显示插件。它会自动读取robot_description参数,把机器人模型渲染出来。你还可以添加TF显示,看看各个link的坐标系是否在正确的位置。
调试技巧:如果Rviz里模型显示不出来,先检查终端有没有报错。最常见的错误是:URDF里引用的mesh文件路径不对。ROS里用package://开头,比如package://my_robot/meshes/arm.stl。如果路径写成了路径或者相对路径,Rviz是找不到的。
3.5 知识体系结构图
下面这张图,帮你理清URDF的核心逻辑:
这张图把URDF的核心逻辑拆成了三块:link定义机器人的「有什么」,joint定义「怎么动」,最后用Rviz来验证「对不对」。三者缺一不可。
3.6 实战中的常见问题
最后,分享几个我在项目中踩过的坑:
- 坐标系混乱:URDF里所有坐标都是相对于父link的。写
origin时,一定要搞清楚当前是在哪个坐标系下。我习惯在URDF文件头部加注释,标明每个link的父级关系。 - 关节轴方向错误:
axis向量必须归一化。如果你写xyz="0 0 2",ROS不会报错,但关节运动会变得很奇怪。我曾经因为这个,让一个六轴机器人的末端执行器画出了一个完美的螺旋线——可惜不是我想要的效果。 - 惯性参数缺失:MoveIt的动力学规划需要惯性参数。如果你不写
<inertial>,默认质量是0,会导致仿真崩溃。我的做法是:先用估算值填上,等实际测量后再更新。 - mesh路径问题:用
package://而不是file://或路径。这样你的URDF才能在不同机器上复用。
再强调一次:URDF是MoveIt的基石。花时间把URDF写对、调好,后面所有工作都会顺利很多。别急着跳到运动规划,先把模型在Rviz里转几圈,确认每个关节的运动方向、限位都正确。这一步省下来的时间,会在后面十倍百倍地还给你。