机器人定位结果看起来有系统性偏差时,直觉会先去怀疑标定。但相当一部分“标定不准”的现象,实际来自 TF 树本身错了:某个坐标系根本不存在、父子关系接反了、或者关节值没有真正驱动对应的变换。

这篇文章按“先确认树结构,再确认数值来源”的顺序展开,因为在一个结构错误的 TF 树上做标定,只会把结构问题掩盖得更深。

URDF 描述连杆与关节robot_state_publisher 读取模型订阅 joint_states计算各连杆位姿发布 TF 变换下游按 frame 查询
从模型到变换URDF 只描述结构,真实的关节值来自 joint_states;两者缺一,TF 树都不完整。
link坐标系节点,本身没有运动自由度。joint定义父子 link 与运动类型,是变换的来源。URDF静态结构描述,可用 xacro 生成,不包含运行时数值。joint_states运行时关节值,由驱动或仿真发布。robot_state_publisher把结构加数值算成 TF 变换。frame_id下游查询时使用的名字,必须与树中完全一致。

先确认树本身是否连通

第一个检查不该是标定参数,而是坐标系能不能查到、链路是否完整:

1
2
ros2 run tf2_tools view_frames
ros2 run tf2_ros tf2_echo base_link camera_link

view_frames 会输出一张包含全部坐标系和连接关系的图。如果相机坐标系根本不在这张图上,那么后面的定位误差都无从谈起。如果它在图上但和基座不在同一条链路上,说明中间缺了一个关节或一棵子树。

常见问题包括:关节的 parentchild 写反,导致变换方向相反;模型里有两个 base_link 导致树出现分叉;以及固定关节被误写成可动关节,使静态部分被当作运行时数值。

数值来源必须和模型对齐

URDF 描述的是结构,/joint_states 提供的是数值。两者必须按关节名对齐。名字对不上的关节不会报错,只是保持默认位置,表现为某个部件在 RViz 里静止不动。

1
2
ros2 topic echo /joint_states --once
ros2 param get /robot_state_publisher robot_description | head -c 200

比对时要关注三件事:关节名是否完全一致(包括命名空间前缀)、关节值单位是否为弧度、以及 joint_states 的时间戳是否在推进。时间戳停住的关节值会被 TF 按过期数据处理,查询时得到外推失败或旧值。

固定关节与静态变换

不做运动的坐标系不需要 robot_state_publisher 参与,可以用静态变换发布:

1
2
3
ros2 run tf2_ros static_transform_publisher \
--x 0.1 --y 0 --z 0.3 --roll 0 --pitch 0 --yaw 0 \
--frame-id base_link --child-frame-id lidar_link

这里容易出现重复发布:同一个变换既有 URDF 里的固定关节,又有额外的静态发布者。两个来源的数值不一致时,下游查询到的结果取决于时序,故障表现为偶发跳变。

选择原则很简单:属于机器人模型的固定关节放在 URDF 里,传感器安装外参要么全部放 URDF,要么全部用静态发布,不要混用。

和标定的边界

TF 树正确是标定有意义的前提。如果 base_linkcamera_link 的变换本身就错了,那么手眼标定求出的结果会把这个结构错误吸收进去,表现为“标定参数看起来怪但误差不大”,一旦结构修正就需要重新标定。

因此排查顺序建议是先验证树结构和数值来源,确认静态部分符合设计,再进入标定环节。这两类问题的证据不同:结构问题看树和关节名,标定问题看残差和任务误差。

参考资料

证据边界

本文讨论 TF 树结构与关节值来源的排查顺序,不包含任何具体机器人的模型文件或标定结果。文中命令用于检查结构一致性;“树正确”只能说明坐标系关系符合模型描述,不能证明标定精度或传感器数据正确。