目录
1、坐标变换
由于坐标变换在机器人开发之中非常重要,因此ROS2就开发了一个基于话题通讯的工具,即坐标变换工具,即TF。
1.1 核心作用:统一时空参考系
-
建立坐标系关联:将机器人各部件的局部坐标系(如激光雷达、摄像头、机械臂等)与全局坐标系(如地图
map
或里程计odom
)动态关联 -
解决"在哪里"的问题:让系统始终知道每个传感器/执行机构相对于机器人本体和环境的实时位置
1.2 关键应用场景
(1) 多传感器融合
-
示例:激光雷达(
lidar_link
)和摄像头(camera_link
)的数据对齐 -
实现方式:
# 发布静态变换:base_link → lidar_link static_transform_publisher x=0.1 y=0 z=0.5 q=0 0 0 1
(2) 自主导航与定位
-
SLAM:实时发布
map
→odom
→base_link
的变换链
graph LR map-->odom-->base_link-->sensor_frames
-
路径规划:所有障碍物坐标统一转换到
map
帧下计算
(3) 机械臂控制
-
手眼标定:发布相机到机械臂末端的固定变换
-
运动学计算:实时更新各关节坐标系关系
(4) 可视化调试
-
RViz显示:依赖TF树渲染机器人模型和各传感器数据
ros2 run rviz2 rviz2 -d <config.rviz>
下面介绍其如何通过C++发布坐标变换:
2、使用命令行
在 ROS2 中,可以通过命令行工具 ros2 run
来发布坐标变换(Transforms)。这通常使用 tf2_ros
包中的 StaticTransformBroadcaster
来完成。
ros2 run tf2_ros static_transfrom_publisher --x 0.1 --y 0.0 --z 0.2 --roll 0.0 --pitch 0.0 --yaw 0.0 --frame-id base_link --child-frame-id base_laser
3、使用功能包
步骤1:建立功能包
ros2 pkg create demo_cpp_tf --build-type ament_cmake --dependencies rclcpp tf2 tf2_ros geometry_msgs tf2_geometry_msgs --license Apache-2.0
步骤2:编写代码
在功能包下创建src文件夹,在src目录下建立static_tf_broadacaster.cpp文件,编写如下代码:
#include <rclcpp/rclcpp.hpp>
#include <tf2_ros/static_transform_broadcaster.h>
#include <geometry_msgs/msg/transform_stamped.hpp>
#include <tf2/LinearMath/Quaternion.h>
#include <cmath>
class StaticTfPublisher : public rclcpp::Node {
public:
StaticTfPublisher() : Node("static_tf_publisher") {
// 创建静态TF广播器
tf_pub_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);
// 发布静态变换
publish_static_transform();
}
private:
void publish_static_transform() {
// 创建TransformStamped消息
geometry_msgs::msg::TransformStamped transform;
// 设置时间戳
transform.header.stamp = this->now();
transform.header.frame_id = "base_link"; // 父坐标系
transform.child_frame_id = "laser"; // 子坐标系
// 设置平移 (x, y, z)
transform.transform.translation.x = 0.1;
transform.transform.translation.y = 0.0;
transform.transform.translation.z = 0.2;
// 设置旋转 (四元数)
tf2::Quaternion q,q1;
q.setRPY(0, 0, 0); // 无旋转 (roll=0, pitch=0, yaw=0)
transform.transform.rotation.x = q.x();
transform.transform.rotation.y = q.y();
transform.transform.rotation.z = q.z();
transform.transform.rotation.w = q.w();
// 发布变换
tf_pub_->sendTransform(transform);
RCLCPP_INFO(this->get_logger(), "Published static transform: base_link → laser");
}
std::shared_ptr<tf2_ros::StaticTransformBroadcaster> tf_pub_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<StaticTfPublisher>());
rclcpp::shutdown();
return 0;
}
步骤3:完善CMakeLists.txt
add_executable(static_tf_broadcaster src/static_tf_broadcaster.cpp)
ament_target_dependencies(static_tf_broadcaster rclcpp tf2_ros geometry_msgs tf2 tf2_geometry_msgs)
install(TARGETS
static_tf_broadcaster
DESTINATION lib/${PROJECT_NAME})
附加:如果使用launch文件进行启动节点,则需要在src/static_tf_broadcaster下建立launch文件夹,并创建static_tf.launch.py,在里面编写代码:
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_cpp_tf',
executable='static_tf_broadcaster',
name='static_tf_publisher',
parameters=[]
)
])
参数 | 说明 |
---|---|
| 节点所属的ROS2包名(需与 |
| 可执行文件名称(通常是Python脚本或C++节点编译后的名称) |
| 节点在ROS2系统中的名称(可通过 |
| (可选)日志输出方式,如 |
注意:
如果使用launch,则需要在CMakeLists.txt之中添加如下指令:
install(DIRECTORY launch
DESTINATION share/${PROJECT_NAME}/),否则运行时在share/功能包目录下找不到.launch文件
步骤4:编译运行
编译:
colcon build
source install/setup.bash
运行:
ros2 run cpp_static_tf static_tf_publisher
# 或通过launch文件启动
ros2 launch cpp_static_tf static_tf.launch.py
步骤5:验证节点
(1) 验证节点是否启动:
ros2 node list
(2) 验证TF树:
可以使用以下命令查看TF树:
ros2 run tf2_tools view_frames
或者查看特定变换:
ros2 run tf2_ros tf2_echo base_link lidar_link
(3) 使用RViz2查看TF变换:
ros2 run rviz2 rviz2
4、高级用法:
4.1 发布多个静态坐标变换
.cpp代码修改为:
#include <rclcpp/rclcpp.hpp>
#include <tf2_ros/static_transform_broadcaster.h>
#include <geometry_msgs/msg/transform_stamped.hpp>
#include <tf2/LinearMath/Quaternion.h>
#include <cmath>
class StaticTfPublisher : public rclcpp::Node {
public:
StaticTfPublisher() : Node("static_tf_publisher") {
// 创建静态TF广播器
tf_pub_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);
// 发布静态变换
publish_static_transform();
}
private:
void publish_static_transform() {
// 创建TransformStamped消息
geometry_msgs::msg::TransformStamped transform;
geometry_msgs::msg::TransformStamped transform1;
// 设置时间戳
transform.header.stamp = this->now();
transform.header.frame_id = "base_link"; // 父坐标系
transform.child_frame_id = "laser"; // 子坐标系
transform1.header.stamp = this->now();
transform1.header.frame_id = "base_link"; // 父坐标系
transform1.child_frame_id = "camera"; // 子坐标系
// 设置平移 (x, y, z)
transform.transform.translation.x = 0.1;
transform.transform.translation.y = 0.0;
transform.transform.translation.z = 0.2;
transform1.transform.translation.x = 5.0;
transform1.transform.translation.y = 5.0;
transform1.transform.translation.z = 5.0;
// 设置旋转 (四元数)
tf2::Quaternion q,q1;
q.setRPY(0, 0, 0); // 无旋转 (roll=0, pitch=0, yaw=0)
transform.transform.rotation.x = q.x();
transform.transform.rotation.y = q.y();
transform.transform.rotation.z = q.z();
transform.transform.rotation.w = q.w();
double roll = 180.0 * M_PI / 180.0; // 转换为弧度
double pitch = 120.0 * M_PI / 180.0; // 转换为弧度
double yaw = 40.0 * M_PI / 180.0; // 转换为弧度
q1.setRPY(roll, pitch, yaw); // 设置旋转 (roll, pitch, yaw)
transform1.transform.rotation.x = q1.x();
transform1.transform.rotation.y = q1.y();
transform1.transform.rotation.z = q1.z();
transform1.transform.rotation.w = q1.w();
// 发布变换
tf_pub_->sendTransform(transform);
RCLCPP_INFO(this->get_logger(), "Published static transform: base_link → laser");
tf_pub_->sendTransform(transform1);
RCLCPP_INFO(this->get_logger(), "Published static transform: base_link → camera");
}
std::shared_ptr<tf2_ros::StaticTransformBroadcaster> tf_pub_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<StaticTfPublisher>());
rclcpp::shutdown();
return 0;
}
4.2 从参数服务器读取配置,用于坐标变换
在src/demo_cpp_tf目录下建立config文件夹,创建params.yaml文件,编写代码:
static_tf_publisher:
ros__parameters:
parent_frame: "base_link"
child_frame: "lidar"
translation_x: 0.15
translation_y: -0.05
translation_z: 0.3
rotation_x: 0.0
rotation_y: 0.1
rotation_z: 0.0
注:第一行是节点名,不是可执行文件名!
publish_static_transform函数修改为:
void publish_static_transform() {
// 创建TransformStamped消息
// 设置坐标系
this->declare_parameter("parent_frame", "base_link");
this->declare_parameter("child_frame", "laser");
this->declare_parameter("translation_x", 0.1);
this->declare_parameter("translation_y", 0.0);
this->declare_parameter("translation_z", 0.2);
this->declare_parameter("rotation_x", 0.0);
this->declare_parameter("rotation_y", 10.0* M_PI / 180.0);
this->declare_parameter("rotation_z", 0.0);
geometry_msgs::msg::TransformStamped transform;
transform.header.frame_id = this->get_parameter("parent_frame").as_string(); // 父坐标系
transform.child_frame_id = this->get_parameter("child_frame").as_string(); // 子坐标系
// 设置平移 (x, y, z)
transform.transform.translation.x = this->get_parameter("translation_x").as_double();
transform.transform.translation.y = this->get_parameter("translation_y").as_double();
transform.transform.translation.z = this->get_parameter("translation_z").as_double();
// 设置旋转 (四元数)
tf2::Quaternion q;
q.setRPY(this->get_parameter("rotation_x").as_double(),
this->get_parameter("rotation_y").as_double(),
this->get_parameter("rotation_z").as_double());
transform.transform.rotation.x = q.x();
transform.transform.rotation.y = q.y();
transform.transform.rotation.z = q.z();
transform.transform.rotation.w = q.w();
// 发布变换
tf_pub_->sendTransform(transform);
RCLCPP_INFO(this->get_logger(), "Published static transform: base_link → laser");
}
launch文件修改为:
from launch import LaunchDescription
from launch_ros.actions import Node
//新增两个库文件
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
//新增params.yaml路径
package_dir = get_package_share_directory('demo_cpp_tf')
params_file = os.path.join(package_dir, 'config', 'params.yaml')
return LaunchDescription([
Node(
package='demo_cpp_tf',
executable='static_tf_broadcaster',
name='static_tf_publisher',
parameters=[params_file] //导入参数
)
])
CMakeLists.txt之中添加如下内容:
install(DIRECTORY config
DESTINATION share/${PROJECT_NAME}/)
验证参数是否有效:
启动节点 :ros2 launch demo_cpp_tf static_tf.launch.py
列出节点参数:ros2 param list static_tf_publisher
child_frame
parent_framerotation_x
rotation_y
rotation_ztranslation_x
translation_y
translation_z
获取参数值:ros2 param get static_tf_publisher child_frame
String value is: lidar
其他参数指令
描述参数
ros2 param describe [node_name] [parameter_name]设置参数
ros2 param set [node_name] [parameter_name] [value]
转储参数
ros2 param dump [node_name] > params.yaml
加载参数
ros2 param load [node_name] params.yaml
在启动时设置参数
ros2 run [package_name] [node_name] --ros-args -p [parameter_name]:=value