tf2:setRPY、doTransform、getRPY

  • 欧拉角可以分为绕定轴(外旋方式)和绕动轴(内旋方式)的变换方式。按定轴转动时,矩阵依次左乘。绕动轴转动时,矩阵依次右乘。一般来说,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,那没问题。

全部评论: 0

    写评论: