想象一支快递机器人团队:底盘驮着箱子,激光雷达像眼睛长在头顶,摄像头像额头的另一只眼,机械臂像右手。队员报告位置时,个个说方言——雷达说“箱子在我外壳坐标系里前方两米”,摄像头说“箱子在我视网膜坐标系里偏左三十厘米”。调度中心听得一头雾水,因为每个人口中的“前方”根本不是一个方向。此时,队长带着两件宝贝登场:一本覆盖所有队员从属关系的“组织树图”,和一块让每条消息都盖上时间戳的“同步手表”。队长的工作,就是随时把任何一句方言翻译成地图上的标准普通话。这套翻译机制,就是ROS2里的TF2坐标变换库。

岗位关联

对AI从业者而言,TF2是多传感器融合、机器人导航与机械臂控制的空间对齐底座。掌握它,才能让感知模型的输出真正驱动物理世界的行动,是连接算法与硬件的职业分水岭。

目录
  1. 可视化演示
  2. 坐标变换基础
  3. TF2核心架构
  4. 变换发布机制
  5. 坐标查询与运算
  6. TF2 API实践
  7. 问题排查技巧
  8. 工程应用案例
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示TF2坐标变换:TF坐标系树、静态与动态变换原理,展示不同坐标系之间的相对位姿换算与广播机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

TF树 父系 子系 Tchild→parent 坐标系按父子层级广播

坐标变换基础

队长的第一件事,是为小队员们的方言编一本对照字典。机械臂说“爪尖在我前方”,激光雷达说“障碍物在左上方”,其实说的是同一个物理点,只是各自都有一套“以我为原点”的说法。要把方言翻译成普通话,就必须先明确两件事:两个坐标系原点隔了多远(平移),三根轴之间差了多少角度(旋转)。

坐标系,就是刻画“以谁为基准”的三轴刻度尺。TF2里的每个传感器都拥有自己的坐标系,位于传感器安装点;机器人底盘也有底盘坐标系。任何一次坐标变换,本质上都是把某个点在一把尺子下的读数,换算到另一把尺子下的读数。

最优雅的打包方式,是用一个4×4的齐次变换矩阵T,把旋转和平移融成一次矩阵乘法。左上角3×3块是旋转矩阵R,携带三轴夹角信息;右上角3×1块是平移向量t,携带原点偏移;最下面一行固定为\([0\;0\;0\;1]\)。

就这样,队长把每一条方言都压成了一张张“标准小卡片”。拿着卡片,任何两种方言都能互相翻译,队伍里的信息第一次有了共同的文字。

T=[Rt01×31]\displaystyle T = \begin{bmatrix} R & t \\ 0_{1\times3} & 1 \end{bmatrix}
这个矩阵是TF2交换坐标信息的“统一方言卡”,一张卡片同时回答“方向差多少”和“原点差多少”两个问题。
  1. 矩阵 T4×4的齐次变换矩阵,同时容纳旋转与平移,是TF2坐标换算的核心“字典条目”。
    T=[Rt01×31]\displaystyle T = \begin{bmatrix} R & t \\ 0_{1\times3} & 1 \end{bmatrix}
  2. 旋转子块 R3×3旋转矩阵,各列分别表示源坐标系三根轴在目标坐标系中的方向。
    R=[r11r12r13r21r22r23r31r32r33]\displaystyle R = \begin{bmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} & r_{23} \\ r_{31} & r_{32} & r_{33} \end{bmatrix}
  3. 平移子块 t3×1列向量,表示源坐标系原点在目标坐标系下的偏移。
    t=[txtytz]T\displaystyle t = \begin{bmatrix} t_x & t_y & t_z \end{bmatrix}^{\mathsf{T}}
  4. 齐次行最后一行固定为全0加末尾1,使矩阵乘法能统一表达“旋转+平移”。
    [0001]\displaystyle \begin{bmatrix} 0 & 0 & 0 & 1 \end{bmatrix}

TF2核心架构

光有字典还不够。队长发现,队员之间有明确的上下级关系:激光雷达装在底盘上,底盘跟着里程计走,里程计又相对地图世界定位。这种隶属关系若画成图,就是一棵倒挂的“组织树”——TF树。

TF树是TF2的骨架。每个节点代表一个坐标系,每条边代表父坐标系到子坐标系的变换。任一节点都可以沿着树上的边一步步走到另一个节点,串联起来的路径就是“变换链”。两帧之间没有直接边也没关系,只要路径存在,就能算出整体变换。

假设要从坐标系A变到坐标系C,树上没有直接边,但有A→B→C这条路。TF2不是一步到位,而是先把A→B的变换与B→C的变换相乘,得到A到C的整体变换。注意顺序不能颠倒,矩阵乘法不满足交换律,就像“先向右转再走一步”和“先走一步再向右转”的落点完全不同。

队长拿着组织树,谁挂在谁下面一目了然;任何两句话,只要沿树爬一遍楼梯,就能翻成同一种方言。这就是TF树赋予TF2的“全局视野”。

TA→C=TB→C⋅TA→B\displaystyle T_{A \to C} = T_{B \to C} \cdot T_{A \to B}
这条公式是TF树路径查找的数学本质:两次相邻变换串联,得到跨越多帧的整体变换。
  1. 管道路径A到C必须先经过中间帧B,这一步由TF树的路径查找自动完成。
    A→B→C\displaystyle A \to B \to C
  2. 前段变换\(T_{A \to B}\)表示把A坐标系中的点换算到B坐标系中,它是本段的“翻译卡”。
    TA→B∈R4×4\displaystyle T_{A \to B} \in \mathbb{R}^{4 \times 4}
  3. 后段变换\(T_{B \to C}\)表示把B中的点继续换算到C中。
    TB→C∈R4×4\displaystyle T_{B \to C} \in \mathbb{R}^{4 \times 4}
  4. 合成顺序整体变换等于后段乘以前段,先作用A→B,再作用B→C。
    TA→C=TB→C⋅TA→B\displaystyle T_{A \to C} = T_{B \to C} \cdot T_{A \to B}

变换发布机制

组织树不是画一次就永久有效。队长要求队员不断汇报自己的位置:底盘每动一下、雷达每转一下,都要喊一句“我现在在哪”。这种持续汇报,就是“发布变换”。

TF2把汇报分成两类:静态变换与动态变换。激光雷达相对底盘的安装位置固定,属于静态变换,用StaticTransformBroadcaster在启动时发布一次,之后大家共享同一张“座位表”;底盘相对里程计的位置时时变化,属于动态变换,要用TransformBroadcaster按固定频率持续广播。频率太低跟不上运动,太高则白白消耗CPU。

每条被发布的消息都是TransformStamped,包含父子帧名、时间戳和4×4矩阵。当需要把某个传感器坐标系里的点换算到目标坐标系时,调度逻辑沿树查找路径,先旋转对齐方向,再平移对齐原点。

每一次广播,都像队员扯着嗓子喊一句带时间戳的方言;队长细心存档,为后续查询准备好了完整的“语音档案”。

ptarget=R⋅psource+t\displaystyle p_{\text{target}} = R \cdot p_{\text{source}} + t
一次标准的坐标换算:先让方向一致,再把原点挪到同一处,源点就变成了目标坐标系下的坐标。
  1. 源点传感器坐标系中读到的原始坐标,比如雷达报告“前方2米处有障碍”。
    psource=[xsyszs]T\displaystyle p_{\text{source}} = \begin{bmatrix} x_s & y_s & z_s \end{bmatrix}^{\mathsf{T}}
  2. 旋转对齐用旋转矩阵把源点的方向转成目标坐标系的朝向。
    R⋅psource\displaystyle R \cdot p_{\text{source}}
  3. 平移对齐加上两坐标系原点的偏移,使源点落在目标坐标系的标准视角下。
    +  t\displaystyle + \; t
  4. 合成目标点最终得到目标坐标系下的坐标,可直接用于后续规划与控制。
    ptarget=R⋅psource+t\displaystyle p_{\text{target}} = R \cdot p_{\text{source}} + t

坐标查询与运算

当有人问“苹果在哪个房间”时,队长要当场翻档案。他不能临时去挨个问队员,那样太慢。他早已把每一句汇报按时间顺序存进“广播记录本”,也就是变换缓存。

TF2中有一个专门的坐标缓冲器Buffer,维护着TF树最近的完整历史。调用lookupTransform(target, source, time)时,先按帧名找到树上的路径,再从缓存中取出对应时刻的变换。若查询时刻刚好落在两次相邻广播之间,缓存里没有整点记录,TF2并不会直接报错,而是用时间插值估算一个“当下”的变换。

假设上次广播在\(t_a\)时刻,下次广播在\(t_b\)时刻,查询时刻\(t_{\text{query}}\)落在两者之间。TF2先计算查询时刻在区间内的归一化比例\(\alpha\),再用\(\alpha\)对两帧变换做线性插值,得到最接近当下的结果。

队长翻档案翻得飞快,即便记录卡带也能用插值算出“最接近当下”的答案,绝不空手而归。正是这种机制,让高速运动的机器人也能实时回答“此刻在哪里”。

α=tquery−tatb−ta,Tinterp=Ta+α (Tb−Ta)\displaystyle \alpha = \frac{t_{\text{query}} - t_a}{t_b - t_a}, \quad T_{\text{interp}} = T_a + \alpha\,(T_b - T_a)
时间插值让TF2在缓存没有“整点记录”时仍能给出平滑结果,是实时系统的关键容错机制。
  1. 距上次广播的时长查询时刻减去上一帧时刻,反映“已经过了多久”。
    tquery−ta\displaystyle t_{\text{query}} - t_a
  2. 归一化比例除以上下两帧的间隔,得到0到1之间的权重\(\alpha\)。
    α=tquery−tatb−ta\displaystyle \alpha = \frac{t_{\text{query}} - t_a}{t_b - t_a}
  3. 线性插值用\(\alpha\)在相邻两帧变换之间折中,得到查询时刻的变换估计。
    Tinterp=Ta+α (Tb−Ta)\displaystyle T_{\text{interp}} = T_a + \alpha\,(T_b - T_a)

TF2 API实践

思路理清了,队长开始把规矩写成管理手册。在ROS2程序中,这份手册就是tf2_ros包提供的三个角色:广播员、旁听员、调度员。

广播员对应TransformBroadcaster,负责把TransformStamped消息发到/tf话题,相当于队员喊话;旁听员对应TransformListener,订阅/tf并写入Buffer,相当于队长记档;调度员是Buffer自带的查询与变换接口,如transform和lookupTransform,相当于队长翻本子答话。

工程中最常见的场景,是把激光雷达点云从雷达坐标系变换到地图坐标系。实现上先查雷达→底盘、底盘→里程计、里程计→地图这条完整链条,再把每一点依次乘上合成矩阵。代码看起来是几行API调用,背后却是整棵TF树的协同工作。

手册写清楚后,队长再也不用每次亲自出马,任何新同事都能按API照章办事。这正是TF2的工程价值:把复杂的空间逻辑封装成简洁的调用接口。

pmap=Tmapodom⋅Todombase⋅Tbaselaser⋅plaser\displaystyle p_{\text{map}} = T_{\text{map}}^{\text{odom}} \cdot T_{\text{odom}}^{\text{base}} \cdot T_{\text{base}}^{\text{laser}} \cdot p_{\text{laser}}
这就是点云变换的完整链条:从传感器帧逐级上升到公共世界帧,每一步都是查表得到的小变换矩阵。
  1. 雷达原始点激光雷达坐标系下读到的障碍物坐标,是整条链的起点。
    plaser\displaystyle p_{\text{laser}}
  2. 提升到底盘帧使用雷达相对底盘的安装矩阵,把点从雷达视角转到底盘视角。
    Tbaselaser⋅plaser\displaystyle T_{\text{base}}^{\text{laser}} \cdot p_{\text{laser}}
  3. 经过里程计帧继续乘上底盘相对里程计、里程计相对地图的变换,完成逐级上行。
    Tmapodom⋅Todombase\displaystyle T_{\text{map}}^{\text{odom}} \cdot T_{\text{odom}}^{\text{base}}
  4. 地图坐标结果最终得到地图坐标系下的坐标,供全局规划与定位使用。
    pmap\displaystyle p_{\text{map}}

问题排查技巧

再好的队长也会遇到翻车:有的队员广播串台,有人手表走偏,有人报的是另一个同名队员的话。TF2的报错信息,就是队长查案的线索。

最常见的三个翻车现场:一是报错“Could not find transform“,表示TF树上两帧间找不到通路,多半是帧名拼错或广播没启动;二是”lookup would require extrapolation”,表示查询时刻超出了缓存时间范围;三是帧重复定义,两个广播器声明了同名字,却挂到不同的父节点下。

排查时先分清“错在哪一环”。用tf2_tools回放TF树可快速看到帧的父子关系;对时间类错误,要检查广播频率与查询时间戳。工程上常给查询时刻加一个容差判断,允许±0.1秒的时间偏差作为兜底,避免因微小抖动频繁报错。

队长靠这套“对表+查树”的功夫,把每次翻车都变成教学案例。排查技巧的本质不是背报错,而是理解TF树的拓扑结构与时间戳语义。

∣treq−tavail∣<ε\displaystyle |t_{\text{req}} - t_{\text{avail}}| < \varepsilon
时间容差公式:只要请求时刻与缓存可用时刻相差足够小,就接受本次查询,防止微小时间偏差导致整条链路崩溃。
  1. 时间差值先算出请求时刻与可用时刻的差,这是判断偏离程度的原始依据。
    treq−tavail\displaystyle t_{\text{req}} - t_{\text{avail}}
  2. 取绝对值距离只关心大小、不关心方向,所以取绝对值。
    ∣treq−tavail∣\displaystyle | t_{\text{req}} - t_{\text{avail}} |
  3. 与容差比较\(\varepsilon\)是预先设定的容差阈值,差值小于阈值就视为可用。
    ε>0\displaystyle \varepsilon > 0

工程应用案例

终于,整个团队拿着同一张地图干活了。激光雷达看路、摄像头看物、机械臂抓取,三种方言在队长的翻译下汇成一句话:“目标就在那里。”

工程落地上有三个典型场景:多传感器融合——把雷达点云变换到车体坐标,与相机图像对齐;机器人导航——把里程计odom帧里的位姿变换到map帧,供全局路径规划;机械臂控制——把视觉识别出的目标位姿从相机帧变换到基座帧,供逆运动学求解。

多传感器融合中,每个传感器的估计都带噪声。TF2先把各传感器观测对齐到公共坐标系,再对同一目标的多份观测做加权融合,噪声越小的传感器权重越大,最终得到更稳的目标位置。这里用到的正是两个观测方差的加权公式。

一个翻译官、一棵组织树、一块同步手表,让机器人们各司其职,在同一张地图上默契协作。这就是TF2在工程世界里的最终姿态:让不同来源的空间信息,在同一个坐标系下和谐共处。

xfused=σ22σ12+σ22 x1+σ12σ12+σ22 x2\displaystyle x_{\text{fused}} = \frac{\sigma_2^2}{\sigma_1^2 + \sigma_2^2}\, x_1 + \frac{\sigma_1^2}{\sigma_1^2 + \sigma_2^2}\, x_2
融合权重与噪声成反比:越可靠的传感器,对最终估计的贡献越大,这是多传感器融合的基本思想。
  1. 两份观测\(x_1\)与\(x_2\)分别来自两个传感器对同一目标的坐标估计,\(\sigma\)表示各自噪声方差。
    x1,  σ1;x2,  σ2\displaystyle x_1,\; \sigma_1; \quad x_2,\; \sigma_2
  2. 权重计算每个传感器的权重等于对方方差的占比,方差越小的传感器获得越大权重。
    σ22σ12+σ22,σ12σ12+σ22\displaystyle \frac{\sigma_2^2}{\sigma_1^2 + \sigma_2^2}, \quad \frac{\sigma_1^2}{\sigma_1^2 + \sigma_2^2}
  3. 加权求和把两份观测按权重合成,得到比单一传感器更稳定的融合结果。
    xfused\displaystyle x_{\text{fused}}

AI 应用场景

多传感器融合的自动驾驶感知

阶段:感知数据预处理

解决的问题:传统做法为每个传感器单独标定、单独建图,面对同一路面目标,雷达说“在右前2米”,相机说“在图像中心偏左”,无法直接融合。TF2将各传感器点云与图像变换到统一车体坐标或世界坐标后,AI模型才能把多路观测当作同一特征输入。

具体案例:某无人配送车将16线激光雷达点云与双目相机图像配准。系统先启动静态变换,广播雷达与相机的安装关系,再通过lookupTransform获取变换矩阵,把点云映射到相机坐标系,与图像对齐后送入BEV感知神经网络。

机械臂视觉抓取

阶段:推理结果空间对齐

解决的问题:视觉模型输出的“目标在视野中”是像素坐标,机械臂控制器需要的是基座坐标系下的三维位置。TF2将相机坐标系中的目标位姿,经相机→末端→基座的变换链换算为基座坐标,供逆运动学求解。

具体案例:分拣机械臂安装Eye-in-Hand相机,识别出箱内零件后,调用TransformListener查询相机到基座的变换,再把识别出的抓取点三维坐标变换到基座坐标系,驱动机械臂完成抓取。

移动机器人导航定位

阶段:地图与位姿对齐

解决的问题:导航系统同时维护map与odom两个坐标系,传统导航常因里程计漂移导致路径推算错误。TF2用TF树把odom挂在map下,融合GPS、IMU、激光定位的输出,实时修正两个坐标系之间的偏移,保证全局路径在map中正确执行。

具体案例:仓储AGV在货架间穿行时,定位算法实时发布map→odom的校正变换;导航模块在map坐标系规划路径,控制模块在odom坐标系跟踪期望速度,两者通过TF2无缝衔接。

Python 演示

python71 行
import mathimport numpy as np def compose(*mats):    result = np.eye(4)    for m in mats:        result = result @ m    return result class Frame:    """TF树上的一个坐标系节点。t 表示本帧相对父帧的 4x4 变换矩阵。"""    def __init__(self, name, parent, x=0.0, y=0.0, yaw=0.0):        self.name = name        self.parent = parent        c, s = math.cos(yaw), math.sin(yaw)        self.t = np.array([            [c, -s, 0.0, x],            [s, c, 0.0, y],            [0.0, 0.0, 1.0, 0.0],            [0.0, 0.0, 0.0, 1.0],        ]) class TFBuffer:    """迷你版 TF2 Buffer:保存帧关系,并提供 lookup_transform。"""    def __init__(self):        self.frames = {}     def add(self, frame):        self.frames[frame.name] = frame     def _chain(self, name):        chain = []        while name is not None:            chain.append(name)            frame = self.frames.get(name)            name = frame.parent if frame else None        return chain     def _path_mats(self, start, common):        mats = []        name = start        while name != common:            frame = self.frames[name]            mats.append(frame.t)            name = frame.parent        mats.reverse()        return mats     def lookup_transform(self, target, source):        """返回把 source 坐标系下点变换到 target 坐标系的 4x4 矩阵。"""        s_chain = self._chain(source)        t_chain = self._chain(target)        common = next(name for name in s_chain if name in t_chain)        source_mats = self._path_mats(source, common)        target_mats = self._path_mats(target, common)        combined = [np.linalg.inv(m) for m in target_mats] + source_mats        return compose(*combined) buffer = TFBuffer()buffer.add(Frame("map", None))buffer.add(Frame("odom", "map", x=0.0, y=0.0, yaw=0.1))buffer.add(Frame("base_footprint", "odom", x=1.0, y=0.5, yaw=0.3))buffer.add(Frame("laser", "base_footprint", x=0.2, y=0.0, yaw=0.0)) T = buffer.lookup_transform("map", "laser")p_laser = np.array([2.0, 1.0, 0.0, 1.0])p_map = T @ p_laser print("map -> laser 的变换矩阵:")print(np.round(T, 4))print("激光点在 map 下的坐标:", np.round(p_map[:2], 4))
目的:用纯NumPy实现一个迷你版TF2缓冲器,模拟ROS2中lookup_transform的树路径查找与矩阵合成逻辑,直观展示坐标变换链的数学本质。
  1. buffer.add(Frame("odom", "map", yaw=0.1))注册坐标帧,等价于TF2里广播一条静态变换。
  2. def _chain(self, name)沿父链上溯到根,得到“坐标家谱”,与TF树的帧查询一致。
  3. common = next(name for name in s_chain if name in t_chain)查找两帧的最近公共祖先,对应TF树路径查找。
  4. combined = [np.linalg.inv(m) for m in target_mats] + source_mats目标侧走逆变换,源侧走顺变换,最终合成总矩阵。
  5. p_map = T @ p_laser一次矩阵乘法完成点云坐标变换,对应TF2中的transform接口。

常见误区

误区我觉得静态变换和动态变换没区别,都只要在程序启动时广播一次。反正机器人不动的时候数据也能对上,何必要反复发?

正解静态变换适合“安装关系固定”的场景,比如雷达装在底盘上,发布一次确实够用。但动态变换描述的是“运动中的关系”,比如底盘相对里程计的位置,它每时每刻都在变,必须持续广播。用比喻说:胸牌上的名字报一次就行,但人满场跑的时候,位置就得不停喊。

为何常见:初学者写demo时机器人静止不动,单次发布也能跑通,于是把“碰巧能跑”误当成“机制正确”。

误区调用lookupTransform时,我总把target和source传反,结果算出的坐标怎么都不对,还以为是缓存或时间戳的问题。

正解第一参数是目标帧,第二参数是源帧。意思是“把源帧坐标系下的点,变换到目标帧坐标系下”。就像队长答话要明确“把谁的方言翻译成谁的话”,方向反了,整句意思就反了。

为何常见:很多API习惯先写source再写target,大脑的惯性让初学者不自觉地沿用错误的顺序。

误区我先旋转90度再平移1米,和先平移1米再旋转90度,结果应该一样吧?矩阵相乘不就是可以换顺序吗?

正解矩阵乘法不满足交换律。想象你站在路口:先向右转再迈一步,和先迈一步再向右转,落点完全不同。TF2的链式变换必须严格按树路径的方向顺序连接,顺序颠倒就会翻车。

为何常见:从小学的标量乘法里,人们习惯了“乘法可交换”,把直觉直接迁移到矩阵世界。

误区遇到报错"Could not find transform"时,我第一反应是把缓存时长调大,以为存得越多就越不会报错。

正解这个报错通常说明帧路径断裂或广播根本还没启动,比如帧名拼错、父帧没创建、广播节点没运行。先查TF树的连通性和帧名拼写,再看查询时刻是否超范围,最后才考虑调整缓存参数。

为何常见:报错文本容易让人联想“数据没存够”,但TF2的缓存是滑动窗口机制,容量问题往往不是首要原因。

面试题

Q1. TF2中“静态变换”最适合应用于哪种场景?

单选题 · easy

  • A.激光雷达与车体的固定安装关系← 正确答案
  • B.底盘在里程计中不断变化的位姿
  • C.机械臂抓取时末端的实时姿态
  • D.摄像头检测到目标的瞬时坐标

答案:A。静态变换描述“安装关系永远不变”的坐标系对,如激光雷达固定在车体上,发布一次即可。B、C、D描述的位姿都随时间变化,必须用动态变换持续广播。

Q2. 齐次变换矩阵的维度是多少?

单选题 · easy

  • A.2×2矩阵
  • B.3×3矩阵
  • C.4×4矩阵← 正确答案
  • D.1×4向量

答案:C。齐次变换矩阵把3×3旋转块、3×1平移向量和齐次行组合成4×4矩阵,4阶方阵才能统一表达“旋转+平移”,所以正确答案是C。

Q3. 关于TF树结构,下列描述正确的是?

单选题 · mid

  • A.每个坐标有唯一父节点的单根树← 正确答案
  • B.允许多个根节点共享相同的子帧
  • C.父子关系可以随意交叉的有向图
  • D.所有节点彼此独立的散点集合

答案:A。TF2将坐标系组织成单根树,每个子帧有且仅有一个父帧,全局只有唯一的根节点。B允许子帧有多个父会破坏树结构,C是图而非树,D则完全失去坐标关联。

Q4. ROS2中发布动态变换应使用哪个类?

单选题 · mid

  • A.StaticTransformBroadcaster
  • B.TransformBroadcaster← 正确答案
  • C.FrameListener
  • D.TransformBuffer

答案:B。TransformBroadcaster负责按频率广播动态变换;StaticTransformBroadcaster只用于静态变换;FrameListener与TransformBuffer是监听与缓存角色,不负责发布。

Q5. 查询时刻落在两条相邻广播之间时,TF2如何应对?

单选题 · mid

  • A.直接报错并丢弃该查询
  • B.对两帧变换做时间插值估算← 正确答案
  • C.永远返回最近一帧的数据
  • D.强制等待新数据到达再响应

答案:B。TF2的Buffer会计算查询时刻在相邻快照间的比例α,用线性插值估算“当下”变换。报错只会在查询时刻超出缓存范围时出现,而不是落在两条记录之间。

Q6. 下列哪项是TF树出现“帧重复定义”的典型表现?

单选题 · hard

  • A.两个广播器声明同名帧,产生两个父节点← 正确答案
  • B.缓存中完全没有该帧的任何变换
  • C.查询结果出现较长的固定时间延迟
  • D.静态变换在系统重启后自动消失

答案:A。帧重复意味着同一坐标名被多个广播器以不同父帧发布,TF树出现分支竞争。B是帧不存在,C是时间同步问题,D是静态变换丢失,都与重复定义无关。

Q7. 调用lookupTransform(target, source, time)的参数含义是?

单选题 · easy

  • A.先把源帧点变换到目标帧,time为查询时刻← 正确答案
  • B.先把目标帧点变换到源帧,time为查询时刻
  • C.两个帧参数顺序对结果完全没有影响
  • D.必须先传时间参数,再传两个帧名字

答案:A。lookupTransform把源帧坐标系下的点变换到目标帧坐标系,time决定查询哪一历史时刻。B方向反了,C忽略了方向性,D混淆了参数顺序。

Q8. 下列哪些属于TF2缓存查询常见的异常原因?

多选题 · mid

  • A.两帧之间不存在完整通路← 正确答案
  • B.查询时间超出了缓存时间范围← 正确答案
  • C.目标帧的坐标数值特别巨大
  • D.同名帧被多个广播器同时发布← 正确答案

答案:ABD。通路断裂会导致找不到变换;时间超范围会触发extrapolation异常;同名帧多发布会破坏树结构。坐标数值巨大不会导致查询失败,只是数值问题。

Q9. 关于齐次变换矩阵描述正确的是?

多选题 · hard

  • A.旋转块和平移块封装成一个4×4矩阵← 正确答案
  • B.矩阵乘法满足交换律,顺序可以随意调换
  • C.求逆矩阵可得到反方向的变换关系← 正确答案
  • D.最后一行固定为齐次的0 0 0 1← 正确答案

答案:ACD。齐次矩阵将旋转和平移融合为单次矩阵运算;求逆可得到反向变换;齐次行固定为[0 0 0 1]。B错误,矩阵乘法不满足交换律。

Q10. 把激光雷达点云变换到地图坐标系,正确的变换链是?

多选题 · extreme

  • A.p_map = T_laser_map × T_base_laser × p_laser
  • B.p_map = T_map_odom × T_odom_base × T_base_laser × p_laser← 正确答案
  • C.只要最终帧名对,变换顺序不影响结果
  • D.所有变换必须用四元数而非矩阵表达

答案:B。正确链必须沿TF树从laser逐级上行到map,顺序为laser→base→odom→map。A中的T_laser_map不存在或方向反了;C明显错误,矩阵不满足交换律;D错误,矩阵与四元数可相互转换。