第十篇:TF、eigen、经纬度等坐标变换

本文详细介绍了ROS中的TF功能包,包括TF坐标转换树的作用、坐标变换的常用功能,如tf::Quaternion和tf::Matrix3x3类的使用,以及如何通过Broadcast和Listener进行坐标发布和订阅。此外,还探讨了使用Eigen库替代ROS_TF进行坐标转换的可能性,并给出了经纬度坐标转换的方法。内容深入浅出,适合机器人导航系统开发者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发管理相关博客专栏:
https://blog.csdn.net/qq_35635374/article/details/138258301

开发经验及方法博客专栏:
https://blog.csdn.net/qq_35635374/article/details/138256324

嵌入式系统平台硬软件底层开发相关博客专栏:
https://blog.csdn.net/qq_35635374/article/details/138229695

机器人导航系统架构及业务模块组合策略的相关博客专栏:
https://blog.csdn.net/qq_35635374/article/details/138202210

运动学与动力学基础知识相关博客专栏:
https://blog.csdn.net/qq_35635374/article/details/138201806

机器人传感器及感知相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138507260

机器人定位相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138199360

机器人地图建立相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138199063

机器人动作策略规划相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138175048

机器人全局路径规划相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138174918

机器人局部轨迹规划相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138174730

机器人轨迹跟踪控制相关教程及博客请关注专栏:
https://blog.csdn.net/qq_35635374/article/details/138168913

本文先对**TF坐标变换(导航定位必须用到!)**做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章


提示:以下是本篇文章正文内容

一、ROS_TF功能包作用

1.TF介绍

坐标转换和坐标订阅发布

TF库的目的是实现系统任一个点在所有坐标系之间的坐标变换,也就是说,只要给定一个坐标系下的一个点的坐标,就可以获得这个点在其他坐标系下的坐标,而且这些坐标变换是带有时间戳stamp

tf功能包就是给定起始位姿坐标,终止位姿坐标,TF功能包把每个位姿坐标通过tf_tree动态/静态链接依赖,并实时找到其变换矩阵【防盗标记–盒子君hzj】

使用的是广播订阅的通讯,也使用了launch文件管理启动,tf是ROS中的坐标变换系统,在机器人的建模仿真中经常用到
.

2.ROS-TF坐标转换树作用

在这里插入图片描述
(1)位置标定

实现各个坐标系的坐标变换,提供了ROS中各个组件(如:底盘、激光雷达,相机,IMU等),位置标定是机器人定位的基础

(2)访问关于机器人及其组件的时间和坐标的信息

我们可以从ROS-TF坐标转换树话题中访问关于机器人及其组件的时间和坐标的信息【防盗标记–盒子君hzj】

(3)记录和推算坐标系之间的关系

(1)五秒之前,机器人的参考坐标系相对于全局参考坐标系的关系是怎样的
(2)机器人上的激光雷达相对于机器人参考坐标系是怎样的
(3)机器人上的相机相对于机器人参考坐标系是怎样的

3.常用参考系类型

(1)全局坐标系(map)
(2)机器人身体坐标系(base_link)
(3)激光雷达参考坐标系
(4)摄像头参考坐标系【防盗标记–盒子君hzj】
(5)IMU参考坐标系
.
.

二、TF的常用功能

1.【transform】坐标系的数据格式、转换及运算

【位姿:欧拉角(rpy)、四元数(quternion)、旋转向量(vector)】

头文件#include<tf/transform_datatypes.h>

(1)tf::Quaternion类【四元素相关–直观】

(0)将一个坐标系下的点转化到另一个坐标系下【transformPoint()】
geometry_msgs::PointStamped point1;
point1.header.stamp=ros::Time();
point1.header.frame_id="child_frame";【防盗标记–盒子君hzj】
point1.point.x=1;
point1.point.y=2;
point1.point.z=3;
geometry_msgs::PointStamped point2;
try
{
     listener.transformPoint("frame",point1,point2);
}
catch (tf::TransformException &ex) 
{
    ROS_ERROR("%s",ex.what());
    ros::Duration(1.0).sleep();
 }
(1)用TF初始化创建四元数类型数据

在这里插入图片描述

(2)两种四元数数据类型转换【tf::Quaternion&&geometry_msgs::Quaternion】

在这里插入图片描述

(3)三种姿态形式的转换【四元数(quternion)、欧拉角(rpy)、旋转向量(vector)】

在这里插入图片描述
源码实现

tfScalar getAngle() const 【防盗标记–盒子君hzj】
{
	tfScalar s = tfScalar(2.) * tfAcos(m_floats[3]);
}
Vector3 getAxis() const
{
	tfScalar s_squared = tfScalar(1.) - tfPow(m_floats[3], tfScalar(2.));
	if (s_squared < tfScalar(10.) * TFSIMD_EPSILON) //Check for divide by zero
	        return Vector3(1.0, 0.0, 0.0);  // Arbitrary
	tfScalar s = tfSqrt(s_squared);
	return Vector3(m_floats[0] / s, m_floats[1] / s, m_floats[2] / s);
}
(4)四元数运算【相乘,点乘,求逆,加减乘除】

(1)四元数求模:sqrt(q.xq.x + q.yq.y + q.zq.z + q.wq.w)

tfScalar length() const
{
	return tfSqrt(length2());
}

(2)四元数模的平方:q.xq.x + q.yq.y + q.zq.z + q.wq.w

tfScalar length2() const
{
	return dot(*this);【防盗标记–盒子君hzj】
}

(3)四元数归一化

Quaternion& normalize() 
{
	return *this /= length();
}

Quaternion normalized() const 
{
	return *this / length();
}

(4)四元数点乘

tfScalar dot(const Quaternion& q) const
{
	return m_floats[0] * q.x() + m_floats[1] * q.y() + m_floats[2] * q.z() + m_floats[3] * q.m_floats[3];
}

(5)四元数相乘

TFSIMD_FORCE_INLINE Quaternion operator*(const Quaternion& q1, const Quaternion& q2) {
return Quaternion(
	q1.w() * q2.x() + q1.x() * q2.w() + q1.y() * q2.z() - q1.z() * q2.y(),
	q1.w() * q2.y() + q1.y() * q2.w() + q1.z() * q2.x() - q1.x() * q2.z(),
	q1.w() * q2.z() + q1.z() * q2.w() + q1.x() * q2.y() - q1.y() * q2.x(),【防盗标记–盒子君hzj】
	q1.w() * q2.w() - q1.x() * q2.x() - q1.y() * q2.y() - q1.z() * q2.z()); 
}

Quaternion& operator*=(const Quaternion& q)
{
	setValue(m_floats[3] * q.x() + m_floats[0] * q.m_floats[3] + m_floats[1] * q.z() - m_floats[2] * q.y(),
	        m_floats[3] * q.y() + m_floats[1] * q.m_floats[3] + m_floats[2] * q.x() - m_floats[0] * q.z(),
	        m_floats[3] * q.z() + m_floats[2] * q.m_floats[3] + m_floats[0] * q.y() - m_floats[1] * q.x(),
	        m_floats[3] * q.m_floats[3] - m_floats[0] * q.x() - m_floats[1] * q.y() - m_floats[2] * q.z());
	return *this;
}
(5)四元数插值
Quaternion slerp(const Quaternion& q, const tfScalar& t) const
注意

移植TF库的时候,这些实现原理都要懂

.
.
.

(2)tf::Matrix3x3类【变换矩阵相关–不直观】

(简单认为就是tf::Quaternion类是一维的,tf::Matrix3x3类是三维的)

在Matrix3x3类中,主要涉及到有各种不同方式的旋转矩阵初始化,欧拉角转旋转矩阵,旋转矩阵转欧拉角,四元数转旋转矩阵,旋转矩阵转四元数,以及一些常用的矩阵操作(矩阵相乘,求逆,转置,求行列式,…)等操作 :【防盗标记–盒子君hzj】

.
.
.

2.【Broadcaster/Listenter】订阅/发布每个关节的坐标(实现步骤)

(1)创建一个TF坐标变换的功能包

【当然这个功能可以在其他功能包内嵌套,但是最好还是单独成立一个tf坐标变换的功能包,好排除bug】

在功能包下的src文件夹新建一个文件夹tf,在tf文件夹里新建tf_boardcaster.cpp\tf_listener.cpp
.
.

(2)(发布者)广播TF变换

【Broadcaster发布坐标】
向系统中广播坐标系与坐标系变换的关系,系统中存在多个不同TF变换广播,每个广播都会直接在坐标系变换树中插入,有助于更快的查询、增加和删除TF节点

实现TF广播器程序步骤

(1)头文件
#include<tf/transform_broadcaster.h>

(2)定义发布器
tf::TransformBroadcaster broadcaster;【防盗标记–盒子君hzj】

(3)发布一个tf(源文件随时发布)
broadcaster.sendTransform( stamped_transform ) ;
【stamped_transform是一个tf::StampedTransform类型的数据】

.
.

(3)(订阅者)监听TF变换

【Listenter订阅坐标】
接收并缓存系统中发布的所有坐标系变换、并从中查询所需要的参考系变换

实现TF监听器程序步骤

(1)头文件
#include<tf/transform_listenter.h>

(2)定义监听器
tf::TransformListener  listener

(3)订阅TF的坐标信息【lookuoTransform()】

使用方法
	tf::StampedTransform stamped_transform;   //定义存放变换关系的变量
	try
	{
	//得到child_frame坐标系原点,在frame坐标系下的坐标与旋转角度
	      listener.lookupTransform("frame", "child_frame",ros::Time(0), stamped_transform);                   
	}
	    catch (tf::TransformException &ex)
	{
	      ROS_ERROR("%s",ex.what());
	      ros::Duration(1.0).sleep();
	      continue;
	}

	listener.lookupTransform("frame", "child_frame",ros::Time(0), stamped_transform);
	//获取到stamped_transform后可以使用这些信息
	transform.getOrigin().x()
	transform.getOrigin().y()
	 
	transform.getRotation().getW();
	transform.getRotation().getX();【防盗标记–盒子君hzj】
	transform.getRotation().getY();
	transform.getRotation().getZ();

(4)编译代码

1、在cmakelist.txt中设置编译选项,设置链接库
2、在元功能包下编译

.
.

3.TF变换的运算本质

TF变换就是两个刚性坐标系的平移和旋转的矩阵运算,不过它是实时运行的,包括位置和姿态

TF变换是基于右手坐标系的
拿起你的右手,先给自己竖个大拇指,然后打开手掌,将大拇指的方向朝向是z轴,让剩下的四根手指朝向的x轴,此时朝向手心外的是y轴的方向

三、使用eigen实现tf功能包的坐标转换功能替换(公司的导航系统没有用ROS_TF,eigen是矩阵运算的首选)

eigen的坐标转换功能有很多,要用什么才移植什么,太多了~

1、eigen的原理学习

https://blog.csdn.net/sunqin_csdn/article/details/108134138
.
.

2、位姿pose转矩阵

Eigen::Matrix4d Converter::poseToTransformMatrix( const Eigen::Vector3d& _ned, Eigen::Vector3d& _euler_angle, const Eigen::Vector3d& _offset_angle, const Eigen::Vector3d& _offset_position) {

  Eigen::Quaterniond _offset_qua;
  eulerToQuaternion(_offset_angle, _offset_qua);
  Eigen::Quaterniond _qua;
  eulerToQuaternion(_euler_angle, _qua);
  Eigen::Translation3d _offset_translation(_offset_position(0), _offset_position(1), _offset_position(2));

  Eigen::Translation3d translation3(_ned[0], _ned[1], _ned[2]);
  Eigen::Matrix4d transform = Eigen::Isometry3d(translation3 * _qua * _offset_translation * _offset_qua) .matrix();  // angle_yaw*angle_pitch*angle_roll
  return transform;
}

3、欧拉角euler转四元数Quaternion

void Converter::eulerToQuaternion(const Eigen::Vector3d& _euler, Eigen::Quaterniond& _qua) {
  Eigen::AngleAxis<double> angle_roll(_euler[0] * 0.0174532922f,  Eigen::Vector3d(1, 0, 0));
  Eigen::AngleAxis<double> angle_pitch(_euler[1] * 0.0174532922f, Eigen::Vector3d(0, 1, 0));
  Eigen::AngleAxis<double> angle_yaw(_euler[2] * 0.0174532922f, Eigen::Vector3d(0, 0, 1));
  _qua = Eigen::Quaterniond(angle_yaw * angle_pitch * angle_roll);
}

四、注意

(1)四元数(0,0,0,1)代表方向都为0的全能角【防盗标记–盒子君hzj】

(2)四元数是用来表示姿态pose的,而不是表示位置point的。能表示姿态还可以是旋转向量vector、欧拉角rpy、四元数(x,y,z,w)

(3)tf坐标连接关系可以通过rosrun rqt_tf_tree rqt_tf_tree查看,若是节点断开了就代表tf连接有问题,去源码看看有没有问题,实在不行就在launch文件中用静态链接指令手动连接在一起(只是一个隐藏的bug).静态链接指令[x,y,z,r,p,y/x,y,z,四元数 : 父节点坐标、子节点坐标]【防盗标记–盒子君hzj】

五、经纬度转换

1、北东地坐标表转经纬度坐标

void Converter::nedToLls(const Eigen::Vector3i& _lls_takeoff, const Eigen::Vector3d& _ned, Eigen::Vector3i& _lls) {
  _lls[0] = static_cast<int32_t>(static_cast<double>(_ned[0]) / LANLON_COEFFICIENT) + _lls_takeoff[0];
  _lls[1] = static_cast<int32_t>((static_cast<double>(_ned[1]) / cos(static_cast<double>(_lls[0]) / pow(10, 7) * 0.0174532925f)) / LANLON_COEFFICIENT) + _lls_takeoff[1];
  _lls[2] = _lls_takeoff[2] - _ned[2] * 100.0f;
}

2、经纬度坐标转北东地坐标表

void Converter::llsToNed(const Eigen::Vector3i& _lls_takeoff, const Eigen::Vector3i& _lls, Eigen::Vector3d& _ned) {
  double temp = static_cast<double>(_lls[1] - _lls_takeoff[1]) * LANLON_COEFFICIENT;
  temp *= cos(static_cast<double>(_lls[0]) / pow(10, 7) * 0.0174532925f);
  _ned[1] = temp;
  temp = static_cast<double>(_lls[0] - _lls_takeoff[0]) * LANLON_COEFFICIENT;
  _ned[0] = temp;
  _ned[2] = -static_cast<double>(_lls[2] - _lls_takeoff[2]) / 100.0f;
}


相关技术专栏推荐

(1)计算技术&硬软件开发工程篇
https://blog.csdn.net/qq_35635374/category_12821115.html

(2)计算机技术基础&开发经验
https://blog.csdn.net/qq_35635374/category_11471204.html

(3)嵌入式系统硬软件开发
https://blog.csdn.net/qq_35635374/category_11464543.html

(4)开发技术管理
https://blog.csdn.net/qq_35635374/category_12344669.html

(5)机器人/自动驾驶导航算法篇
https://blog.csdn.net/qq_35635374/category_12825966.html

(6)导航系统架构及业务模块组合策略
https://blog.csdn.net/qq_35635374/category_11464757.html

(7)运动学与动力学基础知识
https://blog.csdn.net/qq_35635374/category_11471199.html

(8)多传感器标定、数据融合与状态估计
https://blog.csdn.net/qq_35635374/category_11464733.html

(9)定位、地图建立、地图管理SLAM合集
https://blog.csdn.net/qq_35635374/category_12805256.html

(10)定位location
https://blog.csdn.net/qq_35635374/category_11464501.html

(11)地图mapping
https://blog.csdn.net/qq_35635374/category_11464370.html

(12)机器人决策规划控制合集
https://blog.csdn.net/qq_35635374/category_12804215.html

(13)任务决策规划mission_planner
https://blog.csdn.net/qq_35635374/category_12344770.html

(14)动作策略规划motion_planner
https://blog.csdn.net/qq_35635374/category_12176372.html

(15)全局路线规划global_planner
https://blog.csdn.net/qq_35635374/category_12176370.html

(16)局部路径规划local_planner
https://blog.csdn.net/qq_35635374/category_12176374.html

(17)轨迹跟踪控制模块Path_tracking
https://blog.csdn.net/qq_35635374/category_12176376.html

(18)机器人实战篇
https://blog.csdn.net/qq_35635374/category_12821111.html

(19)足式机器人&机械臂控制合集
https://blog.csdn.net/qq_35635374/category_11523332.html

(20)自动驾驶&无人机导航合集
https://blog.csdn.net/qq_35635374/category_12804317.html

(21)四足机器人MIT Cheetah mini
https://blog.csdn.net/qq_35635374/category_11523325.html

(22)自动驾驶Autoware
https://blog.csdn.net/qq_35635374/category_11523328.html

(23)无人机fast_planner
https://blog.csdn.net/qq_35635374/category_11523335.html

### 使用Eigen库进行坐标变换及创建变换矩阵 #### 创建变换矩阵 在Eigen库中,可以通过`Affine3f`或`Isometry3f`类来表示三维空间中的刚体变换。这些类能够处理旋转和平移操作。 对于给定的角度θ=π/2(即90度),可以构建一个绕Z轴逆时针方向的旋转变换矩阵R: \[ R=\begin{bmatrix} \cos(\theta)&-\sin(\theta)\\ \sin(\theta)&\cos(\theta) \end{bmatrix}= \begin{bmatrix} 0&-1\\ 1&0 \end{bmatrix}\] 接着定义平移向量t=(tx,ty)=(2,2),则完整的齐次变换矩阵T可由下述方式获得[^3]: ```cpp #include <iostream> #include <Eigen/Dense> using namespace Eigen; int main() { Matrix3d rotation_matrix; rotation_matrix << 0,-1, 1, 0; // Rotation by pi/2 radians around Z axis Vector2d translation_vector(2, 2); Affine2d transform = Translation2d(translation_vector)*Rotation2D<double>(M_PI / 2); std::cout << "Transform matrix:\n" << transform.matrix() << "\n"; } ``` 此代码片段展示了如何利用Eigen库构造一个二维平面内的仿射变换对象,并打印出对应的变换矩阵形式。 #### 应用变换到点上 当有一个位于frame_1下的点P其坐标为(1,0),要将其转换至全局坐标系(world frame)下,则需执行如下运算: \[ P_{world}=RP_{local}+t \] 其中\( P_{local}=[1,0]^T\) 是待变换点的位置矢量,在上述例子中已经给出具体的数值;而 \( t=[2,2]^T \) 则代表了frame_1相对世界坐标的位移分量。因此, \[ P_{world} = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix} \times \begin{bmatrix} 1 \\ 0 \end{bmatrix}+ \begin{bmatrix} 2 \\ 2 \end{bmatrix}= \begin{bmatrix} 2 \\ 3 \end{bmatrix}\]. 这段解释说明了怎样通过乘法和加法完成从局部坐标系到全局坐标系之间的位置映射过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RoboticsTechLab

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值