旋转矩阵 (Rotation Matrix) 详解与 ROS 实践


1. 基本定义与几何意义

在三维刚体运动中,旋转矩阵用于描述一个坐标系相对于另一个坐标系的姿态(Orientation)

假设存在参考坐标系 $\{s\}$(Space/Fixed)和目标坐标系 $\{b\}$(Body)。旋转矩阵 $R_{sb} \in \mathbb{R}^{3 \times 3}$ 的每一列,恰好是 $\{b\}$ 坐标系的三个单位基向量在 $\{s\}$ 系下的坐标表示:

$$ R_{sb} = \begin{bmatrix} \hat{x}_b^s & \hat{y}_b^s & \hat{z}_b^s \end{bmatrix} = \begin{bmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} & r_{23} \\ r_{31} & r_{32} & r_{33} \end{bmatrix} $$

其中 $\hat{x}_b^s, \hat{y}_b^s, \hat{z}_b^s$ 分别表示 $\{b\}$ 系的 $x, y, z$ 轴单位向量在 $\{s\}$ 系中的投影坐标。


2. 核心数学约束与特殊正交群

旋转矩阵并非任意 $3 \times 3$ 矩阵,它必须满足严格的几何约束,数学上称为 特殊正交群 (Special Orthogonal Group),记作 $SO(3)$。

2.1 约束条件

  1. 单位向量约束(每列模长为 1): $$ \|\hat{x}_b^s\|^2 = \|\hat{y}_b^s\|^2 = \|\hat{z}_b^s\|^2 = 1 $$
  2. 正交性约束(两两内积为 0): $$ \hat{x}_b^s \cdot \hat{y}_b^s = \hat{x}_b^s \cdot \hat{z}_b^s = \hat{y}_b^s \cdot \hat{z}_b^s = 0 $$
  3. 右手定则约束(行列式为 +1): $$ \hat{x}_b^s \times \hat{y}_b^s = \hat{z}_b^s \quad \Rightarrow \quad \det(R) = 1 $$

上述 6 个标量约束可统一写为矩阵正交条件:

$$ R^T R = I $$

结合行列式约束,完整定义为:

$$ SO(3) = \{ R \in \mathbb{R}^{3 \times 3} \mid R^T R = I, \ \det(R) = 1 \} $$

2.2 群公理验证

$SO(3)$ 满足数学群的四大公理:

公理 数学表达 说明
封闭性 $R_1, R_2 \in SO(3) \Rightarrow R_1 R_2 \in SO(3)$ 连续旋转仍是纯旋转
结合律 $(R_1 R_2) R_3 = R_1 (R_2 R_3)$ 矩阵乘法天然满足
单位元 $I \in SO(3)$,满足 $RI = IR = R$ 零旋转状态
可逆性 $R^{-1} = R^T \in SO(3)$ 反向旋转只需转置

3. 核心性质

性质 数学表达 物理意义
逆等于转置 $R^{-1} = R^T$ 求反向姿态无需复杂求逆,计算高效
非交换性 $R_1 R_2 \neq R_2 R_1$(一般情况) 旋转顺序直接影响最终姿态(三维特有)
保范数性 $\|R \mathbf{v}\| = \|\mathbf{v}\|$ 纯旋转不改变向量长度
保内积性 $(R\mathbf{u})^T (R\mathbf{v}) = \mathbf{u}^T \mathbf{v}$ 旋转不改变向量夹角

4. 常用旋转矩阵表达式

4.1 二维旋转 $SO(2)$

二维仅含 1 个自由度 $\theta \in [0, 2\pi)$:

$$ R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} $$

4.2 绕固定坐标轴旋转(基本旋转矩阵)

$$ R_x(\theta) = \begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta \\ 0 & \sin\theta & \cos\theta \end{bmatrix}, \quad R_y(\theta) = \begin{bmatrix} \cos\theta & 0 & \sin\theta \\ 0 & 1 & 0 \\ -\sin\theta & 0 & \cos\theta \end{bmatrix}, \quad R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} $$

4.3 绕任意单位轴 $\hat{\omega} = (\omega_1, \omega_2, \omega_3)^T$ 旋转 $\theta$

罗德里格斯公式 (Rodrigues’ Formula) 给出:

$$ R(\hat{\omega}, \theta) = \begin{bmatrix} c_\theta + \omega_1^2(1-c_\theta) & \omega_1\omega_2(1-c_\theta) - \omega_3 s_\theta & \omega_1\omega_3(1-c_\theta) + \omega_2 s_\theta \\ \omega_1\omega_2(1-c_\theta) + \omega_3 s_\theta & c_\theta + \omega_2^2(1-c_\theta) & \omega_2\omega_3(1-c_\theta) - \omega_1 s_\theta \\ \omega_1\omega_3(1-c_\theta) - \omega_2 s_\theta & \omega_2\omega_3(1-c_\theta) + \omega_1 s_\theta & c_\theta + \omega_3^2(1-c_\theta) \end{bmatrix} $$

其中 $s_\theta = \sin\theta,\ c_\theta = \cos\theta$。具有对称性:$R(\hat{\omega}, \theta) = R(-\hat{\omega}, -\theta)$。


5. 两大应用场景:表示 vs 操作

旋转矩阵在工程中有两种截然不同的用法,关键看矩阵是否带下标

用法 特征 物理含义 示例
1. 表示 (Representation) 带下标,如 $R_{sb}$ 描述坐标系 $\{b\}$ 相对于 $\{s\}$ 的静态姿态 相机相对于机器人的朝向
2. 操作 (Operation) 无下标,如 $R$ 同一坐标系内执行旋转动作 机械臂末端绕自身轴转动

6. 坐标变换规则与下标消去法

6.1 参考系传递(坐标系连乘)

已知 $\{b\}$ 相对 $\{s\}$ 的 $R_{sb}$,以及 $\{c\}$ 相对 $\{b\}$ 的 $R_{bc}$,求 $\{c\}$ 相对 $\{s\}$ 的 $R_{sc}$:

$$ R_{sc} = R_{sb} R_{bc} $$

下标消去技巧:相邻相同下标自动抵消,$R_{ab} R_{bc} R_{cd} = R_{ad}$。
利用正交性可推导:$R_{sc} = R_{sb} R_{cb}^{-1} = R_{sb} R_{cb}^T$。

6.2 向量坐标变换(点/向量换系)

设点 $P$ 在 $\{b\}$ 系中的坐标为 $\mathbf{p}_b$,求其在 $\{s\}$ 系中的坐标 $\mathbf{p}_s$:

$$ \mathbf{p}_s = R_{sb} \mathbf{p}_b $$

向量变换口诀:$R_{ab} \mathbf{p}_b = \mathbf{p}_a$(矩阵下标左侧对应目标系,右侧对应原系)。


7. 左乘与右乘的物理意义(重点)

当旋转矩阵不带下标(如 $R = \text{Rot}(\hat{z}, 90^\circ)$)时,表示在同一坐标系内进行旋转操作。此时左右顺序决定旋转轴参考系:

操作 公式 物理含义
左乘 $R'_{sb} = R \cdot R_{sb}$ 固定参考系 $\{s\}$ 的坐标轴旋转
右乘 $R'_{sb} = R_{sb} \cdot R$ 运动载体系 $\{b\}$ 的坐标轴旋转

记忆技巧:看 $R_{sb}$ 的字母位置。s 在左侧,左乘 $R$ 表示绕 $\{s\}$ 系转;b 在右侧,右乘 $R$ 表示绕 $\{b\}$ 系转。
无论左乘还是右乘,结果矩阵 $R'_{sb}$ 仍然是描述 $\{b\}$ 相对于 $\{s\}$ 的新姿态。


8. ROS 工程实践 (tf2 库)

在 ROS 中,坐标系变换通过 tf2 管理。每个 TransformStamped 本质是一个齐次变换矩阵(含旋转与平移)。ROS 底层统一使用四元数存储旋转。

8.1 发布者 (Publisher):广播静态坐标系

#include <ros/ros.h>
#include <tf2_ros/static_transform_broadcaster.h>
#include <geometry_msgs/TransformStamped.h>
#include <tf2/LinearMath/Quaternion.h>

int main(int argc, char** argv) {
    ros::init(argc, argv, "rotation_publisher");
    ros::NodeHandle nh;

    tf2_ros::StaticTransformBroadcaster static_broadcaster;
    geometry_msgs::TransformStamped static_transformStamped;

    // 1. 设置父子关系:s -> b
    static_transformStamped.header.stamp = ros::Time::now();
    static_transformStamped.header.frame_id = "s";
    static_transformStamped.child_frame_id = "b";

    // 2. 平移(纯旋转设为 0,此处设 x=-2 仅用于 RViz 视觉区分)
    static_transformStamped.transform.translation.x = -2.0;
    static_transformStamped.transform.translation.y = 0.0;
    static_transformStamped.transform.translation.z = 0.0;

    // 3. 旋转:{b} 由 {s} 绕 z_s 轴旋转 90° 得到
    tf2::Vector3 z_axis(0, 0, 1);
    double theta = M_PI / 2.0;
    tf2::Quaternion quat;
    quat.setRotation(z_axis, theta);

    static_transformStamped.transform.rotation.x = quat.x();
    static_transformStamped.transform.rotation.y = quat.y();
    static_transformStamped.transform.rotation.z = quat.z();
    static_transformStamped.transform.rotation.w = quat.w();

    static_broadcaster.sendTransform(static_transformStamped);
    ROS_INFO("Published transform: s -> b (Rot z by 90 deg)");
    ros::spin();
    return 0;
}

8.2 监听者 (Listener):使用 TF 变换向量

#include <ros/ros.h>
#include <tf2_ros/transform_listener.h>
#include <geometry_msgs/PointStamped.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#include <boost/bind.hpp>

void transformPoint(const tf2_ros::Buffer& buffer) {
    // 1. 定义在 {b} 系中的点
    geometry_msgs::PointStamped p_b;
    p_b.header.frame_id = "b";
    p_b.header.stamp = ros::Time(0);
    p_b.point.x = -1.0;
    p_b.point.y =  0.0;
    p_b.point.z =  0.0;

    try {
        // 2. 获取从 {b} 到 {s} 的变换 (target_frame, source_frame)
        geometry_msgs::TransformStamped R_sb = buffer.lookupTransform("s", "b", ros::Time(0));

        // 3. 执行坐标变换: p_s = R_sb * p_b
        geometry_msgs::PointStamped p_s;
        tf2::doTransform(p_b, p_s, R_sb);

        ROS_INFO("{b}:(%.2f, %.2f, %.2f) ---> {s}:(%.2f, %.2f, %.2f)",
                 p_b.point.x, p_b.point.y, p_b.point.z,
                 p_s.point.x, p_s.point.y, p_s.point.z);
    } catch (tf2::TransformException &ex) {
        ROS_ERROR("TF Lookup failed: %s", ex.what());
    }
}

int main(int argc, char** argv) {
    ros::init(argc, argv, "rotation_listener");
    ros::NodeHandle nh;

    tf2_ros::Buffer tfBuffer;
    tf2_ros::TransformListener tfListener(tfBuffer);

    // 定时执行 (1Hz)
    ros::Timer timer = nh.createTimer(ros::Duration(1.0), 
                                      boost::bind(&transformPoint, boost::ref(tfBuffer)));
    ros::spin();
    return 0;
}

ROS TF 补充说明

  1. lookupTransform("target", "source", time) 返回的是从 sourcetarget 的变换。
  2. TF 树方向永远是 Parent → Child,但 RViz 中坐标轴箭头显示的是 Child 相对于 Parent 的朝向。二者描述同一数学关系,仅视角不同。
  3. tf2::doTransform 内部已处理四元数与向量的乘法,无需手动展开矩阵。

9.要点总结

场景 规则/口诀 示例
连乘消下标 中间字母相消,首尾保留 $R_{ab} R_{bc} R_{cd} = R_{ad}$
向量换系 矩阵下标右侧匹配向量原系 $R_{sb} \mathbf{p}_b = \mathbf{p}_s$
求逆转置 交换下标顺序等价于求转置 $R_{bs} = R_{sb}^{-1} = R_{sb}^T$
左乘/右乘 左定右动:左乘绕定系,右乘绕动系 $R_{new} = R_{\text{fixed}} R_{old}$ (左)
$R_{new} = R_{old} R_{\text{body}}$ (右)
带下标 vs 不带下标 带下标:改变参考系/表示方向
不带下标:同系内独立旋转操作
$R_{sb}$ 表示姿态
$\text{Rot}(\hat{z},\theta)$ 表示操作

备注:在实际机器人开发中,当需要频繁进行姿态插值、滤波或优化时,建议结合四元数 (Quaternion)李代数 $\mathfrak{so}(3)/\mathfrak{se}(3)$ 使用,可避免万向节死锁并提升数值稳定性。旋转矩阵仍是理解几何关系与坐标系映射最直观的基石。