视觉SLAM十四讲学习记录 第四讲

之前第三讲的地址

第四讲 李群与李代数

  在第3讲中,我们介绍了三维世界中刚体运动的描述方式。并且在其中我们重点介绍了旋转的表示。不过在SLAM中,除了表示,我们还要对它们进行估计和优化。因为在SLAM中位姿是未知的,而我们需要解决形如“什么样的相机位姿最符合当前观测数据”这样的问题。一种典型的方式是把它构建成一个优化问题,求解最优的 R , t , R,t, R,t,使得误差最小化。
  如前所言,旋转矩阵自身是带有约束的(正交且行列式为1)。它们作为优化变量时,会引入额外的约束,使优化变得困难。通过李群——李代数间的转换关系,我们希望把位姿估计变成无约束的优化问题,简化求解方式。

4.1 李群与李代数基础

  第3讲中,我们介绍了旋转矩阵和变换矩阵的定义。当时,我们说三维旋转矩阵构成了特殊正交群SO(3),而变换矩阵构成了特殊欧氏群SE(3)。它们写起来像这样:
          S O ( 3 ) = { R ∈ R 3 × 3 ∣ R R T = I , d e t ( R ) = 1 } SO(3) = \{R∈\mathbb{R}^{3 × 3}|RR^T = I,det(R) = 1\} SO(3)={ RR3×3RRT=I,det(R)=1}
          S E ( 3 ) = { T = [ R t 0 T 1 ] ∈ R 4 × 4 ∣ R ∈ S O ( 3 ) , t ∈ R 3 } SE(3) = \{T=\begin{bmatrix}R&t\\\\0^T&1\end{bmatrix}∈\mathbb{R}^{4 × 4}|R ∈ SO(3),t ∈ \mathbb{R}^3\} SE(3)={ T= R0Tt1 R4×4RSO(3),tR3}
  不过,当时我们并未详细解释的含义。读者应该可以注意到,旋转矩阵也好,变换矩阵也好,**它们对加法是不封闭的。**换句话说,对任意两个旋转矩阵 R 1 , R 2 , R_1,R_2, R1,R2,按照矩阵加法的定义,和不再是一个旋转矩阵:

          R 1 + R 2 ∉ S O ( 3 ) ,    T 1 + T 2 ∉ S E ( 3 ) . R_1 + R_2 ∉ SO(3), \;T_1 + T_2 ∉ SE(3). R1+R2/SO(3),T1+T2/SE(3).

你也可以说两种矩阵并没有良好定义的加法,或者通常矩阵加法对这两个集合不封闭。相对地,它们只有一种较好的运算:乘法。 S O ( 3 ) SO(3) SO(3) S E ( 3 ) SE(3) SE(3)关于乘法是封闭的:

          R 1 R 2 ∈ S O ( 3 ) ,    T 1 T 2 ∈ S E ( 3 ) . R_1R_2 ∈ SO(3), \;T_1T_2 ∈ SE(3). R1R2SO(3),T1T2SE(3).

同时,我们也可以对任何一个旋转或变换矩阵(在乘法的意义上)求逆。我们知道,乘法对应着旋转或变换的复合,两个旋转矩阵相乘表示做了两次旋转。对于这种只有一个(良好的)运算的集合,我们称之为

4.1.1 群

  接下来,我们需要介绍一些抽象代数方面的知识。
  群(Group)是一种集合加上一种运算的代数结构。我们把集合记作 A , A, A,运算记作 ⋅ , \cdot, ,那么群可以记作 G = ( A , ⋅ ) 。 G = (A,\cdot)。 G=(A,)群要求这个运算满足以下几个条件:

  1. 封闭性: ∀ a 1 , a 2 ∈ A ,    a 1 ⋅ a 2 ∈ A . \forall a_1,a_2 \in A,\;a_1\cdot a_2\in A. a1,a2A,a1a2A.
  2. 结合律: ∀ a 1 , a 2 , a 3 ∈ A ,    ( a 1 ⋅ a 2 ) ⋅ a 3 = a 1 ⋅ ( a 2 ⋅ a 3 ) \forall a_1,a_2,a_3 \in A,\;(a_1\cdot a_2)\cdot a_3 = a_1\cdot (a_2\cdot a_3) a1,a2,a3A,(a1a2)a3=a1(a2a3)
  3. 幺元: ∃ a 0 ∈ A ,    s . t .    ∀ a ∈ A ,    a 0 ⋅ a = a ⋅ a 0 = a . \exists a_0 \in A,\;s.t.\;\forall a \in A, \;a_0\cdot a = a \cdot a_0 = a. a0A,s.t.aA,a0a=aa0=a.
  4. 逆: ∀ a ∈ A ,    ∃ a − 1 ∈ A ,    s . t .    a ⋅ a − 1 = a 0 . \forall a \in A,\;\exist a^{-1} \in A,\;s.t.\;a\cdot a^{-1} = a_0. aA,a1A,s.t.aa1=a0.

  读者可以将上述四个性质记作“封结幺逆”。容易验证,旋转矩阵集合和矩阵乘法构成群,同样,变换矩阵和矩阵乘法也构成群(因此才能称它们为旋转矩阵群和变换矩阵群)。其他常见的群包括整数的加法 ( Z , + ) , (\mathbb{Z},+), (Z,+),去掉0后的有理数的乘法(幺元为1) ( Q ∖ 0 , ⋅ ) , (\mathbb{Q} \setminus 0 ,\cdot), (Q0,)等等。矩阵中常见的群有:

  • 一般线性群 G L ( n ) GL(n) GL(n)  指 n × n n × n n×n 的可逆矩阵,它们对矩阵乘法成群。
  • 特殊正交群 S O ( n ) SO(n) SO(n)  也就是所谓的旋转矩阵群,其中 S O ( 2 ) SO(2) SO(2) S O ( 3 ) SO(3) SO(3)最为常见。
  • 特殊欧氏群 S E ( n ) SE(n) SE(n)  也就是前面提到的 n n n维欧氏变换,如 S E ( 2 ) SE(2) SE(2) S E ( 3 ) SE(3) SE(3)

  群结构保证了在群上的运算具有良好的性质,群论则是研究群的各种结构和性质的理论。对群论感兴趣的读者可以参考任意一本近世代数教材。李群是指具有连续(光滑)性质的群。像整数群 Z \mathbb{Z} Z那样离散的群没有连续性质,所以不是李群。而 S O ( n ) SO(n) SO(n) S E ( n ) SE(n) SE(n)在实数空间上是连续的。我们能够直观地想象一个刚体能够连续地在空间中运动,所以它们都是李群。由于 S O ( 3 ) SO(3) SO(3) S E ( 3 ) SE(3) SE(3)对于相机姿态估计尤其重要,所以我们主要讨论这两个李群。再从较简单的 S O ( 3 ) SO(3) SO(3)开始讨论,引出 S O ( 3 ) SO(3) SO(3)上面的李代数 s o ( 3 ) \mathfrak{so}(3) so(3)

4.1.2 李代数的引出

  考虑任意旋转矩阵 R , R, R,我们知道它满足:

          R R T = I RR^T = I RRT=I

现在,我们说, R R R是某个相机的旋转,它会随时间连续地变化,即为时间的函数: R ( t ) 。 R(t)。 R(t)由于它仍是旋转矩阵,有

          R ( t ) R ( t ) T = I R(t)R(t)^T = I R(t)R(t)T=I

在等式两边对时间求导,得到

          R ˙ ( t ) R ( t ) T + R ( t ) R ˙ ( t ) T = 0 \.{R}(t)R(t)^T + R(t)\.{R}(t)^T = 0 R˙(t)R(t)T+R(t)R˙(t)T=0

整理得

          R ˙ ( t ) R ( t ) T = − ( R ˙ ( t ) R ( t ) T ) T \.{R}(t)R(t)^T = -(\.{R}(t)R(t)^T)^T R˙(t)R(t)T=(R˙(t)R(t)T)T

  可以看出, R ˙ ( t ) R ( t ) T \.{R}(t)R(t)^T R˙(t)R(t)T是一个反对称矩阵。在之前介绍叉积时,引入了^符号,将一个向量变成了反对称矩阵。同理,对于任意反对称矩阵,我们也能找到唯一与之对应的向量,把这个运算用符号    ˇ \;\check{} ˇ 表示:

          a ∧ = A = [ 0 − a 3 a 2 a 3 0 − a 1 − a 2 a 1 0 ] , A ∨ = a a^{\land} = A = \begin{bmatrix}0&-a_3&a_2\\\\a_3&0&-a_1\\\\-a_2&a_1&0\end{bmatrix},A^{\lor} = a a=A= 0a3a2a30a1a2a10 ,A=a

  于是,由于 R ˙ ( t ) R ( t ) T \.{R}(t)R(t)^T R˙(t)R(t)T是一个反对称矩阵,我们可以找到一个三维向量 ϕ ( t ) ∈ R 3 \phi(t)\in\mathbb{R}^3 ϕ(t)R3与之对应:

          R ˙ ( t ) R ( t ) T = ϕ ( t ) ∧ . \.{R}(t)R(t)^T = \phi(t)^{\land}. R˙(t)R(t)T=ϕ(t).

  等式两边右乘 R ( t ) , R(t), R(t),由于 R R R为正交阵,有

          R ˙ ( t ) = ϕ ( t ) ∧ R ( t ) = [ 0 − ϕ 3 ϕ 2 ϕ 3 0 − ϕ 1 − ϕ 2 ϕ 1 0 ] R ( t ) . \.R(t) = \phi(t)^{\land}R(t) = \begin{bmatrix} 0&-\phi_3&\phi_2\\\\\phi_3&0&-\phi_1\\\\-\phi_2&\phi_1&0 \end{bmatrix}R(t). R˙(t)=ϕ(t)R(t)= 0ϕ3ϕ2ϕ30ϕ1ϕ2ϕ10 R(t).

  可以看到,每对旋转矩阵求一次导数,只需左乘一个 ϕ ∧ ( t ) \phi^{\land}(t) ϕ(t)矩阵即可。考虑 t 0 = 0 t_0 = 0 t0=0时,设此时旋转矩阵为 R ( 0 ) = I 。 R(0) = I。 R(0)=I按照导数定义,可以把 R ( t ) R(t) R(t) t = 0 t = 0 t=0附近进行一阶泰勒展开:

          R ( t ) ≈ R ( t 0 ) + R ˙ ( t 0 ) ( t − t 0 ) = I + ϕ ( t 0 ) ∧ ( t ) . \begin{aligned}R(t) &≈ R(t_0) + \.R{}(t_0)(t - t_0)\\&=I + \phi(t_0)^{\land}(t).\end{aligned} R(t)R(t0)+R˙(t0)(tt0)=I+ϕ(t0)(t).

  我们看到 ϕ \phi ϕ反映了 R R R的导数性质,故称它在 S O ( 3 ) SO(3) SO(3)原点附近的正切空间(Tangent Space)上。同时在 t 0 t_0 t0附近,设 ϕ \phi ϕ保持为常数 ϕ ( t 0 ) = ϕ 0 。 \phi(t_0) = \phi_0。 ϕ(t0)=ϕ0故有:

          R ˙ ( t ) = ϕ ( t 0 ) ∧ R ( t ) = ϕ 0 ∧ R ( t ) . \.{R}(t) = \phi(t_0)^{\land}R(t) = \phi_0^{\land}R(t). R˙(t)=ϕ(t0)R(t)=ϕ0R(t).

  上式是一个关于 R R R的微分方程,而且有初始值 R ( 0 ) = I R(0) = I R(0)=I,解得

          R ( t ) = exp ⁡ ( ϕ 0 ∧ t ) . R(t) = \exp{(\phi_0^{\land}t)}. R(t)=exp(ϕ0t).

  读者可以验证上式对微分方程和初始值均成立。这说明在 t = 0 t = 0 t=0附近,旋转矩阵可以由 exp ⁡ ( ϕ 0 ∧ t ) \exp{(\phi_0^{\land}t)} exp(ϕ0t)计算出来。我们看到,旋转矩阵 R R R与另一个反对称矩阵 ϕ 0 ∧ t \phi_0^{\land}t ϕ0t通过指数关系发生了联系。但是矩阵的指数是什么呢?这里我们有两个问题需要澄清:

  1. 给定某时刻的 R , R, R我们就能求得一个 ϕ , \phi, ϕ它描述了 R R R在局部的导数关系。与 R R R对应的 ϕ \phi ϕ有什么含义呢?我们说, ϕ \phi ϕ正是对应到 S O ( 3 ) SO(3) SO(3)上的李代数 s o ( 3 ) ; \mathfrak{so}(3); so(3);
  2. 其次,给定某个向量 ϕ \phi ϕ时,矩阵指数 exp ⁡ ( ϕ ∧ ) \exp{(\phi^{\land})} exp(ϕ)如何计算?反之,给定 R R R时,能否有相反的运算来计算 ϕ ? \phi? ϕ?事实上,这正是李群与李代数间的指数/对数映射。

  下面,我们会解决这两个问题。

4.1.3 李代数的定义

  每个李群都有与之对应的李代数。李代数描述了李群的局部性质,准确地说,是单位元附近的正切空间。一般的李代数的定义如下:
  李代数由一个集合 V 、 \mathbb{V}、 V一个数域 F \mathbb{F} F和一个二元运算 [ , ] [,] [,]组成。如果它们满足以下几条性质,则称 ( V , F , [ , ] ) (\mathbb{V,F,[,]}) (V,F,[,])为一个李代数,记作 g 。 \mathfrak{g}。 g

  1. 封闭性    ∀ X , Y ∈ V , [ X , Y ] ∈ V . \;\forall X,Y \in \mathbb{V},[X,Y]\in\mathbb{V}. X,YV,[X,Y]V.
  2. 双线性    ∀ X , Y , Z ∈ V , a , b ∈ F , \;\forall X,Y,Z \in \mathbb{V},a,b \in \mathbb{F}, X,Y,ZV,a,bF,
              [ a X + b Y , Z ] = a [ X , Z ] + b [ Y , Z ] ,    [ Z , a X + b Y ] = a [ Z , X ] + b [ Z , Y ] . [aX + bY,Z] = a[X,Z] + b[Y,Z],\;[Z,aX + bY] = a[Z,X] + b[Z,Y]. [aX+bY,Z]=a[X,Z]+b[Y,Z],[Z,aX+bY]=a[Z,X]+b[Z,Y].
  3. 自反性    ∀ X ∈ V , [ X , X ] = 0. \;\forall X \in \mathbb{V},[X,X] = 0. XV,[X,X]=0.
  4. 雅可比等价    ∀ X , Y , Z ∈ V , [ X , [ Y , Z ] ] + [ Z , [ X , Y ] ] + [ Y , [ Z , X ] ] = 0 \;\forall X,Y,Z \in \mathbb{V},[X,[Y,Z]] + [Z,[X,Y]] + [Y,[Z,X]] = 0 X,Y,ZV,[X,[Y,Z]]+[Z,[X,Y]]+[Y,[Z,X]]=0

其中二元运算被称为李括号。从表面上看,李代数所需要的性质还是挺多的。相比于群中的较为简单的二元运算,李括号表达了两个元素的差异。它不要求结合律,而要求元素和自己做李括号之后为零的性质。作为例子,三维向量 R 3 \mathbb{R}^3 R3上定义的叉积×是一种李括号,因此 g = ( R 3 , R , × ) \mathfrak{g} = (\mathbb{R}^3,\mathbb{R},×) g=(R3,R,×)构成了一个李代数。

4.1.4 李代数 s o ( 3 ) \mathfrak{so}(3) so(3)

  之前提到的 ϕ , \phi, ϕ事实上是一种李代数。 S O ( 3 ) SO(3) SO(3)对应的的是定义在 R 3 \mathbb{R}^3 R3上的向量,我们记作 ϕ \phi ϕ。根据前面的推导,每个 ϕ \phi ϕ都可以生成一个反对称矩阵:

          Φ = ϕ ∧ = [ 0 − ϕ 3 ϕ 2 ϕ 3 0 − ϕ 1 − ϕ 2 ϕ 1 0 ] ∈ R 3 × 3 . \Phi = \phi^{\land} = \begin{bmatrix} 0&-\phi_3&\phi_2\\\\\phi_3&0&-\phi_1\\\\-\phi_2&\phi_1&0 \end{bmatrix}\in \mathbb{R}^{3×3}. Φ=ϕ= 0ϕ3ϕ2ϕ30ϕ1ϕ2ϕ10 R3×3.
  在此定义下,两个向量 ϕ 1 , ϕ 2 \phi_1,\phi_2 ϕ1,ϕ2的李括号为

          [ ϕ 1 , ϕ 2 ] = ( Φ 1 Φ 2 − Φ 2 Φ 1 ) ∨ . [\phi_1,\phi_2] = (\Phi_1\Phi_2 - \Phi_2\Phi_1)^{\lor}. [ϕ1,ϕ2]=(Φ1Φ2Φ2Φ1).

  读者可以验证该定义下的李括号是否满足上面的四条性质。由于向量 ϕ \phi ϕ与反对称矩阵是一一对应的,在不引起歧义的情况下,就说 s o ( 3 ) \mathfrak{so}(3) so(3)的元素是三维向量或者三维反对称矩阵,不加区别:

          s o ( 3 ) = { ϕ ∈ R 3 , Φ = ϕ ∧ ∈ R 3 × 3 } . \mathfrak{so}(3) = \{\phi \in \mathbb{R}^3,\Phi = \phi^{\land} \in \mathbb{R}^{3 × 3}\}. so(3)={ ϕR3,Φ=ϕR3×3}.

有些书里也会用 ϕ ^ \hat{\phi} ϕ^这样的符号表示反对称,但意义是一样的。至此,我们已清楚了 s o ( 3 ) \mathfrak{so}(3) so(3)的内容。它们是一个由三维向量组成的,每个向量对应一个反对称矩阵,可以用于表达旋转矩阵的导数。它与 S O ( 3 ) SO(3) SO(3)的关系由指数映射给定:

          R = exp ⁡ ( ϕ ∧ ) . R = \exp{(\phi^{\land})}. R=exp(ϕ).

指数映射会在稍后介绍。由于已经介绍了 s o ( 3 ) , \mathfrak{so}(3), so(3),我们顺带再来看看 S E ( 3 ) SE(3) SE(3)对应的李代数。

4.1.5 李代数 s e ( 3 ) \mathfrak{se}(3) se(3)

  对于 S E ( 3 ) SE(3) SE(3),也有对应的李代数 s e ( 3 ) 。 \mathfrak{se}(3)。 se(3)为节省篇幅,这里就不介绍如何引出 s e ( 3 ) \mathfrak{se}(3) se(3)了。与 s o ( 3 ) \mathfrak{so}(3) so(3)相似, s e ( 3 ) \mathfrak{se}(3) se(3)位于 R 6 \mathbb{R}^6 R6空间中:

          s e ( 3 ) = { ξ = [ ρ ϕ ] ∈ R 6 , ρ ∈ R 3 , ϕ ∈ s o ( 3 ) , ξ ∧ = [ ϕ ∧ ρ 0 T 0 ] ∈ R 4 × 4 } . \mathfrak{se}(3) = \left\{ \xi = \begin{bmatrix} \rho\\\\\phi\end{bmatrix}\in\mathbb{R}^6,\rho\in\mathbb{R}^3,\phi\in\mathfrak{so}(3),\xi^{\land} = \begin{bmatrix}\phi^{\land}&\rho\\\\0^T&0\end{bmatrix}\in\mathbb{R}^{4×4}\right\}. se(3)= ξ= ρϕ R6,ρR3,ϕso(3),ξ= ϕ0Tρ0 R4×4 .

我们把每个 s e ( 3 ) \mathfrak{se}(3) se(3)元素记作 ξ , \xi, ξ它是一个六维向量。前三维为平移,记作 ρ ; \rho; ρ后三维为旋转,记作 ϕ , \phi, ϕ实质上是 s o ( 3 ) \mathfrak{so}(3) so(3)元素。同时,我们拓展了    ˆ \;\^{} ˆ 符号的含义。在 s e ( 3 ) \mathfrak{se}(3) se(3)中,同样使用    ˆ \;\^{} ˆ 符号,将一个六维向量转换成四维矩阵,但这里不再表示反对称:

          ξ ∧ = [ ϕ ∧ ρ 0 T 0 ] ∈ R 4 × 4 . \xi^{\land} = \begin{bmatrix}\phi^{\land}&\rho\\\\0^T&0\end{bmatrix}\in\mathbb{R}^{4×4}. ξ= ϕ0Tρ0 R4×4.

  我们仍使用    ˆ \;\^{} ˆ    ˇ \;\check{} ˇ 符号指代“从向量到矩阵”和“从矩阵到向量”的关系,以保持和 s o ( 3 ) \mathfrak{so}(3) so(3)上的一致性。它们依旧是一一对应的。读者可以简单地把 s e ( 3 ) \mathfrak{se}(3) se(3)理解成“由一个平移加上一个 s o ( 3 ) \mathfrak{so}(3) so(3)元素构成的向量”(尽管这里的 ρ \rho ρ还不直接是平移)。同样,李代数 s e ( 3 ) \mathfrak{se}(3) se(3)也有类似于 s o ( 3 ) \mathfrak{so}(3) so(3)的李括号:

          [ ξ 1 , ξ 2 ] = ( ξ 1 ∧ ξ 2 ∧ − ξ 2 ∧ ξ 1 ∧ ) ∨ . [\xi_1,\xi_2] = (\xi_1^{\land}\xi_2^{\land} - \xi_2^{\land}\xi_1^{\land})^{\lor}. [ξ1,ξ2]=(ξ1ξ2ξ2ξ1).

  读者可以验证它是否满足李代数的定义。至此,我们已经见过两种重要的李代数 s o ( 3 ) \mathfrak{so}(3) so(3) s e ( 3 ) \mathfrak{se}(3) se(3)了。

4.2 指数与对数映射

4.2.1 S O ( 3 ) SO(3) SO(3)上的指数映射

  现在来考虑第二个问题:如何计算 exp ⁡ ( ϕ ∧ ) ? \exp{(\phi^{\land})}? exp(ϕ)?显然它是一个矩阵的指数,在李群和李代数中,称为指数映射(Exponential Map)。同样,我们会先讨论 s o ( 3 ) \mathfrak{so}(3) so(3)的指数映射,再讨论 s e ( 3 ) \mathfrak{se}(3) se(3)的情形。
  任意矩阵的指数映射可以写成一个泰勒展开,但是只有在收敛的情况下才会有结果,其结果仍是一个矩阵:

          exp ⁡ ( A ) = ∑ n = 0 ∞ 1 n ! A n . \exp(A) =\sum\limits_{n=0}^\infty \frac{1}{n!}A^n. exp(A)=n=0n!1An.

  同样地,对 s o ( 3 ) \mathfrak{so}(3) so(3)中的任意元素 ϕ , \phi, ϕ我们也可按此方式定义它的指数映射:

          exp ⁡ ( ϕ ∧ ) = ∑ n = 0 ∞ 1 n ! ( ϕ ∧ ) n . \exp(\phi^{\land}) =\sum\limits_{n=0}^\infty \frac{1}{n!}(\phi^{\land})^n. exp(ϕ)=n=0n!1(ϕ)n.

但这个定义没法直接计算,因为我们不想计算矩阵的无穷次幂。下面我们推导一种计算指数映射的简便方法。由于 ϕ \phi ϕ是三维向量,我们可以定义它的模长和方向,分别记作 θ \theta θ a , a, a于是有 ϕ = θ a 。 \phi = \theta a。 ϕ=θa这里 a a a是一个长度为1的方向向量,即 ∥ a ∥ = 1 。 \left \Vert a \right \Vert = 1。 a=1首先,对于 a ∧ , a^{\land}, a,有以下两条性质:

          a ∧ a ∧ = [ − a 2 2 − a 3 2 a 1 a 2 a 1 a 3 a 1 a 2 − a 1 2 − a 3 2 a 2 a 3 a 1 a 3 a 2 a 3 − a 1 2 − a 2 2 ] = a a T − I , a^{\land}a^{\land} = \begin{bmatrix}-a^2_2 - a_3^2&a_1a_2&a_1a_3\\\\a_1a_2&-a_1^2-a_3^2&a_2a_3\\\\a_1a_3&a_2a_3&-a_1^2-a_2^2\end{bmatrix} = aa^T - I, aa= a22a32a1a2a1a3a1a2a12a32a2a3a1a3a2a3a12a22 =aaTI,

  以及

          a ∧ a ∧ a ∧ = a ∧ ( a a T − I ) = − a ∧ . a^{\land}a^{\land}a^{\land} = a^{\land}(aa^T - I) = -a^{\land}. aaa=a(aaTI)=a.

这两个式子提供了处理 a ∧ a^{\land} a高阶项的方法。我们可以把指数映射写成

          exp ⁡ ( ϕ ∧ ) = exp ⁡ ( θ a ∧ ) = ∑ n = 0 ∞ 1 n ! ( θ a ∧ ) n = I + θ a ∧ + 1 2 ! θ 2 a ∧ a ∧ + 1 3 ! θ 3 a ∧ a ∧ a ∧ + 1 4 ! θ 4 ( a ∧ ) 4 + ⋅ ⋅ ⋅ = a a T − a ∧ a ∧ + θ a ∧ + 1 2 ! θ 2 a ∧ a ∧ − 1 3 ! θ 3 a ∧ − 1 4 ! θ 4 ( a ∧ ) 2 + ⋅ ⋅ ⋅ = a a T + ( θ − 1 3 ! θ 3 + 1 5 ! θ 5 − ⋅ ⋅ ⋅ ) ⏟ sin ⁡ θ a ∧ − ( 1 − 1 2 ! θ 2 + 1 4 ! θ 4 − ⋅ ⋅ ⋅ ) ⏟ cos ⁡ θ a ∧ a ∧ = a ∧ a ∧ + I + sin ⁡ θ a ∧ − cos ⁡ θ a ∧ a ∧ = ( 1 − cos ⁡ θ ) a ∧ a ∧ + I + sin ⁡ θ a ∧ = cos ⁡ θ I + ( 1 − cos ⁡ θ ) a a T + sin ⁡ θ a ∧ \begin{aligned}\exp(\phi^{\land}) = \exp(\theta a^{\land}) &= \sum\limits_{n = 0}^{\infty}\frac{1}{n!}(\theta a^{\land})^n\\ &=I + \theta a^{\land} + \frac{1}{2!}\theta^{2}a^{\land}a^{\land} + \frac{1}{3!}\theta^{3}a^{\land}a^{\land}a^{\land} + \frac{1}{4!}\theta^{4}(a^{\land})^4 + ···\\&= aa^T - a^{\land}a^{\land} + \theta a^{\land} + \frac{1}{2!}\theta^{2}a^{\land}a^{\land} - \frac{1}{3!}\theta^{3}a^{\land} - \frac{1}{4!}\theta^{4}(a^{\land})^2 + ···\\&= aa^T + \begin{matrix}\underbrace{(\theta - \frac{1}{3!}\theta^3 + \frac{1}{5!}\theta^5 - ···)}\\\sin{\theta}\end{matrix}a^{\land} - \begin{matrix}\underbrace{(1 - \frac{1}{2!}\theta^2 + \frac{1}{4!}\theta^4 - ···)}\\\cos{\theta}\end{matrix}a^{\land}a^{\land}\\&= a^{\land}a^{\land} + I + \sin{\theta}a^{\land} - \cos{\theta}a^{\land}a^{\land}\\&= (1-\cos{\theta})a^{\land}a^{\land} + I + \sin{\theta}a^{\land}\\&= \cos{\theta}I + (1-\cos{\theta})aa^{T} + \sin{\theta}a^{\land} \end{aligned} exp(ϕ)=exp(θa)=n=0n!1(θa)n=I+θa+2!1θ2aa+3!1θ3aaa+4!1θ4(a)4+⋅⋅⋅=aaTaa+θa+2!1θ2aa3!1θ3a4!1θ4(a)2+⋅⋅⋅=aaT+ (θ3!1θ3+5!1θ5⋅⋅⋅)sinθa (12!1θ2+4!1θ4⋅⋅⋅)cosθaa=aa+I+sinθacosθaa=(1cosθ)aa+I+sinθa=cosθI+(1cosθ)aaT+sinθa

  最后,得到一个似曾相识的式子:

          exp ⁡ ( θ a ∧ ) = cos ⁡ θ I + ( 1 − cos ⁡ θ ) a a T + sin ⁡ θ a ∧ . \exp{(\theta a^{\land})} = \cos{\theta}I + (1-\cos{\theta})aa^{T} + \sin{\theta}a^{\land}. exp(θa)=cosθI+(1cosθ)aaT+sinθa.

  回想第3讲的内容,它和罗德里格斯公式如出一辙。这表明, s o ( 3 ) \mathfrak{so}(3) so(3)实际上就是由所谓的旋转向量组成的空间,而指数映射即罗德里格斯公式。通过它们,我们把 s o ( 3 ) \mathfrak{so}(3) so(3)中任意一个向量对应到了一个位于 S O ( 3 ) SO(3) SO(3)中的旋转矩阵。反之,如果定义对数映射,也能把 S O ( 3 ) SO(3) SO(3)中的元素对应到 s o ( 3 ) \mathfrak{so}(3) so(3)中:

          ϕ = ln ⁡ ( R ) ∨ = ( ∑ n = 0 ∞ ( − 1 ) n n + 1 ( R − I ) n + 1 ) ∨ . \phi = \ln{(R)^{\lor}} = \left(\sum\limits_{n = 0}^{\infty}\frac{(-1)^n}{n + 1}(R - I)^{n + 1} \right)^{\lor}. ϕ=ln(R)=(n=0n+1(1)n(RI)n+1).

和指数映射一样,我们没必要直接用泰勒展开计算对数映射。在第3讲中,我们介绍过如何根据旋转矩阵计算对应的李代数,利用迹的性质分别求解转角和转轴,采用这种方式更省事。
  现在,我们介绍了指数映射的计算方法。那么,指数映射有何性质呢?是否对于任意的 R R R都能找到一个唯一的 ϕ ? \phi? ϕ?很遗憾,指数映射只是一个满射,并不是单射(有关满射、单射、双射参考图灵的猫i的文章)。这意味着每个 S O ( 3 ) SO(3) SO(3)中的元素,都可以找到一个 s o ( 3 ) \mathfrak{so}(3) so(3)元素与之对应;但是可能存在多个 s o ( 3 ) \mathfrak{so}(3) so(3)中的元素,对应到同一个 S O ( 3 ) SO(3) SO(3)。至少对于旋转角 θ \theta θ,我们知道多转 360 ° 360° 360°和没有转是一样的——它既有周期性。但是,如果我们把旋转角度固定在 ± π ±\pi ±π之间,那么李群和李代数元素是一一对应的。
   S O ( 3 ) SO(3) SO(3) s o ( 3 ) \mathfrak{so}(3) so(3)的结论似乎在我们的意料之中。它和我们前面讲的旋转向量与旋转矩阵很相似,而指数映射即罗德里格斯公式。旋转矩阵的导数可以由旋转向量指定,指导着如何在旋转矩阵中进行微积分运算。

4.2.2 S E ( 3 ) SE(3) SE(3)上的指数映射

  下面介绍 s e ( 3 ) \mathfrak{se}(3) se(3)上的指数映射。如上,形式如下:

          exp ⁡ ( ξ ∧ ) = [ ∑ n = 0 ∞ 1 n ! ( ϕ ∧ ) n ∑ n = 0 ∞ 1 ( n + 1 ) ! ( ϕ ∧ ) n ρ 0 T 1 ] = Δ [ R J p 0 T 1 ] = T \begin{aligned}\exp{(\xi^{\land})} &= \begin{bmatrix} \sum\limits_{n = 0}^{\infty}\frac{1}{n!}(\phi^{\land})^n&\sum\limits_{n = 0}^{\infty}\frac{1}{(n + 1)!}(\phi^{\land})^n\rho\\\\0^T&1\end{bmatrix}\\\\ &\overset{\Delta}{=}\begin{bmatrix}R&Jp\\\\0^T&1\end{bmatrix} = T \end{aligned} exp(ξ)= n=0n!1(ϕ)n0Tn=0(n+1)!1(ϕ)nρ1 =Δ R0TJp1 =T

  只要有一点耐心,可以照着 s o ( 3 ) \mathfrak{so}(3) so(3)上的做法推导,把 exp ⁡ \exp exp进行泰勒展开推导此事。令 ϕ = θ a , \phi = \theta a, ϕ=θa,其中 a a a为单位向量,则

          ∑ n = 0 ∞ 1 ( n + 1 ) ! ( ϕ ∧ ) n = I + 1 2 ! θ a ∧ + 1 3 ! θ 2 ( a ∧ ) 2 + 1 4 ! θ 3 ( a ∧ ) 3 + 1 5 ! θ 4 ( a ∧ ) 4 ⋅ ⋅ ⋅ = 1 θ ( 1 2 ! θ 2 − 1 4 ! θ 4 + ⋅ ⋅ ⋅ ) ( a ∧ ) + 1 θ ( 1 3 ! θ 3 − 1 5 ! θ 5 + ⋅ ⋅ ⋅ ) ( a ∧ ) 2 + I = 1 θ ( 1 − cos ⁡ θ ) ( a ∧ ) + θ − sin ⁡ θ θ ( a a T − I ) + I = sin ⁡ θ θ I + ( 1 − sin ⁡ θ θ ) a a T + 1 − cos ⁡ θ θ a ∧ = d e f J . \begin{aligned} \sum\limits_{n = 0}^{\infty}\frac{1}{(n + 1)!}(\phi ^{\land})^n&=I + \frac{1}{2!}\theta a^{\land} + \frac{1}{3!}\theta^{2}(a^{\land})^2 + \frac{1}{4!}\theta^{3}(a^{\land})^3 +\frac{1}{5!}\theta^{4}(a^{\land})^4 ···\\&= \frac{1}{\theta}\begin{matrix}(\frac{1}{2!}\theta^2 - \frac{1}{4!}\theta^4 + ···)\end{matrix}(a^{\land}) + \frac{1}{\theta}\begin{matrix}(\frac{1}{3!}\theta^3 - \frac{1}{5!}\theta^5 + ···)\end{matrix}(a^{\land})^2 + I\\&= \frac{1}{\theta}(1-\cos{\theta})(a^{\land}) + \frac{\theta - \sin{\theta}}{\theta}(aa^T - I) + I\\&= \frac{\sin{\theta}}{\theta}I + (1-\frac{\sin\theta}{\theta})aa^{T} + \frac{1 - \cos{\theta}}{\theta}a^{\land} \overset{def}{=}J. \end{aligned} n=0(n+1)!1(ϕ)n=I+2!1θa+3!1θ2(a)2+4!1θ3(a)3+5!1θ4(a)4⋅⋅⋅=θ1(2!1θ24!1θ4+⋅⋅⋅)(a)+θ1(3!1θ35!1θ5+⋅⋅⋅)(a)2+I=θ1(1cosθ)(a)+θθsinθ(aaTI)+I=θsinθI+(1θsinθ)aaT+θ1cosθa=defJ.

  从结果上看, ξ \xi ξ的指数映射左上角的 R R R是我们熟知的 S O ( 3 ) SO(3) SO(3)中的元素,与 s e ( 3 ) \mathfrak{se}(3) se(3)中的旋转部分 ϕ \phi ϕ对应。而右上角的 J J J由上面的推导给出:

          J = sin ⁡ θ θ I + ( 1 − sin ⁡ θ θ ) a a T + 1 − cos ⁡ θ θ a ∧ . J = \frac{\sin{\theta}}{\theta}I + (1-\frac{\sin\theta}{\theta})aa^{T} + \frac{1 - \cos{\theta}}{\theta}a^{\land}. J=θsinθI+(1θsinθ)aaT+θ1cosθa.

  该式与罗德里格斯公式有些相似,但不完全一样。我们看到,平移部分经过指数映射之后,发生了一次以 J J J为系数矩阵的线性变换。
  同样地,虽然我们也可以类比推得对数映射,不过根据变换矩阵 T T T s o ( 3 ) \mathfrak{so}(3) so(3)上的对应向量有更省事的方式:从左上角的 R R R计算旋转向量,而右上角的 t t t满足:

          t = J ρ . t = J\rho. t=Jρ.

  由于 J J J可以由 ϕ \phi ϕ得到,所以这里的 ρ \rho ρ也可以由此线性方程解得。现在,我们已经弄清了李群、李代数的定义与相互的转换关系,如下图所示:
在这里插入图片描述

4.3 李代数求导与扰动模型

4.2.1 BCH公式与近似形式

  使用李代数的一大动机是进行优化,而在优化过程中导数是非常必要的信息。下面来考虑一个问题。虽然我们已经清楚了 S O ( 3 ) SO(3) SO(3) S E ( 3 ) SE(3) SE(3)上的李群与李代数关系,但是,当在 S O ( 3 ) SO(3) SO(3)中完成两个矩阵乘法时,李代数中 s o ( 3 ) \mathfrak{so}(3) so(3)上发生了什么改变呢?反过来说,当 s o ( 3 ) \mathfrak{so}(3) so(3)上做两个李代数的加法时, S O ( 3 ) SO(3) SO(3)是否对应着两个矩阵的乘积?如果成立,相当于:

          exp ⁡ ( ϕ 1 ∧ ) exp ⁡ ( ϕ 2 ∧ ) = exp ⁡ ( ( ϕ 1 + ϕ 2 ) ∧ ) ? \exp(\phi^{\land}_1)\exp(\phi^{\land}_2) = \exp((\phi_1 + \phi_2)^{\land})? exp(ϕ1)exp(ϕ2)=exp((ϕ1+ϕ2))?

  如果 ϕ 1 , ϕ 2 \phi_1,\phi_2 ϕ1,ϕ2为标量,那么显然该式成立;但此时我们计算的是矩阵的指数函数,而非标量的指数。换言之,我们在研究下式是否成立:

          ln ⁡ ( exp ⁡ ( A ) exp ⁡ ( B ) ) = A + B ? \ln(\exp(A)\exp(B)) = A + B? ln(exp(A)exp(B))=A+B

  很遗憾,该式在矩阵时并不成立。两个李代数指数映射乘积的完整形式,由Baker-Campbell-Hausdorff公式(BCH公式)给出。由于其完整形式较复杂,我们只给出其展开式的前几项:

          ln ⁡ ( e x p ( A ) exp ⁡ ( B ) ) = A + B + 1 2 [ A , B ] + 1 12 [ A , [ A , B ] ] − 1 12 [ B , [ A , B ] ] + ⋅ ⋅ ⋅ \ln(exp(A)\exp(B)) = A + B + \frac{1}{2}[A,B] +\frac{1}{12}[A,[A,B]]-\frac{1}{12}[B,[A,B]] + ··· ln(exp(A)exp(B))=A+B+21[A,B]+121[A,[A,B]]121[B,[A,B]]+⋅⋅⋅

  其中 [    ] [\;] []为李括号。BCH公式告诉我们,当处理两个矩阵指数之积时,它们会产生一些由李括号组成的余项。特别地,考虑 S O ( 3 ) SO(3) SO(3)上的李代数 ln ⁡ ( e x p ( ϕ 1 ∧ ) exp ⁡ ( ϕ 2 ∧ ) ) ∨ , \ln(exp(\phi_1^{\land})\exp(\phi_2^{\land}))^{\lor}, ln(exp(ϕ1)exp(ϕ2)), ϕ 1 \phi_1 ϕ1 ϕ 2 \phi_2 ϕ2为小量时,小量二次以上的项都可以被忽略。此时,BCH拥有线性近似表达:

          ln ⁡ ( e x p ( ϕ 1 ∧ ) exp ⁡ ( ϕ 2 ∧ ) ) ∨ ≈ { J l ( ϕ 2 ) − 1 ϕ 1 + ϕ 2 当 ϕ 1 为小量 , J r ( ϕ 1 ) − 1 ϕ 2 + ϕ 1 当 ϕ 2 为小量 , \ln(exp(\phi_1^{\land})\exp(\phi_2^{\land}))^{\lor} ≈\begin{cases} J_l(\phi_2)^{-1}\phi_1 + \phi_2& 当\phi_1为小量, \\ J_r(\phi_1)^{-1}\phi_2 + \phi_1& 当\phi_2为小量, \end{cases} ln(exp(ϕ1)exp(ϕ2)){ Jl(ϕ2)1ϕ1+ϕ2Jr(ϕ1)1ϕ2+ϕ1ϕ1为小量,ϕ2为小量,

  以第一个近似为例。该式告诉我们,当对一个旋转矩阵 R 2 R_2 R2(李代数为 ϕ 2 \phi_2 ϕ2)左乘一个微小旋转矩阵 R 1 R_1 R1(李代数为 ϕ 1 \phi_1 ϕ1)时,可以近似地看作,在原有的李代数 ϕ 2 \phi_2 ϕ2上加上了一项 J l ( ϕ 2 ) − 1 ϕ 1 。 J_l(\phi_2)^{-1}\phi_1。 Jl(ϕ2)1ϕ1同理,第二个近似描述了右乘一个微小位移的情况。于是李代数在BCH近似下,分成了左乘近似和右乘近似两种,在使用时我们须注意使用的是左乘模型还是右乘模型。
  本书以左乘为例。左乘BCH近似雅可比 J l J_l Jl事实上就是式(4.27)的内容:

          J l = J = sin ⁡ θ θ I + ( 1 − sin ⁡ θ θ ) a a T + 1 −    cos ⁡ θ θ a ∧ . J_l = J = \frac{\sin\theta}{\theta}I + (1 - \frac{\sin\theta}{\theta})aa^T + \frac{1 - \;\cos\theta}{\theta}a^{\land}. Jl=J=θsinθI+(1θsinθ)aaT+θ1cosθa.

  它的逆为

          J l − 1 = θ 2 cot ⁡ θ 2 I + ( 1 − θ 2 cot ⁡ θ 2 ) a a T − θ 2 a ∧ . J_l^{-1} = \frac{\theta}{2}\cot\frac{\theta}{2}I + (1 - \frac{\theta}{2}\cot\frac{\theta}{2})aa^T - \frac{\theta}{2}a^{\land}. Jl1=2θcot2θI+(12θcot2θ)aaT2θa.

而右乘雅可比仅需要对自变量取负号即可:

          J r ( ϕ ) = J l ( − ϕ ) . J_r(\phi) = J_l(-\phi). Jr(ϕ)=Jl(ϕ).

  这样,我们就可以谈论李群乘法与李代数加法的关系了。
  为了方便读者理解,我们重新叙述BCH近似的意义。假定对某个旋转 R , R, R, 对应的李代数为 ϕ 。 \phi。 ϕ我们给它左乘一个微小旋转,记作 Δ R , \Delta R, ΔR,对应的李代数为 Δ ϕ 。 \Delta \phi。 Δϕ那么,在李群上,得到的结果就是 Δ R ⋅ R , \Delta R·R, ΔRR, 而在李代数上,根据BCH近似,为 J l − 1 ( ϕ ) Δ ϕ + ϕ 。 J_l^{-1}(\phi)\Delta\phi + \phi。 Jl1(ϕ)Δϕ+ϕ合并起来,可以简单地写成:

          exp ⁡ ( Δ ϕ ∧ ) exp ⁡ ( ϕ ∧ ) = exp ⁡ ( ( ϕ + J l − 1 ( ϕ ) Δ ϕ ) ∧ ) . \exp(\Delta\phi^{\land})\exp(\phi^{\land}) = \exp((\phi + J_l^{-1}(\phi)\Delta\phi)^{\land}). exp(Δϕ)exp(ϕ)=exp((ϕ+Jl1(ϕ)Δϕ)).

  反之,如果我们在李代数上进行加法,让一个 ϕ \phi ϕ加上 Δ ϕ , \Delta\phi, Δϕ让一个 ϕ \phi ϕ加上 Δ ϕ , \Delta \phi, Δϕ那么可以近似为李群上带左右雅可比的乘法:

          exp ⁡ ( ( ϕ + Δ ϕ ) ∧ ) ) = exp ⁡ ( ( J l Δ ϕ ) ∧ ) exp ⁡ ( ϕ ∧ ) = exp ⁡ ( ϕ ∧ ) exp ⁡ ( ( J r Δ ϕ ) ∧ ) . \exp((\phi + \Delta\phi)^{\land})) = \exp((J_l\Delta\phi)^{\land})\exp(\phi^{\land}) = \exp(\phi^{\land})\exp((J_r\Delta\phi)^{\land}). exp((ϕ+Δϕ)))=exp((JlΔϕ))exp(ϕ)=exp(ϕ)exp((JrΔϕ)).

  这就为之后李代数上做微积分提供了理论基础。同样地,对于 S E ( 3 ) , SE(3), SE(3),也有类似的BCH近似:

          exp ⁡ ( Δ ξ ∧ ) exp ⁡ ( ξ ∧ ) ≈ exp ⁡ ( ( J l − 1 Δ ξ + ξ ) ∧ ) , \exp(\Delta\xi^{\land})\exp(\xi^{\land}) ≈ \exp((\mathcal{J}_l^{-1}\Delta\xi + \xi)^{\land}), exp(Δξ)exp(ξ)exp((Jl1Δξ+ξ)),
          exp ⁡ ( ξ ∧ ) exp ⁡ ( Δ ξ ∧ ) ≈ exp ⁡ ( ( J r − 1 Δ ξ + ξ ) ∧ ) . \exp(\xi^{\land})\exp(\Delta\xi^{\land}) ≈ \exp((\mathcal{J}_r^{-1}\Delta\xi + \xi)^{\land}). exp(ξ)exp(Δξ)exp((Jr1Δξ+ξ)).

  这里$\mathcal{J_l}形式比较复杂,它是一个 6 × 6 6×6 6×6的矩阵。由于我们在计算中没有用到该雅可比,故这里略去它的实际形式。

4.3.2 SO(3)上的李代数求导

  下面来讨论一个带有李代数的函数,以及关于该李代数求导的问题。该问题有很强的实际背景。在SLAM中,我们要估计一个相机的位置和姿态,该位姿是由 S O ( 3 ) SO(3) SO(3)上的旋转矩阵或 S E ( 3 ) SE(3) SE(3)上的变换矩阵描述的。不妨设某个时刻小萝卜的位姿为 T 。 T。 T它观察到了一个世界坐标位于 p p p的点,产生了一个观测数据 z 。 z。 z那么,由坐标变换关系知:

          z = T p + w . z = Tp + w. z=Tp+w.

其中 w w w为随机噪声。由于它的存在, z z z往往不可能精确地满足 z = T p z = Tp z=Tp的关系。所以,我们通常会计算理想的观测与实际数据的我误差:

          e = z − T p . e = z - Tp. e=zTp.

  假设一共有 N N N个这样的路标点和观测,于是就有 N N N个上式。那么,对小萝卜进行位姿估计,相当于寻找一个最优的 T , T, T使得整体误差最小化:

          min ⁡ T J ( T ) = ∑ i = 1 N ∥ z i − T p i ∥ 2 2 . \min\limits_TJ(T) = \sum\limits^N_{i = 1}\Vert z_i - Tp_i\Vert_2^2. TminJ(T)=i=1NziTpi22.

  求解此问题,需要计算目标函数 J J J关于变换矩阵 T T T的导数。我们把具体的算法留到后面再讲。这里的重点是,**我们经常会构建与位姿有关的函数,然后讨论该函数关于位姿的导数,以调整当前的估计值。**然而, S O ( 3 ) , SO(3), SO(3), S E ( 3 ) SE(3) SE(3)上并没有良好定义的加法,它们只是群。如果我们把 T T T当成一个普通矩阵来处理优化,就必须对它加以约束。而从李代数角度来说,由于李代数由向量组成,具有良好的加法运算,因此,使用李代数解决求导问题的思路分为两种:

  1. 用李代数表示姿态,然后根据李代数加法对李代数求导。
  2. 对李群左乘右乘微小扰动,然后对该扰动求导,称为左扰动和右扰动模型。

  第一种方式对应到李代数的求导模型,而第二种方式则对应到扰动模型。下面讨论下这两种思路的异同。

4.3.3 李代数求导

  首先,考虑 S O ( 3 ) SO(3) SO(3)上的情况。假设我们对一个空间点 p p p进行了旋转,得到了 R p Rp Rp。现在,要计算旋转之后点的坐标相对于旋转的导数,我们非正式地记为

                   ∂ ( R p ) ∂ ( R ) \frac{\partial(Rp)}{\partial(R)} (R)(Rp)

由于 S O ( 3 ) SO(3) SO(3)没有加法,所以该导数无法按照导数的定义进行计算。设 R R R对应的李代数为 ϕ , \phi, ϕ我们转而计算:

                   ∂ ( exp ⁡ ( ϕ ∧ ) p ) ∂ ϕ . \frac{\partial(\exp(\phi^{\land})p)}{\partial\phi}. ϕ(exp(ϕ)p).

  按照导数的定义,有

          ∂ ( exp ⁡ ( ϕ ∧ ) p ) ∂ ϕ = lim ⁡ δ ϕ → 0 exp ⁡ ( ( ϕ + δ ϕ ) ∧ ) p − exp ⁡ ( ϕ ∧ ) p δ ϕ = lim ⁡ δ ϕ → 0 exp ⁡ ( ( J l δ ϕ ) ∧ ) exp ⁡ ( ϕ ∧ ) p − exp ⁡ ( ϕ ∧ ) p δ ϕ = lim ⁡ δ ϕ → 0 ( I + ( J l δ ϕ ) ∧ ) exp ⁡ ( ϕ ∧ ) p − exp ⁡ ( ϕ ∧ ) p δ ϕ = lim ⁡ δ ϕ → 0 ( J l δ ϕ ) ∧ exp ⁡ ( ϕ ∧ ) p δ ϕ = lim ⁡ δ ϕ → 0 − ( exp ⁡ ( ϕ ∧ ) p ) ∧ J l δ ϕ δ ϕ = − ( R p ) ∧ J l \begin{aligned} \frac{\partial(\exp(\phi^{\land})p)}{\partial\phi}&= \lim\limits_{\delta\phi\rightarrow0}\frac{\exp((\phi + \delta\phi)^{\land})p - \exp(\phi^{\land})p}{\delta\phi}\\\\&= \lim\limits_{\delta\phi\rightarrow0}\frac{\exp((J_l\delta\phi)^{\land})\exp(\phi^{\land})p - \exp(\phi^{\land})p}{\delta\phi}\\\\&= \lim\limits_{\delta\phi\rightarrow0}\frac{(I +(J_l\delta\phi)^{\land})\exp(\phi^{\land})p - \exp(\phi^{\land})p}{\delta\phi}\\\\&= \lim\limits_{\delta\phi\rightarrow0}\frac{(J_l\delta\phi)^{\land} \exp(\phi^{\land})p}{\delta\phi}\\\\&= \lim\limits_{\delta\phi\rightarrow0}\frac{-(\exp(\phi^{\land})p)^{\land}J_l\delta\phi }{\delta\phi} = -(Rp)^{\land}J_l \end{aligned} ϕ(exp(ϕ)p)=δϕ0limδϕexp((ϕ+δϕ))pexp(ϕ)p=δϕ0limδϕexp((Jlδϕ))exp(ϕ)pexp(ϕ)p=δϕ0limδϕ(I+(Jlδϕ))exp(ϕ)pexp(ϕ)p=δϕ0limδϕ(Jlδϕ)exp(ϕ)p=δϕ0limδϕ(exp(ϕ)p)Jlδϕ=(Rp)Jl

  第2行的近似为BCH线性近似,第3行为泰勒展开舍去高阶项后的近似(由于取了极限,可以写等号),第4行至第5行将反对称符号看作叉积,交换之后变号。于是,我们推导出了旋转后的点相对于李代数的导数:

          ∂ ( R p ) ∂ ϕ = ( − R p ) ∧ J l . \frac{\partial(Rp)}{\partial\phi} = (-Rp)^{\land}J_l. ϕ(Rp)=(Rp)Jl.

  不过,由于这里仍然含有形式比较复杂的 J l , J_l, Jl,我们不太希望计算它。而下面介绍的扰动模型则提供了更为简单的导数计算方式。

4.3.4 扰动模型(左乘)

  另一种求导方式是对 R R R进行一次扰动 Δ R \Delta R ΔR,看结果相对于扰动的变化率。这个扰动可以乘在左边也可以乘在右边,最后结果会有一点儿微小的差异,我们以左扰动为例。设左扰动 Δ R \Delta R ΔR对应的李代数 φ 。 \varphi。 φ然后,对 φ \varphi φ求导,即

          ∂ ( R p ) ∂ φ = lim ⁡ φ → 0 exp ⁡ ( φ ∧ ) exp ⁡ ( ϕ ∧ ) p    −    exp ⁡ ( ϕ ∧ ) p φ . \frac{\partial(Rp)}{\partial\varphi} = \lim\limits_{\varphi\rightarrow0}\frac{\exp(\varphi^{\land})\exp(\phi^{\land})p\;-\;\exp(\phi^{\land})p}{\varphi}. φ(Rp)=φ0limφexp(φ)exp(ϕ)pexp(ϕ)p.

  该式的求导比上面更简单:

          ∂ ( R p ) ∂ φ = lim ⁡ φ → 0 exp ⁡ ( φ ∧ ) exp ⁡ ( ϕ ∧ ) p    −    exp ⁡ ( ϕ ∧ ) p φ = lim ⁡ φ → 0 ( I + φ ∧ ) exp ⁡ ( ϕ ∧ ) p    −    exp ⁡ ( ϕ ∧ ) p φ = lim ⁡ φ → 0 φ ∧ R p φ = lim ⁡ φ → 0 − ( R p ) ∧ φ φ = − ( R p ) ∧ . \begin{aligned}\frac{\partial(Rp)}{\partial\varphi} &= \lim\limits_{\varphi\rightarrow0}\frac{\exp(\varphi^{\land})\exp(\phi^{\land})p\;-\;\exp(\phi^{\land})p}{\varphi}\\\\&= \lim\limits_{\varphi\rightarrow0}\frac{(I + \varphi^{\land})\exp(\phi^{\land})p\;-\;\exp(\phi^{\land})p}{\varphi}\\\\&= \lim\limits_{\varphi\rightarrow0}\frac{\varphi^{\land}Rp}{\varphi} = \lim\limits_{\varphi\rightarrow0}\frac{-(Rp)^{\land}\varphi}{\varphi} = -(Rp)^{\land}. \end{aligned} φ(Rp)=φ0limφexp(φ)exp(ϕ)pexp(ϕ)p=φ0limφ(I+φ)exp(ϕ)pexp(ϕ)p=φ0limφφRp=φ0limφ(Rp)φ=(Rp).

  可见,相比于直接对李代数求导,省去了一个雅可比 J l J_l Jl的计算。这使得扰动模型更为实用。请读者务必理解这里的求导运算,这在位姿估计中具有重要的意义。

4.3.5 SE(3)上的李代数求导

  最后,我们给出 S E ( 3 ) SE(3) SE(3)上的扰动模型,而直接李代数上的求导就不再介绍了。假设某空间点 p p p经过一次变换 T T T(对应李代数为 ξ \xi ξ),得到 T p Tp Tp。现在,给 T T T左乘一个扰动 Δ T = exp ⁡ ( δ ξ ∧ ) , \Delta T = \exp(\delta\xi^{\land}), ΔT=exp(δξ),我们设扰动项的李代数为 δ ξ = [ δ ρ , δ ϕ ] T , \delta\xi = [\delta\rho,\delta\phi]^{T}, δξ=[δρ,δϕ]T,那么:

          ∂ ( T p ) ∂ δ ξ = lim ⁡ δ ξ → 0 exp ⁡ ( δ ξ ∧ ) exp ⁡ ( ξ ∧ ) p    −    exp ⁡ ( ξ ∧ ) p δ ξ = lim ⁡ δ ξ → 0 ( I + δ ξ ∧ ) exp ⁡ ( ξ ∧ ) p    −    exp ⁡ ( ξ ∧ ) p δ ξ = lim ⁡ δ ξ → 0 δ ξ ∧ exp ⁡ ( ξ ∧ ) p δ ξ = lim ⁡ φ → 0 [ δ ϕ ∧ δ ρ 0 T 0 ] [ R p + t 1 ] δ ξ = lim ⁡ φ → 0 [ δ ϕ ∧ ( R p + t ) + δ ρ 0 T ] [ δ ρ , δ ϕ ] T = [ I − ( R p + t ) ∧ 0 T 0 T ] = d e f ( T p ) ⨀ . \begin{aligned}\frac{\partial(Tp)}{\partial\delta\xi} &= \lim\limits_{\delta\xi\rightarrow0}\frac{\exp(\delta\xi^{\land})\exp(\xi^{\land})p\;-\;\exp(\xi^{\land})p}{\delta\xi}\\\\&= \lim\limits_{\delta\xi\rightarrow0}\frac{(I + \delta\xi^{\land})\exp(\xi^{\land})p\;-\;\exp(\xi^{\land})p}{\delta\xi}\\\\&= \lim\limits_{\delta\xi\rightarrow0}\frac{\delta\xi^{\land}\exp(\xi^{\land})p}{\delta\xi}\\\\ &= \lim\limits_{\varphi\rightarrow0}\frac{\begin{bmatrix}\delta\phi^{\land}&\delta\rho\\\\0^T&0\end{bmatrix}\begin{bmatrix}Rp + t\\\\1\end{bmatrix}}{\delta\xi}\\\\ &= \lim\limits_{\varphi\rightarrow0}\frac{\begin{bmatrix}\delta\phi^{\land}(Rp + t) + \delta\rho\\\\0^T\end{bmatrix}}{[\delta\rho,\delta\phi]^{T}}= \begin{bmatrix}I&-(Rp + t)^{\land}\\\\0^T&0^T\end{bmatrix} \overset{def}{=}(Tp)^{\bigodot}. \end{aligned} δξ(Tp)=δξ0limδξexp(δξ)exp(ξ)pexp(ξ)p=δξ0limδξ(I+δξ)exp(ξ)pexp(ξ)p=δξ0limδξδξexp(ξ)p=φ0limδξ δϕ0Tδρ0 Rp+t1 =φ0lim[δρ,δϕ]T δϕ(Rp+t)+δρ0T = I0T(Rp+t)0T =def(Tp).

  我们把最后的结果定义成一个算符 ⨀ ^{\bigodot} ,它把一个齐次坐标的空间点变换成一个 4 × 6 4×6 4×6的矩阵。此式稍微需要解释的是矩阵求导方面的顺序,假设 a , b , x , y a,b,x,y a,b,x,y都是列向量,那么在我们的写法下,有如下的规则:

          d [ a b ] d [ x y ] = ( d [ a , b ] T d [ x y ] ) = [ d a d x d b d x d a d y d b d y ] T = [ d a d x d a d y d b d x d b d y ] \frac{d\begin{bmatrix}\\\\a\\\\b\\\\\end{bmatrix}}{d\begin{bmatrix}\\\\x\\\\y\\\\\end{bmatrix}} = \left( \frac{d[a,b]^T}{d\begin{bmatrix}\\\\x\\\\y\\\\\end{bmatrix}} \right) = \begin{bmatrix}\frac{da}{dx}&\frac{db}{dx}\\\\\frac{da}{dy}&\frac{db}{dy}\end{bmatrix}^T =\begin{bmatrix}\frac{da}{dx}&\frac{da}{dy}\\\\\frac{db}{dx}&\frac{db}{dy}\end{bmatrix} d xy d ab = d xy d[a,b]T = dxdadydadxdbdydb T= dxdadxdbdydadydb

  至此,我们已经介绍了李群与李代数上的微分运算。之后的章节中,我们将应用这些知识解决实际问题。关于李群与李代数的某些重要数学性质,我们作为习题留给读者。

4.4 实践:Sophus

4.4.1 Sophus的基本使用方法

  我们已经介绍了李代数的入门知识,现在是通过实践演练并巩固所学知识的时候了。我们来讨论如何在程序中操作李代数。在第3讲中,我们看到Eigen提供了几何模块,但没有提供李代数的支持。一个较好的李代数库是Strasdat维护的Sophus库。Sophus库支持本章主要讨论的 S O ( 3 ) SO(3) SO(3) S E ( 3 ) , SE(3), SE(3)此外,还含有二维运动 S O ( 2 ) , S E ( 2 ) SO(2),SE(2) SO(2),SE(2)及相似变换Sim(3)的内容。它是直接在Eigen基础上开发的,我们不需要安装额外的依赖库。Sophus早期版本只提供了双精度的李群/李代数类。后续版本改成了模板类。模板类的Sophus中可以使用不同精度的李群/李代数,但同时增加了增加了使用难度。在本书中,我们使用带模板的Sophus库。
  具体见slambook/ch4/useSophus.cpp来演示Sophus库中的 S O ( 3 ) SO(3) SO(3) S E ( 3 ) SE(3) SE(3)运算:
  运行结果如下:
在这里插入图片描述在这里插入图片描述
  上述程序演示了两个部分的内容。前半部分介绍 S O ( 3 ) SO(3) SO(3)上的操作,后半部分则为 S E ( 3 ) 。 SE(3)。 SE(3)我们演示了如何构造 S O ( 3 ) , S E ( 3 ) SO(3),SE(3) SO(3),SE(3)对象,对它们进行指数、对数映射,以及当知道更新量后,如何对李群元素进行更新。当理解了本讲内容,那么这个程序对你来说应该没有什么难度。为了编译,需要在CMakeLists.txt里添加以下几行代码:

find_package( Sophus REQUIRED)
include_directories( ${
    
    Sophus_INCLUDE_DIRS})

add_executable( useSophus useSophus.cpp)

  find_package命令是cmake提供的寻找某个库的头文件与库文件的指令。如果cmake能够找到它,就会提供头文件和库文件所在的目录的变量。在Sophus这个例子中,就是Sophus_INCLUDE_DIRS。基于模板的Sophus库和Eigen一样,是仅含头文件而没有源文件的。根据它们,我们就能将Sophus库引入自己的cmake工程。

4.4.2 例子:评估轨迹的误差

  在实际工程中,我们经常需要评估一个算法的估计轨迹与真实轨迹的差异来评价算法的精度。真实轨迹往往通过某些更高精度的系统获得,而估计轨迹则是由待评价的算法计算得到的。第3讲我们演示了如何显示存储在文件中的某条轨迹,本节我们考虑如何计算两条轨迹的误差。考虑一条估计轨迹 T e s t i , i T_{esti,i} Testi,i和真实轨迹 T g t , i , T_{gt,i}, Tgt,i,其中 i = 1 , ⋅ ⋅ ⋅ , N , i = 1, ···, N, i=1,⋅⋅⋅N那么我们可以定义一些误差指标来描述它们之间的差别。
  误差指标可以有很多种,常见的有绝对轨迹误差(Absolute Trajectory Error, ATE), 形如:

          A T E a l l = 1 N ∑ i = 1 N ∥ log ⁡ ( T g t , i − 1 T e s t i , i ) ∨ ∥ 2 2 , ATE_{all} = \sqrt{\frac{1}{N}\sum\limits_{i = 1}^N\Vert \log(T^{-1}_{gt,i}T_{esti,i})^{\lor}\Vert^2_2}, ATEall=N1i=1Nlog(Tgt,i1Testi,i)22 ,

这实际上是每个位姿李代数的均方根误差(Root-Mean-Squared Error, RMSE)。 这种误差可以刻画两条轨迹的旋转和平移误差。同时,也有的文献仅考虑平移误差,从而可以定义绝对平移误差(Avergae Translational Error):

          A T E t r a n s = 1 N ∑ i = 1 N ∥ t r a n s ( T g t , i − 1 T e s t i , i ) ∥ 2 2 , ATE_{trans} = \sqrt{\frac{1}{N}\sum\limits_{i = 1}^N\Vert trans(T^{-1}_{gt,i}T_{esti,i})\Vert^2_2}, ATEtrans=N1i=1Ntrans(Tgt,i1Testi,i)22 ,

其中trans表示取括号内部变量的平移部分。因为从整条轨迹上看,旋转出现误差后,随后的轨迹在平移上也会出现误差,所以两种指标在实际中都适用。
  除此之外,也可以定义相对的误差。例如,考虑 i i i时刻到 i + Δ t i + \Delta t i+Δt时刻的运动,那么相对位姿误差可定义为:

          R P E a l l = 1 N − Δ t ∑ i = 1 N − Δ t ∥ log ⁡ ( ( T g t , i − 1 T g t , i + Δ t ) − 1 ( T e s t i , i − 1 T e s t i , i + Δ t ) ) ∨ ∥ 2 2 , RPE_{all} = \sqrt{\frac{1}{N - \Delta t}\sum\limits_{i = 1}^{N - \Delta t}\Vert \log((T^{-1}_{gt,i}T_{gt,i + \Delta t})^{-1}(T_{esti,i}^{-1}T_{esti,i + \Delta t}))^{\lor}\Vert^2_2}, RPEall=NΔt1i=1NΔtlog((Tgt,i1Tgt,i+Δt)1(Testi,i1Testi,i+Δt))22 ,

同样地,也可只取平移部分:

          R P E t r a n s = 1 N − Δ t ∑ i = 1 N − Δ t ∥ t r a n s ( ( T g t , i − 1 T g t , i + Δ t ) − 1 ( T e s t i , i − 1 T e s t i , i + Δ t ) ) ∥ 2 2 , RPE_{trans} = \sqrt{\frac{1}{N - \Delta t}\sum\limits_{i = 1}^{N - \Delta t}\Vert trans((T^{-1}_{gt,i}T_{gt,i + \Delta t})^{-1}(T_{esti,i}^{-1}T_{esti,i + \Delta t}))\Vert^2_2}, RPEtrans=NΔt1i=1NΔttrans((Tgt,i1Tgt,i+Δt)1(Testi,i1Testi,i+Δt))22 ,

  利用Sophus库,很容易实现这部分计算。下面哦们演示绝对轨迹误差的计算。在这个例子中,我们有groundtruth.txt和estimated.txt两条轨迹,下面的代码将读取这两条轨迹,计算误差,然后显示到3D窗口中。为简洁起见,省略了画轨迹部分的代码。在第3讲中我们已经做过类似的工作。
  代码见slambook/ch4/example/trajectoryError.cpp
  使代码运行成功的过程也是异常的曲折,分为三个步骤:

  1. 首先报错缺少fmt这个库,是因为新版Sophus不仅需要Pangolin,还需要安装fmt,那便安装即可。
  2. 安装完后运行,他还会报错,fmt/core.h:1711:3: error: static assertion failed: Cannot format an argument.百度后说是版本的问题,都推荐使用fmt 8.1.1,好的,下一个该版本的fmt 8.1.1,无需删除前一个版本,直接重复之前的步骤
  3. 安装完后,记得在CMakeLists.txt中加入下面两句,让cmake包含这个库,这样便可以使程序运行起来
find_package(FMT REQUIRED) 
target_link_libraries(trajectoryError fmt::fmt)
  1. 然后会发现文件地址错了,无法找到两个txt文件的目录。那便需要修改文件的目录
    在这里插入图片描述
    费了我快一个小时才将这个代码跑出来呢~!!
    结果如下:
    在这里插入图片描述
      该程序输出的结果为2.20728。当然也可以去掉旋转部分,仅计算平移部分的误差。就这个例子来说,我们事实上已经帮助读者做了一些预处理任务,包括轨迹的时间对齐、外参预估,这些内容现在还没有提到,以后的学习中会说到。

4.5 * 相似变换群与李代数

  最后,我们要提一下在单目视觉中使用的相似群 S i m ( 3 ) Sim(3) Sim(3),以及对应的李代数 s i m ( 3 ) 。 \mathfrak{sim}(3)。 sim(3)
  我们已经介绍过单目的尺度不确定性。如果在单目SLAM中使用 S E ( 3 ) SE(3) SE(3)表示位姿,那么由于尺度不确定性与尺度漂移,整个SLAM过程中的尺度会发生变化,这在 S E ( 3 ) SE(3) SE(3)中未能体现出来。因此,在单目情况下我们一般会显式地把尺度因子表达出来。用数学语言来说,对于位于空间的点 p p p,在相机坐标系下要经过一个相似变换,而非欧氏变换:

          p ′ = [ s R t 0 T 1 ] p = s R p + t . p^{'} = \begin{bmatrix}sR&t\\\\0^T&1\end{bmatrix}p = sRp + t. p= sR0Tt1 p=sRp+t.

在相似变换中,为我们把尺度 s s s表达出来了。它同时作用在 p p p的3个坐标之上,对 p p p进行了一次缩放。与 S O ( 3 ) 、 S E ( 3 ) SO(3)、SE(3) SO(3)SE(3)相似,相似变换也对矩阵乘法构成群,称为相似变换群Sim(3):

          S i m ( 3 ) = { S = [ s R t 0 T 1 ] ∈ R 4 × 4 } Sim(3) = \begin{Bmatrix} S = \begin{bmatrix}sR&t\\\\0^T &1\end{bmatrix}\in \mathbb{R}^{4 × 4}\end{Bmatrix} Sim(3)= S= sR0Tt1 R4×4

  同样地,Sim(3)也有对应的的李代数、指数映射、对数映射等。李代数 s i m ( 3 ) \mathfrak{sim}(3) sim(3)元素是一个7维向量 ζ \zeta ζ。它的前6维与 s e ( 3 ) \mathfrak{se}(3) se(3)相同,最后多了一项 σ \sigma σ

          s i m ( 3 ) = { ζ ∣ ζ = [ ρ ϕ σ ] ∈ R 7 , ζ ∧ = [ σ I + ϕ ∧ ρ 0 T 0 ] ∈ R 4 × 4 } \mathfrak{sim}(3) = \begin{Bmatrix} \zeta|\zeta = \begin{bmatrix}\rho\\\\\phi\\\\\sigma\end{bmatrix}\in \mathbb{R}^7,\zeta^{\land} = \begin{bmatrix}\sigma I + \phi^{\land}&\rho\\\\0^T &0\end{bmatrix}\in\mathbb{R}^{4 × 4}\end{Bmatrix} sim(3)= ζζ= ρϕσ R7,ζ= σI+ϕ0Tρ0 R4×4

  它比 s e ( 3 ) \mathfrak{se}(3) se(3)多了一项 σ \sigma σ。关联Sim(3)和 s i m ( 3 ) \mathfrak{sim}(3) sim(3)的仍是指数映射和对数映射。指数映射为

          exp ⁡ ( ζ ∧ ) = [ e σ exp ⁡ ( ϕ ∧ ) J s ρ 0 T 1 ] . \exp(\zeta^{\land}) = \begin{bmatrix} e^{\sigma}\exp(\phi^{\land})&J_s\rho\\\\0^T&1\end{bmatrix}. exp(ζ)= eσexp(ϕ)0TJsρ1 .

  其中, J s J_s Js的形式为

          J s = e σ − 1 σ I + σ e σ sin ⁡ θ + ( 1 − e σ cos ⁡ θ ) θ σ 2 + θ 2 a ∧          + ( e σ − 1 σ − ( e σ cos ⁡ θ − 1 ) σ + ( e σ sin ⁡ θ ) θ σ 2 + θ 2 ) a ∧ a ∧ . \begin{aligned}J_s &=\frac{e^\sigma - 1}{\sigma}I + \frac{\sigma e^\sigma \sin\theta + (1- e^\sigma \cos\theta)\theta}{\sigma^2 + \theta^2}a^{\land}\\&\;\;\;\;+\left( \frac{e^\sigma - 1}{\sigma} - \frac{(e^\sigma \cos\theta - 1 )\sigma + (e^\sigma \sin\theta)\theta}{\sigma^2 + \theta^2} \right)a^{\land}a^{\land}.\end{aligned} Js=σeσ1I+σ2+θ2σeσsinθ+(1eσcosθ)θa+(σeσ1σ2+θ2(eσcosθ1)σ+(eσsinθ)θ)aa.

  通过指数映射,我们能够找到李代数与李群的关系。对于李代数 ζ , \zeta, ζ,它与李群的对应关系为

          s = e σ , R = exp ⁡ ( ϕ ∧ ) , t = J s ρ . s = e^\sigma,R=\exp(\phi^{\land}),t=J_s\rho. s=eσ,R=exp(ϕ),t=Jsρ.

  旋转部分和SO(3)是一致的。平移部分,在 s e ( 3 ) \mathfrak{se}(3) se(3)中需要乘一个雅可比 J , \mathcal{J}, J,而相似变换的雅可比更复杂雅克比更复杂。对于尺度因子,可以看到李群中的 s s s即为李代数中的 σ \sigma σ的指数函数。
  Sim(3)的BCH近似与SE(3)是相似的。我们可以讨论一个点 p p p经过相似变换 S p Sp Sp,相对于 S S S的导数。同样地,存在微分模型和扰动模型两种方式,而扰动模型较为简单。我们省略推导过程,直接给出扰动模型的结果。设给予 S p Sp Sp左侧一个小扰动 exp ⁡ ( ζ ∧ ) , \exp(\zeta^{\land}), exp(ζ),并求 S p Sp Sp对于扰动的导数。因为 S p Sp Sp是4维的齐次坐标, ζ \zeta ζ是7维向量,所以该导数应该是 4 × 7 4×7 4×7的雅可比。方便起见,记 S p Sp Sp的前3维组成向量为 q , q, q,那么:

          ∂ S p ∂ ζ = [ I − q ∧ q 0 T 0 T 0 ] \frac{\partial{Sp}}{\partial\zeta} = \begin{bmatrix}I&-q^{\land}&q\\\\0^T&0^T&0\end{bmatrix} ζSp= I0Tq0Tq0

4.6 小结

  本讲引入了李群SO(3)和SE(3),以及它们对应的李代数 s o ( 3 ) \mathfrak{so}(3) so(3) s e ( 3 ) 。 \mathfrak{se}(3)。 se(3)我们介绍了位姿在它们上面的表达和转换,然后通过BCH的线性近似,就可以对位姿进行扰动并求导。这给之后讲解位姿的优化打下了理论基础,因为我们需要经常对某一个位姿的估计值进行调整,使它对应的误差减小。只有在弄清楚如何对位姿进行调整和更新之后,才能继续后续的内容。
  值得一提的是,除了李代数,同样也可以用四元数、欧拉角等方式表示旋转,只是后续的处理要麻烦一些。在实际应用中,也可以使用SO(3)加上平移的方式来代替SE(3),从而回避一些雅可比的计算。

猜你喜欢

转载自blog.csdn.net/qq_20184333/article/details/126003776
今日推荐