3D欧式变换之旋转ABC

Last updated on November 26, 2023 pm

Overview

旋转表示

旋转矩阵 (Rotation Matrix)

旋转矩阵 归一化

我们通过某些计算得出的“旋转矩阵”可能不符合旋转矩阵的条件,需要对其 归一化

1
2
3
4
5
6
7
// ref ORB-SLAM3
cv::Mat NormalizeRotation(const cv::Mat &R)
{
cv::Mat U,w,Vt;
cv::SVDecomp(R,w,U,Vt,cv::SVD::FULL_UV);
return U*Vt;
}

旋转向量/轴角 (Rotation Vector)

  • 旋转轴:矩阵 $\mathbf{R}$ 特征值1对应的特征向量(单位矢量)

  • 旋转角

旋转向量与旋转矩阵的转换,罗德里格斯公式 [2]

Rotation/Unit Quarternion [1]

Lie Group & Lie Algebra $SO(3)$ [1]

Euler Angle

旋转矩阵可以可以分解为绕各自轴对应旋转矩阵的乘积:

根据绕轴的不同,欧拉角共分为两大类,共12种,如下图(基于 右手系)所示:

以上不同旋转轴合成的旋转矩阵,每一种都可以看成 同一旋转矩阵的两种不同物理变换

  • 固定轴 旋转
  • 动轴 旋转

$Z_1Y_2X_3$ 进行为例,旋转矩阵表示为 $\mathbf{R} = \mathbf{R}_z \mathbf{R}_y \mathbf{R}_x$,说明:

  • 固定轴 旋转:以初始坐标系作为固定坐标系,分别先后绕固定坐标系的X、Y、Z轴 旋转;

  • 动轴 旋转:先绕 初始Z轴 旋转,再绕 变换后的Y轴 旋转,最后绕 变换后的X轴 旋转

即 绕 固定坐标轴的XYZ绕运动坐标轴的ZYX 的旋转矩阵是一样的。

我们经常用的欧拉角一般就是 $Z_1Y_2X_3$ 轴序的 yaw-pitch-roll,如下图所示:

对应的旋转矩阵为

其逆矩阵为:

上面 $\mathbf{R}_x \mathbf{R}_y \mathbf{R}_z$ 以 Cosine Matrix 的形式表示为(右手系):

Compare [4]

四元数和旋转矩阵、旋转向量相比究竟有何优势? [5] [6]

  • 平滑插值

  • 内存和运算速度更优

    • 内存上,一个四元数只占四个浮点数
    • 在四元数相乘时可以直接在这四个数上进行加减乘的基本运算,比旋转向量转换成旋转矩阵相乘后再转换回旋转向量要高效得多(Rodrigues 变换还涉及除法和三角函数等高级运算)。这些在嵌入式平台上是不小的优势
  • 浮点数运算总是会有误差的,运算越多,误差累计越多,所以理论上四元数相乘也有精度上的优势

旋转方式

  • active rotation: rotating vectors

  • passive rotation: rotating frames

旋转小量

若小旋转向量 $\Delta \boldsymbol{\phi}$,则旋转小量 一阶近似

旋转扰动

  • Local Perturbation

  • Global Perturbation

旋转更新

对于 Manifold空间 的状态 $x$,因不能直接相加,取其 Tangent空间 小量,再将其映射回 Manifold空间。

即(BCH公式)

而实际上,已知状态 旋转向量 $\theta$,其对应旋转矩阵 $R(\theta)$,旋转小量 $\Delta R(\delta \theta)$ 或 $\Delta q(\delta \theta)$ 对其进行更新,VINS-Mono、MSCKF、ORB-SLAM等开源代码中都是如下直接右乘

四元数形式

右乘更新(MSCKF、VINS-Mono)

而实际上

OKVIS中左乘

1
2
3
4
5
6
7
8
9
10
11
12
// apply small update:
template<typename Derived_delta>
inline bool Transformation::oplus(
const Eigen::MatrixBase<Derived_delta> & delta) {
r_ += delta.template head<3>();
Eigen::Vector4d dq;
double halfnorm = 0.5 * delta.template tail<3>().norm();
dq.template head<3>() = sinc(halfnorm) * 0.5 * delta.template tail<3>();
dq[3] = cos(halfnorm);
q_ = (Eigen::Quaterniond(dq) * q_);
return true;
}

李群李代数形式

g2o中更新,左乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* \brief SE3 Vertex parameterized internally with a transformation matrix
and externally with its exponential map
*/
class VertexSE3Expmap : public BaseVertex<6, SE3Quat>{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW

VertexSE3Expmap();

bool read(std::istream& is);

bool write(std::ostream& os) const;

virtual void setToOriginImpl() {
_estimate = SE3Quat();
}

virtual void oplusImpl(const double* update_) {
Eigen::Map<const Vector6d> update(update_);
setEstimate(SE3Quat::exp(update)*estimate());
}
};

ORB-SLAM3中更新,右乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ImuCamPose::Update(const double *pu)
{
Eigen::Vector3d ur, ut;
ur << pu[0], pu[1], pu[2];
ut << pu[3], pu[4], pu[5];

// Update body pose
twb += Rwb*ut;
Rwb = Rwb*ExpSO3(ur);

// Normalize rotation after 5 updates
its++;
if(its>=3)
{
NormalizeRotation(Rwb);
its=0;
}
}

旋转残差

global angular error

local angular error (VINS-Mono & ORB-SLAM3)

不能像下面这样直接减:

(1)四元数形式(VINS-Mono)

(2)李群李代数形式

g2o中,两个位姿为顶点时的残差:

1
2
3
4
5
6
7
8
void EdgeSE3Expmap::computeError() {
const VertexSE3Expmap* v1 = static_cast<const VertexSE3Expmap*>(_vertices[0]);
const VertexSE3Expmap* v2 = static_cast<const VertexSE3Expmap*>(_vertices[1]);

SE3Quat C(_measurement);
SE3Quat error_ = v2->estimate().inverse() * C * v1->estimate();
_error = error_.log();
}

旋转导数 (雅克比矩阵)

对时间求导

若角速度为 $\boldsymbol{\omega}$,那么旋转的时间导数为

对旋转求导

李代数求导

用李代数表示位姿,然后根据李代数加法来对李代数求导

其中,$\mathbf{J}_l$ 为 $SO(3)$ 的左雅克比矩阵,其定义为

w.r.t Error State

对于 欧式空间

对于 流行空间,例如旋转 $q$

扰动方式

Note: 本质是 Local 或 Global 扰动

对李群左乘或者右乘微小扰动量,然后对该扰动求导,成为左扰动和右扰动模型,这种方式 省去了计算雅克比,所以使用更为常见

对于矩阵A和B,$\exp(A)\exp(B) \neq \exp(A+B)$,错误示例:

四元数形式

Check Jacobian Matrix [3]

根据

验证

Eg. code: imu_x_fusion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  virtual void check_jacobian(
const Eigen::MatrixXd &mat_x, const Eigen::MatrixXd &mat_z) {
Eigen::Vector3d delta(0.0012, -0.00034, -0.00056);
Eigen::Isometry3d T0;
T0.matrix() = mat_x;

// perturbation on t
Eigen::Isometry3d T1 = T0;
T1.translation() += delta;
// perturbation on R
Eigen::Isometry3d T2 = T0;
T2.linear() = State::rotation_update(T2.linear(), State::delta_rot_mat(delta, 1));
Eigen::Isometry3d Tx0 = Tvw_ * T0 * Tcb_.inverse();
Eigen::Isometry3d Tx1 = Tvw_ * T1 * Tcb_.inverse();
Eigen::Isometry3d Tx2 = Tvw_ * T2 * Tcb_.inverse();
const auto &H = measurement_jacobian(mat_x, mat_z);
std::cout << "---------------------" << std::endl;
std::cout << "(purt t) p res: " << (Tx1.translation() - Tx0.translation()).transpose() << std::endl;
std::cout << "(purt t) p Hx: " << (H.block<3, 3>(0, 0) * delta).transpose() << std::endl;
std::cout << "(purt R) p res: " << (Tx2.translation() - Tx0.translation()).transpose() << std::endl;
std::cout << "(purt R) p Hx: " << (H.block<3, 3>(0, 6) * delta).transpose() << std::endl;
std::cout << "(purt R) q res: " << State::rotation_residual(Tx2.linear(), Tx0.linear()).transpose() << std::endl;
std::cout << "(purt R) q Hx: " << (H.block<3, 3>(3, 6) * delta).transpose() << std::endl;
std::cout << "---------------------" << std::endl;
}

Reference


3D欧式变换之旋转ABC
https://cgabc.xyz/posts/5e3fc7f2/
Author
Gavin Gao
Posted on
April 3, 2020
Licensed under