- 欧拉角可以分为绕定轴(外旋方式)和绕动轴(内旋方式)的变换方式。按定轴转动时,矩阵依次左乘。绕动轴转动时,矩阵依次右乘。一般来说,Ros解决问题是绕定轴。那如何区分某个函数是按定轴还是动轴?——函数名没有给出Euler时,定轴;名字中有Euler,则表示为绕动轴。但是,EulerZYX可认为是绕定轴,只不过调用它时,传递的参数顺序是yaw、pitch、roll。
- 从RPY转为tf2::Quaternion,用setRPY或setEulerZYX,不要用setEuler(这是绕动轴)。建议只用setRPY,因为后者声明为过时(ROS_DEPRECATED)。
- tf::Quaternion(const tf2Scalar& yaw, const tf2Scalar& pitch, const tf2Scalar& roll)。tf2把这构造声明为过时(ROS_DEPRECATED),因为内中用了setEuler,这种构造就不建议使用了。可改为,先用无参数版本构造一个Quaternion,然后调用setRPY。
- tf2没有tf2::getRPY函数,为从tf2::Quaternion转为RPY,使用getEulerYPR,注意参数顺序。在不少场合,RPY中只关心偏航角yaw,为此tf2提供了一个只得到yaw的函数:tf2::getYaw。
- 疑问:绕动轴ypr旋转的结果是否等价绕定轴rpy旋转的结果?
tf2是个命名空间,提供了数个解决旋转角、四元数之间转换的函数,按作用分为三个部分。一是从旋转角RPY转为四元数,进而和平移组成位姿,对应标题中的setRPY。二是位姿变换,即把坐标系2中位姿(向量)变换到坐标系1,对应标题中的doTransform。三是由四元数生成旋转角RPY,对应标题中getRPY。
一、setRPY:从旋转角RPY转为四元数
本节内容转自SLAM中坐标轴旋转及ros的接口解释。
ros中的欧拉角可以分为绕定轴(外旋方式)和绕动轴(内旋方式)的变换方式,函数没有给出Euler时,是按定轴转动,矩阵依次左乘。若函数的名字中有Euler,则表示为绕动轴转动的方式,矩阵依次右乘。
tf2提供了欧拉角旋转的函数:setRPY、setEuler、setEulerZYX。其中1、3等价,而且setEulerZYX()声明为过时(ROS_DEPRECATED)。
1.1 setRPY()
这个函数采用固定轴的旋转方式,先绕定轴x旋转(横滚),然后再绕定轴y (俯仰),最后绕定轴z(偏航)。从数学形式上说,这是绕定轴XYZ矩阵依次左乘.即:R=R(z)R(y)R(x)的顺序。由于是绕着定轴转动,所以很直观,便于人机交互。
1.2 setEuler()
这种方式是绕着动轴转动,先绕Y轴,在绕变换后的X轴,再绕变换后的Z轴旋转。从数学形式上说,这是绕定轴YXZ矩阵依次右乘.即:R= R(y)R(x)R(z)的顺序。
1.3 setEulerZYX()
与2相同,此种旋转变换也是绕动轴旋转,只不过次序为ZYX,矩阵也是右乘,即R = R(z)R(y)R(x)。我们发现,这里的R与1中的R的最终结果相同,所以1和3的定义最终是等价的。
<libros>/include/tf2/LinearMath/Quaternion.h ------ class Quaternion : public QuadWord { /**@brief Set the quaternion using euler angles * @param yaw Angle around Z * @param pitch Angle around Y * @param roll Angle around X */ ROS_DEPRECATED void setEulerZYX(const tf2Scalar& yaw, const tf2Scalar& pitch, const tf2Scalar& roll) { setRPY(roll, pitch, yaw); } }
从tf2源可看到,setEulerZYX直接调用了setRPY,而且声明为过时ROS_DEPRECATED。
总结:“外定左,内动右” 。
二、doTransform:位姿变换
假设存在camera、base_footprint两个坐标系,变量transform是camera(相机)到base_footprint的tf,类型geometry_msgs::TransformStamped。变量camera_pose是相机坐标系下的一个位姿(向量)。
geometry_msgs::Pose t_out; tf2::doTransform(camera_pose, t_out, transform);
经过上面这个doTransform后,t_out就是camera_pose在base_footprint下的位姿。
如果你要的只是向量坐标x、y、z,那只要从t_out取出平移即可。
三、getRPY:由四元数生成旋转角RPY
tf2没有tf2::getRPY函数,取而代之的是tf2::getEulerYPR。这有点类似上面“从旋转角RPY转为四元数”,setRPY和setEulerZYX功能上等价。
double roll, pitch, yaw; tf2::getEulerYPR(t_out.orientation, yaw, pitch, roll);
至此由四元数生成了旋转角RPY,注意传给getEulerYPR的参数顺序。另外,在2D时,RPY中往往只关心偏航角yaw,为此tf2提供了一个只得到yaw的函数。
yaw = tf2::getYaw(t_out.orientation);
对tf2::getEulerYPR或tf2::getYaw,它的第一个参数类型可以或tf2::Quaternion,或geometry_msgs::Quaternion,或geometry_msgs::QuaternionStamped。就效率来说tf2::Quaternion最高,因为另外两种都是要先转换为tf2::Quaternion。
tf2也提供了一个叫getRPY方法由四元数生成旋转角,不过那是tf2::Matrix3x3中的一个方法。假设变量q的类型是tf2::Quaternion。
tf2::Quaternion q; q.setRPY(0,0,M_PI/6); // yaw转动30度的四元数 tf2::Matrix3x3 basis(q); tf2Scalar r, p, y; basis.getRPY(r, p, y);
先把tf2::Quaternion转为旋转矩阵tf2::Matrix3x3,再调用旋转矩阵的getRPY方法得到row、pitch、roll。
对旋转矩阵Matrix3x3,由下标[row]可得到具体哪一行。
- Matrix3x3 basis[0]: 表示矩阵的第一行,对应类成员Vector3 m_el[0]。
- Matrix3x3 basis[1]: 表示矩阵的第二行,对应类成员Vector3 m_el[1]。
- Matrix3x3 basis[2]: 表示矩阵的第三行,对应类成员Vector3 m_el[2]。
关于tf2更多内容更参考“ROS-坐标变换(tf2)”。
注意,以上面得到的t_out.orientation为参数,生成一个Eigen::Quaterniond。调用它的matrix().eulerAngles方法,参数不论是(0, 1, 2),还是(2, 1, 0),得到的不是正确RPY。
Eigen::Quaterniond orientation(t_out.orientation.w, t_out.orientation.x, t_out.orientation.y, t_out.orientation.z); orientation.normalize(); // 是否调用过normalize(),一样结果 Eigen::Vector3d rpy = orientation.matrix().eulerAngles(0, 1, 2); Eigen::Vector3d rpy2 = orientation.matrix().eulerAngles(2, 1, 0);
这个结论不那么严谨:tf2::Quaternion、geometry_msgs::Quaternion、geometry_msgs::QuaternionStamped(其实是geometry_msgs::Quaternion)中四元素可以相互赋值,但排除要用于matrix().eulerAngles()的Eigen::Quaterniond。当然,如果Eigen::Quaterniond只是用于存储tf2::Quaternion中的w、x、y、z四数值,最后还是用tf2中函数得到RPY,那没问题。