3 · 坐标系与变换
做机器人开发,坐标系就是你的“世界观”。
我个人习惯把URDF里的坐标系想象成一个个小坐标架,每个link都抱着一个。你想想看,如果这些架子之间关系乱了,机器人肯定“精神分裂”。
3.1 机器人坐标系定义
URDF里每个都有一个自己的坐标系,原点在哪儿?方向朝哪?全由你定。但有个潜规则:右手系。X向前,Y向左,Z向上——这是ROS里的“标准姿势”。
我记得刚入行时,有次把Z轴搞反了,结果仿真里机械臂直接往地里钻。嗯,从那以后我每次定义坐标系都会默念一遍“右手定则”。
· 每个link一个坐标系,原点通常设在关节中心或几何中心。
· 坐标系方向尽量与机器人“自然姿态”对齐。
· 世界坐标系(map/odom)一般固定,Z轴向上。
3.2 父子关系 —— 关节就是“脐带”
URDF里
说白了,关节就是描述“儿子在爸爸身上哪儿、怎么转”。
我在项目里见过有人把父子关系搞反,结果整个机器人像提线木偶一样乱晃。记住:父link是“不动”的参考,子link是“动”的那一方。
- 固定关节 (fixed):子坐标系相对父坐标系永远不变。比如相机支架。
- 旋转关节 (revolute):子坐标系绕父坐标系的某个轴旋转。比如肘关节。
- 移动关节 (prismatic):子坐标系沿父坐标系的某个轴平移。比如升降台。
我曾经在写六轴机械臂时,把joint的
<origin>写错了,导致第三轴和第四轴“打架”。后来我养成了一个习惯:每加一个joint,就在Rviz里看一眼TF树,确认父子关系。
3.3 TF树与URDF的关联
TF(Transform)树是ROS里管理所有坐标系关系的“活地图”。URDF定义的是机器人的“静态骨架”,而TF树发布的是实时变换。
你想想看,URDF告诉TF“我的关节长什么样”,TF告诉其他节点“现在每个link在哪儿”。
我经常把TF树比作族谱:base_link是老祖宗,其他link都是子孙。每个joint就是一条“血缘关系”。
3.4 实战:在URDF里定义坐标系变换
看一个最简单的二连杆结构。父link叫base_link,子link叫arm_link,中间一个旋转关节。
<link name="base_link">
<visual>
<geometry><box size="0.2 0.2 0.1"/></geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
</visual>
</link>
<joint name="arm_joint" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/> <!-- 子坐标系在父Z正上方0.1米 -->
<axis xyz="0 0 1"/> <!-- 绕Z轴旋转 -->
</joint>
<link name="arm_link">
<visual>
<geometry><cylinder length="0.3" radius="0.03"/></geometry>
<origin xyz="0 0 0.15" rpy="0 0 0"/> <!-- 圆柱中心在link坐标系Z正半轴 -->
</visual>
</link>
注意<origin>里的xyz和rpy。它们定义了子坐标系相对于父坐标系的偏移和旋转。
我个人习惯把rpy写成“先绕X转,再绕Y,最后绕Z”,也就是ROS里标准的欧拉角顺序。
很多人把
<origin>写在link里和joint里搞混。记住:· link的origin:定义几何体在link坐标系下的位置。
· joint的origin:定义子link坐标系相对于父link坐标系的变换。
我曾经在调试时把这两个写反,结果机器人看起来像“骨折”了一样。
3.5 用TF树验证你的URDF
启动robot_state_publisher后,用tf_echo或view_frames就能看到实时变换。
我每次写完URDF都会运行:
rosrun tf tf_echo base_link arm_link
如果输出的平移和旋转跟你预期一致,那基本稳了。如果不一致……嗯,回去检查<origin>吧。
在Rviz里添加TF显示,把“Frame”选成
base_link,然后观察每个link的坐标轴。如果某个link的坐标轴歪了,那一定是joint的rpy写错了。我靠这招至少省了十几个小时的排查时间。
说到底,坐标系和变换就是URDF的“语法”。你掌握了父子关系和TF树的关联,后面写复杂模型就像搭积木一样自然。