Last updated on November 26, 2023 pm
Overview
旋转表示 旋转矩阵 (Rotation Matrix) 旋转矩阵 归一化 我们通过某些计算得出的“旋转矩阵”可能不符合旋转矩阵的条件,需要对其 归一化
1 2 3 4 5 6 7 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)
旋转向量与旋转矩阵的转换,罗德里格斯公式
Rotation/Unit Quarternion Lie Group & Lie Algebra $SO(3)$ Euler Angle 旋转矩阵可以可以分解为绕各自轴对应旋转矩阵的乘积:
根据绕轴的不同,欧拉角共分为两大类,共12种,如下图(基于 右手系 )所示:
以上不同旋转轴合成的旋转矩阵,每一种都可以看成 同一旋转矩阵的两种不同物理变换 :
以 $Z_1Y_2X_3$ 进行为例,旋转矩阵表示为 $\mathbf{R} = \mathbf{R}_z \mathbf{R}_y \mathbf{R}_x$,说明:
即 绕 固定坐标轴的XYZ 和 绕运动坐标轴的ZYX 的旋转矩阵是一样的。
我们经常用的欧拉角一般就是 $Z_1Y_2X_3$ 轴序的 yaw-pitch-roll ,如下图所示:
对应的旋转矩阵为
其逆矩阵为:
上面 $\mathbf{R}_x \mathbf{R}_y \mathbf{R}_z$ 以 Cosine Matrix 的形式表示为(右手系 ):
Compare 四元数和旋转矩阵、旋转向量相比究竟有何优势?
旋转方式
旋转小量 若小旋转向量 $\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 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 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 ]; twb += Rwb*ut; Rwb = Rwb*ExpSO3 (ur); 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 根据
验证
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; Eigen::Isometry3d T1 = T0; T1.translation () += delta; 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; }