ROS中创建tf的两种方法对比

第一种方法:来源于 http://wiki.ros.org/urdf/Tutorials/Using urdf with robot_state_publisher
cpp源码如下:(相比较于官方的源码,删去了sensor_msgs::JointState部分,只考虑tf的创建与发布)

int main(int argc, char** argv) {
    ros::init(argc, argv, "state_publisher");
    ros::NodeHandle n;
    tf::TransformBroadcaster broadcaster;

    const double degree = M_PI/120;
    double angle = 0;

    // message declarations
/*    This expresses a transform from coordinate frame header.frame_id 
                               to the coordinate frame child_frame_id*/
    geometry_msgs::TransformStamped odom_trans;
    odom_trans.header.frame_id = "odom";
    odom_trans.child_frame_id  = "box";

    ros::Rate loop_rate(30);
    while (ros::ok()) {
        // update transform
        // (moving in a circle with radius=2)
        odom_trans.header.stamp = ros::Time::now();
        odom_trans.transform.translation.x = cos(angle)*2;  //公转半径
        odom_trans.transform.translation.y = sin(angle)*2;  //公转半径
        odom_trans.transform.translation.z = 0;
        odom_trans.transform.rotation = tf::createQuaternionMsgFromYaw(angle+M_PI/2);

        //send the joint state and transform
        broadcaster.sendTransform(odom_trans);

        angle += d
<think>我们使用ROS1的tf库来实现。需要创建一个节点,该节点监听从map到base_link的变换,然后根据已知的base_link到camera_link的静态变换(假设已经发布),计算map到camera_link的变换并发布。 步骤: 1. 导入必要的ROStf库。 2. 创建一个tf.TransformListener来获取变换。 3. 创建一个tf.TransformBroadcaster来发布变换。 4. 在一个循环中,尝试获取最新的map到base_link的变换。 5. 已知base_link到camera_link的变换(固定值,由静态tf发布,但我们需要在代码中知道这个变换,或者通过tf获取)。 注意:由于静态tf已经发布,我们可以通过tf监听获取base_link到camera_link的变换,但这里我们假设这个变换是固定的并且已知(比如在URDF中定义,由robot_state_publisher发布),我们可以直接使用。但是,为了通用性,我们也可以从tf中获取这个固定变换(只需要获取一次,因为它是静态的)。 然而,用户要求是监听map到base_link,然后发布map到camera_link。我们可以将两个变换组合起来。 变换关系:T_map_camera = T_map_base * T_base_camera 在代码中,我们可以这样实现: - 使用tf_listener.lookupTransform获取T_map_base(即从map到base_link的变换) - 使用tf_listener.lookupTransform获取T_base_camera(即从base_link到camera_link的变换,注意这个变换是静态的,所以我们可以只获取一次,或者每次循环都获取,因为静态变换不会变,但为了效率,我们获取一次即可) 但是,注意:静态变换可能由其他节点发布,我们需要确保在获取它的时候已经存在。因此,我们可以在循环开始前等待这个变换,然后只获取一次并存储。 另一种做法:我们可以在代码中直接定义这个固定变换(如果已知且不变),这样就不需要从tf中获取,避免依赖。但是,如果这个变换可能改变(比如通过参数服务器动态配置),那么就需要每次从tf中获取。 这里,我们假设固定变换是已知的,并且已经在URDF中定义,由robot_state_publisher发布,因此我们可以从tf中获取。 考虑到代码的健壮性,我们可以在节点启动后等待静态变换,然后开始循环。 然而,根据引用[1]和[3],我们知道base_link到camera_link的变换已经通过静态tf发布,所以我们可以通过tf获取。 具体步骤: 1. 初始化节点。 2. 创建TransformListener和TransformBroadcaster。 3. 等待静态变换(base_link到camera_link)可用,并获取一次,存储这个变换(平移和旋转)。 4. 进入循环,在循环中: 尝试获取map到base_link的变换(注意:需要处理可能的异常,如变换不存在或时间问题)。 计算map到camera_link的变换:将两个变换组合(即矩阵相乘,但tf提供了方便的API:我们可以将两个变换连接起来)。 发布map到camera_link的变换。 注意:在ROS1的tf中,组合两个变换可以使用tf.transformations库中的函数,也可以直接使用数学运算(四元数和平移向量)。这里我们使用tf.transformations来简化计算。 但是,更简单的方法是:我们不需要显式计算矩阵乘法,因为我们可以将两个变换视为一个变换链。我们可以通过以下方式得到从map到camera_link的变换: 首先,我们有一个点P在camera_link坐标系中,我们想把它转换到map坐标系: P_map = T_map_base * (T_base_camera * P_camera) 所以,整个变换就是T_map_base * T_base_camera。 在代码中,我们可以这样计算: 平移:先应用T_base_camera的旋转和平移,再应用T_map_base的旋转和平移。 但是,直接计算变换矩阵相乘比较麻烦,我们可以使用tf.transformations.concatenate_matrices。 然而,我们也可以使用tf.TransformerROS类提供的方法,但这里我们使用基本数学运算。 另一种思路:我们可以将两个变换表示为平移向量和旋转四元数,然后使用tf.transformations库中的函数组合变换。 步骤: 将两个变换分别转换为4x4齐次变换矩阵。 将两个矩阵相乘。 然后将结果矩阵分解为平移向量和旋转四元数。 但是,这样计算比较繁琐。在ROS1中,我们可以使用tf.TransformerROS类的方法,但为了简单,我们使用tf.transformations。 不过,我们也可以使用tf.TransformerROS的composeTransforms方法(如果可用),但为了减少依赖,我们使用tf.transformations。 具体计算: 设T_map_base的平移为t1,旋转为q1(四元数) T_base_camera的平移为t2,旋转为q2 那么,组合后的变换的平移和旋转为: 旋转:q3 = q1 * q2 (注意四元数乘法顺序:先旋转q2,再旋转q1) 平移:先将t2用q1旋转,然后加上t1:t3 = t1 + tf.transformations.quaternion_matrix(q1)[:3,:3] * t2 但是,我们也可以使用齐次矩阵相乘: M1 = tf.transformations.quaternion_matrix(q1) M1[0:3,3] = t1 M2 = tf.transformations.quaternion_matrix(q2) M2[0:3,3] = t2 M3 = np.dot(M1, M2) t3 = tf.transformations.translation_from_matrix(M3) q3 = tf.transformations.quaternion_from_matrix(M3) 这样我们就得到了组合变换的平移和旋转。 但是,由于静态变换base_link->camera_link是固定的,我们可以预先计算好这个矩阵,然后在循环中只需要计算一次矩阵乘法。 然而,我们也可以使用tf.transformations.concatenate_matrices(M1, M2)来得到M3,但注意顺序:T_map_base * T_base_camera,所以应该是先T_base_camera(M2)再T_map_base(M1),但是矩阵乘法是M1 * M2,所以顺序正确。 但是,在循环中,T_map_base是动态变化的,而T_base_camera是固定的,所以我们可以预先计算M2(静态变换的矩阵),然后在循环中只计算M1(动态)和矩阵乘法。 考虑到性能,我们可以预先计算静态变换的矩阵M2。 然而,在ROS1中,还有一种更简单的方法:使用tf.TransformerROS类,它提供了transformPose或transformPoint等方法,但这里我们只需要变换关系,而不是具体的点。 实际上,我们可以直接使用tf.TransformerROS的composeTransforms方法,但注意,在Python中,tf.TransformerROS类可能没有直接提供这个方法(在C++中有)。因此,我们使用tf.transformations。 但是,在Python中,我们也可以使用tf.transformations.concatenate_matrices,但注意它接受多个4x4矩阵,然后按顺序相乘。 因此,我们这样实现: 预先: M_static = tf.transformations.quaternion_matrix([q2_x, q2_y, q2_z, q2_w]) M_static[0:3,3] = [t2_x, t2_y, t2_z] 在循环中: 获取T_map_base的平移t1和旋转四元数q1 M_dynamic = tf.transformations.quaternion_matrix([q1.x, q1.y, q1.z, q1.w]) M_dynamic[0:3,3] = [t1.x, t1.y, t1.z] M_result = np.dot(M_dynamic, M_static) # 注意:矩阵乘法顺序,先应用静态变换(base->camera),再应用动态变换(map->base) t3 = tf.transformations.translation_from_matrix(M_result) q3 = tf.transformations.quaternion_from_matrix(M_result) 然后,用t3和q3构造一个TransformStamped消息,并发布。 但是,注意:我们的变换是map->camera_link,所以父坐标系是map,子坐标系是camera_link。 然而,还有一种更简单的方法:使用tf.TransformerROS类的方法,但这里我们不使用。 考虑到性能,矩阵乘法在计算机中很快,所以可以接受。 但是,我们也可以避免使用矩阵,而使用四元数乘法和平移变换公式: t3 = t1 + tf.transformations.quaternion_multiply(q1, tf.transformations.quaternion_multiply(q2, [0,0,0,t2]) ... 这种方法并不直观,所以还是用矩阵。 因此,我们选择矩阵方法。 注意:由于静态变换在节点运行期间不会改变,我们可以预先计算其齐次矩阵。 但是,我们也可以从tf中获取静态变换的平移和旋转,然后计算矩阵。这里我们假设在代码中已知这个变换,或者通过参数获取。但为了通用性,我们从tf中获取。 所以,代码步骤: 1. 初始化节点 2. 创建TransformListener 3. 等待并获取base_link到camera_link的静态变换(只获取一次) 4. 将这个静态变换转换为齐次矩阵M_static 5. 创建TransformBroadcaster 6. 循环(以一定频率): 尝试获取map到base_link的变换(最近时间的) 将动态变换转换为齐次矩阵M_dynamic 计算M_result = M_dynamic * M_static 从M_result中提取平移和旋转(四元数) 构造TransformStamped消息,发布 注意:获取变换时,需要指定时间。对于静态变换,我们可以使用最新时间(now)。对于动态变换(map->base_link),我们使用最新时间(即最近时刻的变换)。 但是,在获取map->base_link时,我们使用tf_listener.getLatestCommonTime,然后使用该时间戳获取变换。或者,我们可以使用lookupTransform,并指定时间为ros.Time(0)表示最新可用的变换。 然而,在ROS1中,使用lookupTransform时,如果指定时间为0,会获取最新的可用变换。 但是,注意:为了时间同步,我们可能需要使用waitForTransform等待变换可用,但这里我们使用try-except。 具体代码实现如下(ROS1 Python):</think>在ROS1中使用`tf`库实现监听`map`到`base_link`的变换并发布`map`到`camera_link`的变换,可以通过以下步骤实现: ### 完整实现代码(ROS1 Python) ```python #!/usr/bin/env python import rospy import tf from tf.transformations import quaternion_multiply, quaternion_from_euler from geometry_msgs.msg import TransformStamped class CameraTfPublisher: def __init__(self): rospy.init_node('camera_tf_publisher') # 1. 定义固定变换:camera_link相对于base_link的位姿 self.fixed_trans = [0.1, 0.0, 0.2] # 平移 (x, y, z) 单位:米 self.fixed_rot = quaternion_from_euler(0, 0.2, 0) # 旋转 (俯仰角0.2弧度) # 2. 初始化TF工具 self.tf_broadcaster = tf.TransformBroadcaster() self.tf_listener = tf.TransformListener() # 3. 设置发布频率(10Hz) self.rate = rospy.Rate(10) rospy.loginfo("Camera TF Publisher 已启动") def run(self): while not rospy.is_shutdown(): try: # 4. 获取map到base_link的最新变换 (trans, rot) = self.tf_listener.lookupTransform( "map", "base_link", rospy.Time(0) # 获取最新可用变换 ) # 5. 计算组合变换:T_map_camera = T_map_base * T_base_camera # 5.1 旋转组合:q_result = q_map_base * q_base_camera combined_rot = quaternion_multiply(rot, self.fixed_rot) # 5.2 平移组合:t_result = R_map_base * t_base_camera + t_map_base # 将固定平移向量旋转到map坐标系 rotated_trans = tf.transformations.quaternion_matrix(rot)[:3, :3].dot(self.fixed_trans) combined_trans = [ trans[0] + rotated_trans[0], trans[1] + rotated_trans[1], trans[2] + rotated_trans[2] ] # 6. 发布map到camera_link的变换 self.tf_broadcaster.sendTransform( combined_trans, combined_rot, rospy.Time.now(), "camera_link", "map" ) except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException) as e: rospy.logwarn("TF查询失败: %s", str(e)) self.rate.sleep() if __name__ == '__main__': try: node = CameraTfPublisher() node.run() except rospy.ROSInterruptException: pass ``` ### 关键步骤说明 1. **定义固定变换** 通过测量或标定确定`camera_link`相对于`base_link`的固定位姿: ```python self.fixed_trans = [0.1, 0.0, 0.2] # 平移 (x, y, z) self.fixed_rot = quaternion_from_euler(0, 0.2, 0) # 旋转 (欧拉角转四元数) ``` 2. **监听动态变换** 使用`tf.TransformListener`获取`map`到`base_link`的最新位姿: ```python (trans, rot) = self.tf_listener.lookupTransform("map", "base_link", rospy.Time(0)) ``` 3. **坐标变换计算** 计算组合变换 $T_{map}^{camera} = T_{map}^{base} \times T_{base}^{camera}$: - **旋转组合**:四元数乘法 ```python combined_rot = quaternion_multiply(rot, self.fixed_rot) ``` - **平移组合**:先旋转固定偏移量,再加基础平移 ```python rotated_trans = tf.transformations.quaternion_matrix(rot)[:3, :3].dot(self.fixed_trans) combined_trans = [trans[i] + rotated_trans[i] for i in range(3)] ``` 4. **发布新变换** 使用`TransformBroadcaster`发布计算得到的变换: ```python self.tf_broadcaster.sendTransform( combined_trans, combined_rot, rospy.Time.now(), "camera_link", "map" ) ``` ### 验证方法 ```bash # 监听坐标系关系 rosrun tf tf_echo map camera_link # 可视化检查 rosrun rviz rviz # 添加TF显示,确认坐标系关系正确 ``` ### 性能优化建议 1. **预计算旋转矩阵** 将固定变换的旋转矩阵预先计算好,避免每次循环重复计算: ```python # 在__init__中添加 self.fixed_rot_matrix = tf.transformations.quaternion_matrix(self.fixed_rot)[:3, :3] # 在run()中替换 rotated_trans = self.fixed_rot_matrix.dot(self.fixed_trans) ``` 2. **使用C++实现** 对于高性能需求,建议使用C++版本的`tf`库,计算效率更高: ```cpp #include <tf/transform_broadcaster.h> #include <tf/transform_listener.h> void publishCameraTf() { tf::StampedTransform base_tf; listener.lookupTransform("map", "base_link", ros::Time(0), base_tf); tf::Transform camera_tf( tf::Quaternion(0, 0.2, 0), // 固定旋转 tf::Vector3(0.1, 0.0, 0.2) // 固定平移 ); tf::Transform result = base_tf * camera_tf; broadcaster.sendTransform( tf::StampedTransform(result, ros::Time::now(), "map", "camera_link") ); } ``` ### 典型应用场景 1. **移动机器人感知系统** 当相机安装在移动底盘上时,实时计算相机在全局坐标系中的位姿[^1] 2. **多传感器融合** 将相机数据与其他传感器(如激光雷达)在统一坐标系中进行融合[^3] 3. **视觉SLAM系统** 为视觉定位提供精确的传感器位姿信息[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值