Unleashing Robotics: Mastering Quaternion Kinematics with Python - Chapter 2(原创系列教程)

Unleashing Robotics: Mastering Quaternion Kinematics with Python

Chapter 2: 四元数旋转与其性质(Rotations and cross-relations)

在第一章中,我们详细介绍了四元数的定义、性质以及运算法则。现在,我们将进一步探讨四元数在三维空间中的几何意义,特别是它与旋转的关系。本章将揭示四元数的一些重要性质,如四元数乘积与旋转复合、四元数指数与旋转矩阵的关系等。同时,我们还会介绍四元数与其他旋转表示之间的转换,如欧拉角、轴角等。

本章的内容对于深入理解四元数在三维旋转中的作用至关重要。只有建立了坚实的理论基础,我们才能在实际应用中灵活运用四元数,并解决旋转表示和运动合成中的各种问题。

2.1 三维向量旋转公式(The 3D vector rotation formula)

首先,我们定义以下符号:

  • x \mathbf{x} x: 原始向量,即旋转前的向量。
  • x ′ \mathbf{x}' x: 旋转后的向量。
  • u \mathbf{u} u: 旋转轴,这是一个单位向量,表示旋转的方向。
  • ϕ \phi ϕ: 旋转角,以弧度为单位,表示绕旋转轴旋转的量。我们假设旋转遵循右手定则。

现在,我们将原始向量 x \mathbf{x} x 分解为两个分量:平行于旋转轴 u \mathbf{u} u 的分量 x ∥ \mathbf{x}_\parallel x,和垂直于旋转轴 u \mathbf{u} u 的分量 x ⊥ \mathbf{x}_\perp x。数学上,我们可以写成:

x = x ∥ + x ⊥ \mathbf{x} = \mathbf{x}_\parallel + \mathbf{x}_\perp x=x+x

其中,

x ∥ = ( x ⋅ u ) u \mathbf{x}_\parallel = (\mathbf{x} \cdot \mathbf{u}) \mathbf{u} x=(xu)u

x ⊥ = x − x ∥ = x − ( x ⋅ u ) u \mathbf{x}_\perp = \mathbf{x} - \mathbf{x}_\parallel = \mathbf{x} - (\mathbf{x} \cdot \mathbf{u}) \mathbf{u} x=xx=x(xu)u

这里, x ⋅ u \mathbf{x} \cdot \mathbf{u} xu 表示向量 x \mathbf{x} x u \mathbf{u} u 的点积(或内积),它给出了 x \mathbf{x} x u \mathbf{u} u 方向上的投影长度。

当我们将向量 x \mathbf{x} x 绕轴 u \mathbf{u} u 旋转 ϕ \phi ϕ 角时,平行分量 x ∥ \mathbf{x}_\parallel x 保持不变,因为它与旋转轴重合。垂直分量 x ⊥ \mathbf{x}_\perp x 则在垂直于 u \mathbf{u} u 的平面内旋转 ϕ \phi ϕ 角。

为了表示 x ⊥ \mathbf{x}_\perp x 的旋转,我们引入两个正交单位向量 e 1 \mathbf{e}_1 e1 e 2 \mathbf{e}_2 e2,它们与 u \mathbf{u} u 一起构成一个正交基。我们可以选择:

e 1 = x ⊥ ∥ x ⊥ ∥ \mathbf{e}_1 = \frac{\mathbf{x}_\perp}{\|\mathbf{x}_\perp\|} e1=xx

e 2 = u × e 1 \mathbf{e}_2 = \mathbf{u} \times \mathbf{e}_1 e2=u×e1

其中, ∥ x ⊥ ∥ \|\mathbf{x}_\perp\| x 表示 x ⊥ \mathbf{x}_\perp x 的长度(或模),符号 × \times × 表示向量叉积(或外积)。这样,我们就有:

x ⊥ = ∥ x ⊥ ∥ e 1 \mathbf{x}_\perp = \|\mathbf{x}_\perp\| \mathbf{e}_1 x=xe1

e 1 \mathbf{e}_1 e1 e 2 \mathbf{e}_2 e2 张成的平面内,旋转 ϕ \phi ϕ 角相当于:

x ⊥ ′ = ∥ x ⊥ ∥ ( cos ⁡ ϕ   e 1 + sin ⁡ ϕ   e 2 ) \mathbf{x}'_\perp = \|\mathbf{x}_\perp\| (\cos\phi \, \mathbf{e}_1 + \sin\phi \, \mathbf{e}_2) x=x(cosϕe1+sinϕe2)

将这个结果代入 x ′ = x ∥ + x ⊥ ′ \mathbf{x}' = \mathbf{x}_\parallel + \mathbf{x}'_\perp x=x+x,我们得到向量旋转的最终公式:

x ′ = x ∥ + cos ⁡ ϕ   x ⊥ + sin ⁡ ϕ   ( u × x ) \mathbf{x}' = \mathbf{x}_\parallel + \cos\phi \, \mathbf{x}_\perp + \sin\phi \, (\mathbf{u} \times \mathbf{x}) x=x+cosϕx+sinϕ(u×x)

这个公式描述了三维向量绕任意轴旋转的结果,它是三维旋转的基本工具之一。在实际应用中,我们可以根据具体的旋转轴和旋转角,将这个公式展开为更具体的形式。

下面用Python代码演示如何应用这个公式:

import numpy as np

def rotate_vector(x, u, phi):
    """
    Rotate vector x by angle phi around axis u.
    
    Parameters:
        x (numpy.ndarray): 3D vector to be rotated.
        u (numpy.ndarray): Unit vector representing the rotation axis.
        phi (float): Rotation angle in radians.
        
    Returns:
        numpy.ndarray: Rotated vector.
    """
    x_parallel = np.dot(u, x) * u
    x_perp = x - x_parallel
    return x_parallel + x_perp * np.cos(phi) + np.cross(u, x) * np.sin(phi)

# Example usage
x = np.array([1, 0, 0])
u = np.array([0, 0, 1])
phi = np.pi / 2

x_rotated = rotate_vector(x, u, phi)
print(x_rotated)  # Output: [0. 1. 0.]

在这个例子中,我们将向量 x = ( 1 , 0 , 0 ) \mathbf{x} = (1, 0, 0) x=(1,0,0) z z z 轴(即 u = ( 0 , 0 , 1 ) \mathbf{u} = (0, 0, 1) u=(0,0,1))旋转 9 0 ∘ 90^\circ 90 (即 ϕ = π / 2 \phi = \pi/2 ϕ=π/2),得到的结果是 ( 0 , 1 , 0 ) (0, 1, 0) (0,1,0),与预期相符。

这个旋转公式在计算机图形学、机器人学等领域有广泛应用。例如,在三维重建中,我们经常需要将点云或网格数据进行旋转,以对齐不同视角下的数据。再如,在机器人运动规划中,我们需要计算机器人关节的旋转,以实现期望的末端位姿。这些问题都可以用旋转公式来解决。

2.2 旋转群SO(3)

The rotation group SO(3)

在三维欧氏空间 R 3 \mathbb{R}^3 R3 中,所有绕原点的旋转(在复合运算下)构成了一个群,称为特殊正交群 S O ( 3 ) SO(3) SO(3)。群 S O ( 3 ) SO(3) SO(3) 中的元素是旋转矩阵,它们满足以下性质:

  1. 正交性: R R ⊤ = R ⊤ R = I RR^\top = R^\top R = I RR=RR=I
  2. 行列式为正: det ⁡ ( R ) = 1 \det(R) = 1 det(R)=1

第一个性质保证了旋转矩阵是正交矩阵,即它的逆矩阵等于其转置。这反映了旋转变换的可逆性,即任何旋转都有一个唯一的逆变换,使得旋转后再逆变换可以回到原来的状态。

第二个性质区分了旋转和镜像。旋转矩阵的行列式为1,而镜像矩阵的行列式为-1。这个性质保证了旋转变换不改变空间的定向(右手系仍为右手系),而镜像会改变定向。

S O ( 3 ) SO(3) SO(3) 的一个重要性质是,它是一个李群(Lie group),即它既是一个群,又是一个光滑的流形(manifold)。这意味着我们可以在 S O ( 3 ) SO(3) SO(3) 上定义连续的曲线,表示连续的旋转过程。此外, S O ( 3 ) SO(3) SO(3) 的切空间是一个李代数(Lie algebra),记为 s o ( 3 ) \mathfrak{so}(3) so(3),它对应于所有 3 × 3 3 \times 3 3×3 的反对称矩阵:

s o ( 3 ) = { Ω ∈ R 3 × 3 ∣ Ω ⊤ = − Ω } \mathfrak{so}(3) = \{\mathbf{\Omega} \in \mathbb{R}^{3 \times 3} | \mathbf{\Omega}^\top = -\mathbf{\Omega}\} so(3)={ΩR3×3Ω=Ω}

李代数 s o ( 3 ) \mathfrak{so}(3) so(3) 的元素可以用三维向量 ω = ( ω 1 , ω 2 , ω 3 ) \boldsymbol{\omega} = (\omega_1, \omega_2, \omega_3) ω=(ω1,ω2,ω3) 表示:

Ω = [ 0 − ω 3 ω 2 ω 3 0 − ω 1 − ω 2 ω 1 0 ] \mathbf{\Omega} = \begin{bmatrix} 0 & -\omega_3 & \omega_2 \\ \omega_3 & 0 & -\omega_1 \\ -\omega_2 & \omega_1 & 0 \end{bmatrix} Ω= 0ω3ω2ω30ω1ω2ω10

向量 ω \boldsymbol{\omega} ω 表示瞬时旋转轴和角速度,其方向为旋转轴,模长为角速度。李代数与李群之间有指数映射和对数映射,我们将在后面详细介绍。

总之, S O ( 3 ) SO(3) SO(3) 是描述三维旋转的基本数学结构,理解它的性质对于分析和求解旋转问题至关重要。在机器人学、计算机视觉等领域,我们经常需要优化旋转矩阵,估计相机或机器人的姿态,这都需要在 S O ( 3 ) SO(3) SO(3) 流形上进行运算和优化。关于 S O ( 3 ) SO(3) SO(3) 的更多内容,感兴趣的读者可以参考以下文献:

  1. Ma, Y., Soatto, S., Kosecka, J., & Sastry, S. S. (2012). An invitation to 3-d vision: from images to geometric models (Vol. 26). Springer Science & Business Media.

  2. Barfoot, T. D. (2017). State estimation for robotics. Cambridge University Press.

  3. Sola, J., Deray, J., & Atchuthan, D. (2018). A micro Lie theory for state estimation in robotics. arXiv preprint arXiv:1812.01537.

2.3 旋转群和旋转矩阵(The rotation group and the rotation matrix)

我们已经知道,旋转矩阵是 S O ( 3 ) SO(3) SO(3) 群的元素。现在让我们更深入地探讨旋转矩阵的性质,以及它与旋转运算的关系。

2.3.1 指数映射(The exponential map)

旋转矩阵与李代数 s o ( 3 ) \mathfrak{so}(3) so(3) 之间有一个重要的映射,称为指数映射(exponential map):

exp ⁡ : s o ( 3 ) → S O ( 3 ) , Ω ↦ exp ⁡ ( Ω ) \exp: \mathfrak{so}(3) \to SO(3), \quad \mathbf{\Omega} \mapsto \exp(\mathbf{\Omega}) exp:so(3)SO(3),Ωexp(Ω)

指数映射将李代数中的元素(即反对称矩阵 Ω \mathbf{\Omega} Ω)映射到李群中的元素(即旋转矩阵 R R R)。它的定义为:

exp ⁡ ( Ω ) = ∑ n = 0 ∞ 1 n ! Ω n = I + Ω + 1 2 ! Ω 2 + 1 3 ! Ω 3 + ⋯ \exp(\mathbf{\Omega}) = \sum_{n=0}^\infty \frac{1}{n!}\mathbf{\Omega}^n = I + \mathbf{\Omega} + \frac{1}{2!}\mathbf{\Omega}^2 + \frac{1}{3!}\mathbf{\Omega}^3 + \cdots exp(Ω)=n=0n!1Ωn=I+Ω+2!1Ω2+3!1Ω3+

这个级数总是收敛的,因为 Ω \mathbf{\Omega} Ω 的所有特征值都是纯虚数或零。

指数映射的物理意义是:将 Ω \mathbf{\Omega} Ω 视为角速度,则 exp ⁡ ( Ω ) \exp(\mathbf{\Omega}) exp(Ω) 表示以 Ω \mathbf{\Omega} Ω 为轴和速度旋转单位时间(1秒)后得到的旋转矩阵。更一般地,对于任意时间 t t t,有:

R ( t ) = exp ⁡ ( t Ω ) = I + t Ω + t 2 2 ! Ω 2 + t 3 3 ! Ω 3 + ⋯ R(t) = \exp(t\mathbf{\Omega}) = I + t\mathbf{\Omega} + \frac{t^2}{2!}\mathbf{\Omega}^2 + \frac{t^3}{3!}\mathbf{\Omega}^3 + \cdots R(t)=exp(tΩ)=I+tΩ+2!t2Ω2+3!t3Ω3+

这表示以 Ω \mathbf{\Omega} Ω 为轴和速度旋转 t t t 秒后得到的旋转矩阵。

下面用Python代码演示如何计算指数映射:

import numpy as np
from scipy.linalg import expm

def exp_map(omega):
    """
    Compute the exponential map of a skew-symmetric matrix.
    
    Parameters:
        omega (numpy.ndarray): 3D vector representing the rotation axis and angle.
        
    Returns:
        numpy.ndarray: 3x3 rotation matrix.
    """
    omega_hat = np.array([[0, -omega[2], omega[1]],
                          [omega[2], 0, -omega[0]],
                          [-omega[1], omega[0], 0]])
    return expm(omega_hat)

# Example usage
omega = np.array([0, 0, np.pi / 2])
R = exp_map(omega)
print(R)
# Output:
# [[ 0. -1.  0.]
#  [ 1.  0.  0.]
#  [ 0.  0.  1.]]

在这个例子中,我们计算了绕 z z z 轴旋转 9 0 ∘ 90^\circ 90 的指数映射,得到了对应的旋转矩阵。

指数映射在机器人运动学中有重要应用。例如,在机器人关节空间控制中,我们经常需要将关节速度转换为末端执行器的速度。设关节速度为 θ ˙ ∈ R n \dot{\boldsymbol{\theta}} \in \mathbb{R}^n θ˙Rn,末端执行器的速度为 v ∈ R 6 \mathbf{v} \in \mathbb{R}^6 vR6 (包括线速度和角速度),则有:

v = J ( θ ) θ ˙ \mathbf{v} = \mathbf{J}(\boldsymbol{\theta}) \dot{\boldsymbol{\theta}} v=J(θ)θ˙

其中 J ( θ ) ∈ R 6 × n \mathbf{J}(\boldsymbol{\theta}) \in \mathbb{R}^{6 \times n} J(θ)R6×n 是机器人的雅可比矩阵。如果我们将末端执行器的速度分解为线速度 v t \mathbf{v}_t vt 和角速度 ω \boldsymbol{\omega} ω,即 v = [ v t , ω ] ⊤ \mathbf{v} = [\mathbf{v}_t, \boldsymbol{\omega}]^\top v=[vt,ω],那么末端执行器在时间 t t t 的位姿变化为:

T ( t ) = [ exp ⁡ ( ω t ) v t t 0 ⊤ 1 ] \mathbf{T}(t) = \begin{bmatrix} \exp(\boldsymbol{\omega}t) & \mathbf{v}_t t \\ \mathbf{0}^\top & 1 \end{bmatrix} T(t)=[exp(ωt)0vtt1]

其中 exp ⁡ ( ω t ) \exp(\boldsymbol{\omega}t) exp(ωt) 表示旋转部分, v t t \mathbf{v}_t t vtt 表示平移部分。这个公式被称为机器人的指数积分公式(exponential integration formula),它将速度空间的运动转换为位姿空间的运动。关于这个话题的更多内容,可以参考以下文献:

  1. Murray, R. M., Li, Z., & Sastry, S. S. (2017). A mathematical introduction to robotic manipulation. CRC press.

  2. Lynch, K. M., & Park, F. C. (2017). Modern Robotics: Mechanics, Planning, and Control. Cambridge University Press.

  3. Selig, J. M. (2004). Geometric fundamentals of robotics. Springer Science & Business Media.

2.3.2 大写指数映射(The capitalized exponential map)

在某些文献中,我们会看到大写的指数映射符号 E x p \mathrm{Exp} Exp,它直接将轴角向量 ϕ ∈ R 3 \boldsymbol{\phi} \in \mathbb{R}^3 ϕR3 映射到旋转矩阵:

E x p : R 3 → S O ( 3 ) , ϕ ↦ E x p ( ϕ ) = exp ⁡ ( ϕ ^ ) \mathrm{Exp}: \mathbb{R}^3 \to SO(3), \quad \boldsymbol{\phi} \mapsto \mathrm{Exp}(\boldsymbol{\phi}) = \exp(\hat{\boldsymbol{\phi}}) Exp:R3SO(3),ϕExp(ϕ)=exp(ϕ^)

其中 ϕ ^ \hat{\boldsymbol{\phi}} ϕ^ 表示 ϕ \boldsymbol{\phi} ϕ 对应的反对称矩阵:

ϕ ^ = [ 0 − ϕ 3 ϕ 2 ϕ 3 0 − ϕ 1 − ϕ 2 ϕ 1 0 ] \hat{\boldsymbol{\phi}} = \begin{bmatrix} 0 & -\phi_3 & \phi_2 \\ \phi_3 & 0 & -\phi_1 \\ -\phi_2 & \phi_1 & 0 \end{bmatrix} ϕ^= 0ϕ3ϕ2ϕ30ϕ1ϕ2ϕ10

大写的指数映射 E x p \mathrm{Exp} Exp 与小写的指数映射 exp ⁡ \exp exp 在概念上是等价的,只是输入不同:前者直接输入轴角向量,后者输入反对称矩阵。在实际应用中,我们通常更倾向于使用大写的指数映射,因为轴角向量更直观,更容易表示旋转。

Python中可以这样实现大写的指数映射:

import numpy as np
from scipy.spatial.transform import Rotation as R

def Exp_map(phi):
    """
    Compute the capitalized exponential map of an axis-angle vector.
    
    Parameters:
        phi (numpy.ndarray): 3D vector representing the rotation axis and angle.
        
    Returns:
        numpy.ndarray: 3x3 rotation matrix.
    """
    return R.from_rotvec(phi).as_matrix()

# Example usage
phi = np.array([0, 0, np.pi / 2])
R = Exp_map(phi)
print(R)
# Output: 
# [[ 0. -1.  0.]
#  [ 1.  0.  0.]
#  [ 0.  0.  1.]]

在这个例子中,我们直接输入轴角向量 ϕ = ( 0 , 0 , π / 2 ) \boldsymbol{\phi} = (0, 0, \pi/2) ϕ=(0,0,π/2),表示绕 z z z 轴旋转 9 0 ∘ 90^\circ 90,得到了对应的旋转矩阵。

2.3.3 旋转矩阵和旋转向量:罗德里格斯旋转公式(Rotation matrix and rotation vector: the Rodrigues rotation formula)

我们已经看到,旋转矩阵可以通过指数映射从轴角向量 ϕ \boldsymbol{\phi} ϕ 得到。这个映射关系可以写成一个更直观的形式,称为罗德里格斯旋转公式(Rodrigues’ rotation formula):

R ( ϕ ) = E x p ( ϕ ) = I + sin ⁡ ϕ u ^ + ( 1 − cos ⁡ ϕ ) u ^ 2 R(\boldsymbol{\phi}) = \mathrm{Exp}(\boldsymbol{\phi}) = I + \sin\phi \hat{\mathbf{u}} + (1 - \cos\phi)\hat{\mathbf{u}}^2 R(ϕ)=Exp(ϕ)=I+sinϕu^+(1cosϕ)u^2

其中 ϕ = ∥ ϕ ∥ \phi = \|\boldsymbol{\phi}\| ϕ=ϕ 是旋转角度, u = ϕ / ϕ \mathbf{u} = \boldsymbol{\phi} / \phi u=ϕ/ϕ 是单位旋转轴, u ^ \hat{\mathbf{u}} u^ u \mathbf{u} u 对应的反对称矩阵。

这个公式可以从指数映射的级数展开推导出来。将 ϕ = ϕ u \boldsymbol{\phi} = \phi\mathbf{u} ϕ=ϕu 代入指数映射的定义式,利用 u ^ \hat{\mathbf{u}} u^ 的性质 u ^ 3 = − u ^ \hat{\mathbf{u}}^3 = -\hat{\mathbf{u}} u^3=u^,可以得到:

exp ⁡ ( ϕ u ^ ) = I + ϕ u ^ + ϕ 2 2 ! u ^ 2 + ϕ 3 3 ! u ^ 3 + ⋯ \exp(\phi\hat{\mathbf{u}}) = I + \phi\hat{\mathbf{u}} + \frac{\phi^2}{2!}\hat{\mathbf{u}}^2 + \frac{\phi^3}{3!}\hat{\mathbf{u}}^3 + \cdots exp(ϕu^)=I+ϕu^+2!ϕ2u^2+3!ϕ3u^3+
= I + ( ϕ − ϕ 3 3 ! + ⋯   ) u ^ + ( ϕ 2 2 ! − ϕ 4 4 ! + ⋯   ) u ^ 2 = I + (\phi - \frac{\phi^3}{3!} + \cdots)\hat{\mathbf{u}} + (\frac{\phi^2}{2!} - \frac{\phi^4}{4!} + \cdots)\hat{\mathbf{u}}^2 =I+(ϕ3!ϕ3+)u^+(2!ϕ24!ϕ4+)u^2
= I + sin ⁡ ϕ u ^ + ( 1 − cos ⁡ ϕ ) u ^ 2 = I + \sin\phi \hat{\mathbf{u}} + (1 - \cos\phi)\hat{\mathbf{u}}^2 =I+sinϕu^+(1cosϕ)u^2

罗德里格斯公式给出了旋转矩阵与轴角向量之间的直接关系,避免了计算矩阵指数。在实际应用中,它常用于旋转的插值和优化问题。

下面是Python中计算罗德里格斯公式的代码:

import numpy as np

def rodrigues(phi):
    """
    Compute the Rodrigues' rotation formula.
    
    Parameters:
        phi (numpy.ndarray): 3D vector representing the rotation axis and angle.
        
    Returns:
        numpy.ndarray: 3x3 rotation matrix.
    """
    phi_norm = np.linalg.norm(phi)
    if phi_norm < 1e-8:
        return np.eye(3)
    else:
        u = phi / phi_norm
        u_hat = np.array([[0, -u[2], u[1]], 
                          [u[2], 0, -u[0]], 
                          [-u[1], u[0], 0]])
        return np.eye(3) + np.sin(phi_norm) * u_hat + (1 - np.cos(phi_norm)) * u_hat @ u_hat

# Example usage
phi = np.array([0, 0, np.pi / 2])
R = rodrigues(phi)
print(R)
# Output:
# [[ 0. -1.  0.]
#  [ 1.  0.  0.]
#  [ 0.  0.  1.]]

在这个例子中,我们计算了绕 z z z 轴旋转 9 0 ∘ 90^\circ 90 的罗德里格斯公式,得到了对应的旋转矩阵。

我再来详细解释一下如何使用罗德里格斯公式进行旋转插值,为了方便理解我使用旋转矩阵,四元数的旋转插值我会在未来章节中详细介绍。

首先说明下旋转插值这个概念,旋转插值是指,给定两个旋转矩阵 R 1 R_1 R1 R 2 R_2 R2,我们想找到一个旋转矩阵 R ( t ) R(t) R(t),使得它在 R 1 R_1 R1 R 2 R_2 R2 之间平滑过渡,其中 t ∈ [ 0 , 1 ] t \in [0, 1] t[0,1] 是插值参数。当 t = 0 t=0 t=0 时, R ( 0 ) = R 1 R(0)=R_1 R(0)=R1;当 t = 1 t=1 t=1 时, R ( 1 ) = R 2 R(1)=R_2 R(1)=R2

用罗德里格斯公式进行旋转插值的步骤如下:

  1. 将旋转矩阵 R 1 R_1 R1 R 2 R_2 R2 转换为轴角向量 ϕ 1 \boldsymbol{\phi}_1 ϕ1 ϕ 2 \boldsymbol{\phi}_2 ϕ2。这可以使用罗德里格斯公式的逆变换:

    ϕ = arccos ⁡ ( t r ( R ) − 1 2 ) [ R 32 − R 23 , R 13 − R 31 , R 21 − R 12 ] T ∥ [ R 32 − R 23 , R 13 − R 31 , R 21 − R 12 ] ∥ \phi = \arccos\left(\frac{\mathrm{tr}(R)-1}{2}\right) \frac{[R_{32}-R_{23}, R_{13}-R_{31}, R_{21}-R_{12}]^T}{\|[R_{32}-R_{23}, R_{13}-R_{31}, R_{21}-R_{12}]\|} ϕ=arccos(2tr(R)1)[R32R23,R13R31,R21R12][R32R23,R13R31,R21R12]T

  2. 对轴角向量进行线性插值:

    ϕ ( t ) = ( 1 − t ) ϕ 1 + t ϕ 2 \boldsymbol{\phi}(t) = (1-t)\boldsymbol{\phi}_1 + t\boldsymbol{\phi}_2 ϕ(t)=(1t)ϕ1+tϕ2

  3. 将插值后的轴角向量 ϕ ( t ) \boldsymbol{\phi}(t) ϕ(t) 转换回旋转矩阵 R ( t ) R(t) R(t)。这可以直接使用罗德里格斯公式:

    R ( t ) = I + sin ⁡ ∥ ϕ ( t ) ∥ [ ϕ ^ ( t ) ] × + ( 1 − cos ⁡ ∥ ϕ ( t ) ∥ ) [ ϕ ^ ( t ) ] × 2 R(t) = I + \sin\|\boldsymbol{\phi}(t)\| [\hat{\boldsymbol{\phi}}(t)]_\times + (1-\cos\|\boldsymbol{\phi}(t)\|) [\hat{\boldsymbol{\phi}}(t)]^2_\times R(t)=I+sinϕ(t)[ϕ^(t)]×+(1cosϕ(t))[ϕ^(t)]×2

    其中 ϕ ^ ( t ) = ϕ ( t ) / ∥ ϕ ( t ) ∥ \hat{\boldsymbol{\phi}}(t) = \boldsymbol{\phi}(t) / \|\boldsymbol{\phi}(t)\| ϕ^(t)=ϕ(t)/∥ϕ(t) 是单位轴角向量。

这样,我们就得到了在 R 1 R_1 R1 R 2 R_2 R2 之间插值的旋转矩阵 R ( t ) R(t) R(t)

下面是一个Python代码示例:

import numpy as np

def axis_angle_from_matrix(R):
    """Convert rotation matrix to axis-angle vector"""
    phi = np.arccos((np.trace(R) - 1) / 2)
    if np.sin(phi) < 1e-8:
        return np.zeros(3)
    axis = np.array([R[2,1]-R[1,2], R[0,2]-R[2,0], R[1,0]-R[0,1]])
    axis = axis / (2*np.sin(phi))
    return phi * axis

def matrix_from_axis_angle(phi):
    """Rodrigues formula for rotation matrix from axis-angle vector"""
    phi_norm = np.linalg.norm(phi)
    if phi_norm < 1e-8:
        return np.eye(3)
    
    axis = phi / phi_norm
    axis_mat = np.array([[    0,      -axis[2],  axis[1]],
                         [ axis[2],     0,      -axis[0]],
                         [-axis[1],  axis[0],     0      ]])
    
    return np.eye(3) + np.sin(phi_norm)*axis_mat + (1-np.cos(phi_norm))*axis_mat@axis_mat

def interpolate_rotations(R1, R2, t):
    """Interpolate between two rotation matrices"""
    phi1 = axis_angle_from_matrix(R1) 
    phi2 = axis_angle_from_matrix(R2)
    
    phi_interp = (1-t)*phi1 + t*phi2
    
    return matrix_from_axis_angle(phi_interp)

# Example usage
R1 = np.array([[1, 0, 0], 
               [0, 0, -1],
               [0, 1, 0]])  # 90 deg rotation around x-axis
R2 = np.array([[0, -1, 0], 
               [1, 0, 0],
               [0, 0, 1]])  # 90 deg rotation around z-axis

t_values = [0, 0.25, 0.5, 0.75, 1]
for t in t_values:
    R_interp = interpolate_rotations(R1, R2, t)
    print(f'Interpolated rotation at t={t}:\n{R_interp}\n')

在这个例子中,我们定义了从旋转矩阵到轴角向量的转换 axis_angle_from_matrix, 以及基于罗德里格斯公式的逆变换 matrix_from_axis_angle。然后,我们定义了旋转插值函数 interpolate_rotations, 它对轴角向量进行线性插值,然后将结果转换回旋转矩阵。

在主程序中,我们构造了两个旋转矩阵 R1R2,分别表示绕 x 轴和 z 轴旋转 90 度。然后,我们选取不同的插值参数 t,计算插值结果并打印出来。

通过这个例子,你可以看到罗德里格斯公式如何被用于旋转插值。这种方法简单直观,而且能保证插值结果始终是一个合法的旋转矩阵。在计算机图形学、动画、机器人等领域,旋转插值是一个非常常见的任务,罗德里格斯公式提供了一个方便的工具。

罗德里格斯公式在计算机视觉和图形学中有广泛应用。例如,在三维重建问题中,我们常常需要估计相机的位姿,即旋转和平移。设相机的旋转用轴角向量 ϕ \boldsymbol{\phi} ϕ 表示,平移用向量 t \mathbf{t} t 表示,则相机的位姿矩阵为:

T = [ R ( ϕ ) t 0 ⊤ 1 ] \mathbf{T} = \begin{bmatrix} R(\boldsymbol{\phi}) & \mathbf{t} \\ \mathbf{0}^\top & 1 \end{bmatrix} T=[R(ϕ)0t1]

其中 R ( ϕ ) R(\boldsymbol{\phi}) R(ϕ) 就是罗德里格斯公式。在优化问题中,我们通常将 ϕ \boldsymbol{\phi} ϕ t \mathbf{t} t 作为优化变量,通过最小化重投影误差来估计相机位姿。

假设我们有一个相机,它在空间中的位置和姿态由旋转矩阵 R R R 和平移向量 t \mathbf{t} t 描述。相机的内参数已知,用矩阵 K K K 表示。现在,我们观察到一组 3D 点 X i \mathbf{X}_i Xi 在图像平面上的投影 x i \mathbf{x}_i xi。我们的目标是估计相机的位姿 ( R , t ) (R, \mathbf{t}) (R,t),使得重投影误差最小化,即:

min ⁡ R , t ∑ i ∥ x i − π ( K ( R X i + t ) ) ∥ 2 \min_{R, \mathbf{t}} \sum_i \|\mathbf{x}_i - \pi(K(R\mathbf{X}_i + \mathbf{t}))\|^2 minR,tixiπ(K(RXi+t))2

其中 π \pi π 是投影函数,它将一个 3D 点投影到图像平面上。

为了使用罗德里格斯公式,我们用轴角向量 ϕ \boldsymbol{\phi} ϕ 来参数化旋转矩阵 R R R。这样,优化问题就变成了:

min ⁡ ϕ , t ∑ i ∥ x i − π ( K ( R ( ϕ ) X i + t ) ) ∥ 2 \min_{\boldsymbol{\phi}, \mathbf{t}} \sum_i \|\mathbf{x}_i - \pi(K(R(\boldsymbol{\phi})\mathbf{X}_i + \mathbf{t}))\|^2 minϕ,tixiπ(K(R(ϕ)Xi+t))2

我们可以用 Levenberg-Marquardt 算法(一种常见的优化算法,这里不做介绍)来求解这个非线性最小二乘问题。在每一步迭代中,我们需要计算雅可比矩阵 J J J,它描述了重投影误差对优化变量 ( ϕ , t ) (\boldsymbol{\phi}, \mathbf{t}) (ϕ,t) 的变化率。这里,我们可以利用罗德里格斯公式的解析形式来计算旋转矩阵 R R R 对轴角向量 ϕ \boldsymbol{\phi} ϕ 的导数。

下面是一个简化的Python示例代码:

import numpy as np
from scipy.optimize import least_squares

def rodrigues(phi):
    """Rodrigues formula for rotation matrix"""
    phi_norm = np.linalg.norm(phi)
    if phi_norm < 1e-8:
        return np.eye(3)
    
    phi_hat = phi / phi_norm
    phi_hat_mat = np.array([[    0,      -phi_hat[2],  phi_hat[1]],
                            [ phi_hat[2],     0,      -phi_hat[0]],
                            [-phi_hat[1],  phi_hat[0],     0      ]])
    
    return np.eye(3) + np.sin(phi_norm)*phi_hat_mat + (1-np.cos(phi_norm))*phi_hat_mat@phi_hat_mat

def project(X, K, R, t):
    """Project 3D points X to 2D points x"""
    X_cam = R @ X.T + t.reshape(-1, 1)
    x = K @ X_cam
    x = x[:2] / x[2]
    return x.T

def reprojection_error(params, X, x, K):
    """Reprojection error"""
    phi, t = params[:3], params[3:]
    R = rodrigues(phi)
    x_proj = project(X, K, R, t)
    return (x - x_proj).ravel()

# 3D points in world coordinates
X = np.array([[0, 0, 1],
              [1, 0, 1],
              [0, 1, 1],
              [1, 1, 1]], dtype=float)

# 2D points in image coordinates  
x = np.array([[100, 200],
              [200, 200], 
              [100, 300],
              [200, 300]], dtype=float)

# Camera intrinsic matrix
K = np.array([[500, 0, 320],
              [0, 500, 240],
              [0,   0,   1]])

# Initial guess for rotation and translation
phi_init = np.array([0, 0, 0]) 
t_init = np.array([0, 0, 1])
params_init = np.concatenate([phi_init, t_init])

# Solve the optimization problem
result = least_squares(reprojection_error, params_init, args=(X, x, K))

# Extract the optimized rotation and translation
phi_opt, t_opt = result.x[:3], result.x[3:]
R_opt = rodrigues(phi_opt)

print(f'Optimized rotation:\n{R_opt}')
print(f'Optimized translation:\n{t_opt}')

在这个例子中,我们有四个 3D 点 X 和它们对应的图像投影 x。相机内参数由矩阵 K 给出。我们定义了旋转矩阵的罗德里格斯参数化 rodrigues,投影函数 project,以及重投影误差函数 reprojection_error。然后,我们使用 scipy.optimize.least_squares 函数来最小化重投影误差,得到最优的旋转和平移估计。相信你大概应该理解如何在优化问题中使用这部分知识了。

关于这个话题的更多内容,可以参考以下文献:

  1. Hartley, R., & Zisserman, A. (2003). Multiple view geometry in computer vision. Cambridge university press.

  2. Szeliski, R. (2010). Computer vision: algorithms and applications. Springer Science & Business Media.

  3. Triggs, B., McLauchlan, P. F., Hartley, R. I., & Fitzgibbon, A. W. (1999, September). Bundle adjustment—a modern synthesis. In International workshop on vision algorithms (pp. 298-372). Springer, Berlin, Heidelberg.

2.3.4 对数映射(The logarithmic maps)

指数映射的逆映射称为对数映射(logarithmic map),它将旋转矩阵 R R R 映射回李代数 s o ( 3 ) \mathfrak{so}(3) so(3):

log ⁡ : S O ( 3 ) → s o ( 3 ) , R ↦ log ⁡ ( R ) = ϕ ^ \log: SO(3) \to \mathfrak{so}(3), \quad R \mapsto \log(R) = \hat{\boldsymbol{\phi}} log:SO(3)so(3),Rlog(R)=ϕ^

对数映射可以通过求解指数映射的反函数得到。具体地,设 R = exp ⁡ ( ϕ ^ ) R = \exp(\hat{\boldsymbol{\phi}}) R=exp(ϕ^),其中 ϕ = ϕ u \boldsymbol{\phi} = \phi\mathbf{u} ϕ=ϕu,则有:

ϕ = arccos ⁡ ( 1 2 ( t r ( R ) − 1 ) ) \phi = \arccos(\frac{1}{2}(\mathrm{tr}(R) - 1)) ϕ=arccos(21(tr(R)1))

u = 1 2 sin ⁡ ϕ ( R − R ⊤ ) ∨ \mathbf{u} = \frac{1}{2\sin\phi}(R - R^\top)^\vee u=2sinϕ1(RR)

其中 t r ( R ) \mathrm{tr}(R) tr(R) 表示矩阵 R R R 的迹, ( ⋅ ) ∨ (\cdot)^\vee () 表示将反对称矩阵转换为向量。

类似地,我们也可以定义大写的对数映射,将旋转矩阵直接映射回轴角向量:

L o g : S O ( 3 ) → R 3 , R ↦ L o g ( R ) = ϕ \mathrm{Log}: SO(3) \to \mathbb{R}^3, \quad R \mapsto \mathrm{Log}(R) = \boldsymbol{\phi} Log:SO(3)R3,RLog(R)=ϕ

其中 ϕ = ϕ u \boldsymbol{\phi} = \phi\mathbf{u} ϕ=ϕu,可以用上面的公式计算。

对数映射在机器人运动规划中有重要应用。例如,在轨迹优化问题中,我们需要在 S O ( 3 ) SO(3) SO(3) 流形上进行优化,以得到最优的旋转轨迹。设机器人末端执行器的旋转轨迹为 R ( t ) R(t) R(t),其中 t t t 为时间参数,则我们可以将轨迹用对数映射参数化:

R ( t ) = exp ⁡ ( ϕ ^ ( t ) ) R(t) = \exp(\hat{\boldsymbol{\phi}}(t)) R(t)=exp(ϕ^(t))

其中 ϕ ( t ) \boldsymbol{\phi}(t) ϕ(t) 是时间 t t t 的函数,表示轴角向量。这样,优化问题就转化为对 ϕ ( t ) \boldsymbol{\phi}(t) ϕ(t) 的优化,可以用标准的优化算法求解。关于这个话题的更多内容,可以参考以下文献:

  1. Park, F. C., & Ravani, B. (1997). Smooth invariant interpolation of rotations. ACM Transactions on Graphics (TOG), 16(3), 277-295.

  2. Ramamoorthy, S., & Rajagopal, R. (2006). Optimal motion planning for humanoid robots. In Robotics: Science and Systems (pp. 3-10).

  3. Selig, J. M. (2004). Lie groups and Lie algebras in robotics. In Computational Noncommutative Algebra and Applications (pp. 101-125). Springer, Dordrecht.

下面是Python中计算对数映射的代码:

import numpy as np

def log_map(R):
    """
    Compute the logarithmic map of a rotation matrix.
    
    Parameters:
        R (numpy.ndarray): 3x3 rotation matrix.
        
    Returns:
        numpy.ndarray: 3D axis-angle vector.
    """
    phi = np.arccos((np.trace(R) - 1) / 2)
    if np.abs(phi) < 1e-8:
        return np.zeros(3)
    else:
        u = 1 / (2 * np.sin(phi)) * np.array([R[2, 1] - R[1, 2], 
                                               R[0, 2] - R[2, 0], 
                                               R[1, 0] - R[0, 1]])
        return phi * u

# Example usage
R = np.array([[0, -1, 0], 
              [1, 0, 0], 
              [0, 0, 1]])
phi = log_map(R)
print(phi)
# Output: [0.         0.         1.57079633]

在这个例子中,我们计算了旋转矩阵 R R R 的对数映射,得到了对应的轴角向量 ϕ = ( 0 , 0 , π / 2 ) \boldsymbol{\phi} = (0, 0, \pi/2) ϕ=(0,0,π/2),表示绕 z z z 轴旋转 9 0 ∘ 90^\circ 90

2.3.5 旋转作用(The rotation action)

旋转矩阵最基本的作用就是将向量进行旋转。设 x ∈ R 3 \mathbf{x} \in \mathbb{R}^3 xR3 是一个三维向量, R ∈ S O ( 3 ) R \in SO(3) RSO(3) 是一个旋转矩阵,则旋转后的向量为:

x ′ = R x \mathbf{x}' = R\mathbf{x} x=Rx

这个运算满足线性性,即 R ( x + y ) = R x + R y R(\mathbf{x} + \mathbf{y}) = R\mathbf{x} + R\mathbf{y} R(x+y)=Rx+Ry,以及可结合性,即 ( R S ) x = R ( S x ) (RS)\mathbf{x} = R(S\mathbf{x}) (RS)x=R(Sx)

我们可以用罗德里格斯公式将旋转矩阵展开,得到向量旋转的另一种表达形式:

x ′ = ( I + sin ⁡ ϕ u ^ + ( 1 − cos ⁡ ϕ ) u ^ 2 ) x \mathbf{x}' = (I + \sin\phi \hat{\mathbf{u}} + (1 - \cos\phi)\hat{\mathbf{u}}^2)\mathbf{x} x=(I+sinϕu^+(1cosϕ)u^2)x
= x + sin ⁡ ϕ ( u × x ) + ( 1 − cos ⁡ ϕ ) ( u ⋅ x ) u = \mathbf{x} + \sin\phi (\mathbf{u} \times \mathbf{x}) + (1 - \cos\phi)(\mathbf{u} \cdot \mathbf{x})\mathbf{u} =x+sinϕ(u×x)+(1cosϕ)(ux)u

这就是我们在2.1节中给出的旋转公式。可以看到,旋转操作实际上是将向量分解为平行于旋转轴和垂直于旋转轴的两个分量,然后对垂直分量进行旋转。

下面是Python中实现向量旋转的代码:

import numpy as np

def rotate_vector(x, phi):
    """
    Rotate a vector by an axis-angle vector.
    
    Parameters:
        x (numpy.ndarray): 3D vector to be rotated.
        phi (numpy.ndarray): 3D axis-angle vector.
        
    Returns:
        numpy.ndarray: Rotated vector.
    """
    phi_norm = np.linalg.norm(phi)
    if phi_norm < 1e-8:
        return x
    else:
        u = phi / phi_norm
        x_parallel = np.dot(u, x) * u
        x_perp = x - x_parallel
        return x_parallel + np.cos(phi_norm) * x_perp + np.sin(phi_norm) * np.cross(u, x)

# Example usage
x = np.array([1, 0, 0])
phi = np.array([0, 0, np.pi / 2])
x_rot = rotate_vector(x, phi)
print(x_rot)
# Output: [0. 1. 0.]

在这个例子中,我们将向量 x = ( 1 , 0 , 0 ) \mathbf{x} = (1, 0, 0) x=(1,0,0) z z z 轴旋转 9 0 ∘ 90^\circ 90,得到旋转后的向量 ( 0 , 1 , 0 ) (0, 1, 0) (0,1,0)

向量旋转在计算机图形学和计算机视觉中有广泛应用。例如,在三维渲染中,我们需要将物体的顶点坐标进行旋转,以实现物体的旋转效果。再如,在相机标定中,我们需要将三维空间点的坐标从世界坐标系转换到相机坐标系,这也需要进行旋转操作。关于这些应用的更多内容,可以参考以下文献:

  1. Dunn, F., & Parberry, I. (2011). 3D math primer for graphics and game development. CRS Press.

  2. Zhang, Z. (2000). A flexible new technique for camera calibration. IEEE Transactions on pattern analysis and machine intelligence, 22(11), 1330-1334.

  3. Hartley, R., & Zisserman, A. (2003). Multiple view geometry in computer vision. Cambridge university press.

2.4 旋转群和四元数(The rotation group and the quaternion)

在前面的章节中,我们主要讨论了旋转矩阵作为 S O ( 3 ) SO(3) SO(3) 群元素的性质。实际上,四元数也可以用来表示三维旋转,并且与旋转矩阵有密切的关系。本节将探讨四元数作为旋转运算符的性质,并建立四元数与旋转矩阵之间的联系。

我们首先给出四元数旋转的定义。设 q = ( q w , q x , q y , q z ) \mathbf{q} = (q_w, q_x, q_y, q_z) q=(qw,qx,qy,qz) 是一个单位四元数,即 ∥ q ∥ = 1 \|\mathbf{q}\| = 1 q=1,则四元数 q \mathbf{q} q 表示的旋转可以写成:

x ′ = q ⊗ x ⊗ q ∗ \mathbf{x}' = \mathbf{q} \otimes \mathbf{x} \otimes \mathbf{q}^* x=qxq

其中 x = ( 0 , x , y , z ) \mathbf{x} = (0, x, y, z) x=(0,x,y,z) 是一个纯四元数,表示三维向量 ( x , y , z ) (x, y, z) (x,y,z), q ∗ \mathbf{q}^* q q \mathbf{q} q 的共轭四元数,即 q ∗ = ( q w , − q x , − q y , − q z ) \mathbf{q}^* = (q_w, -q_x, -q_y, -q_z) q=(qw,qx,qy,qz), ⊗ \otimes 表示四元数乘法。

下面我们来证明这个旋转定义的正确性,并探讨其几何意义。

2.4.1 指数映射(The exponential map)

类似于旋转矩阵,四元数也可以用指数映射来表示。设 ϕ = ϕ u \boldsymbol{\phi} = \phi\mathbf{u} ϕ=ϕu 是一个轴角向量,则对应的四元数为:

q ( ϕ ) = exp ⁡ ( 1 2 ϕ ^ ) = cos ⁡ ϕ 2 + sin ⁡ ϕ 2 u \mathbf{q}(\boldsymbol{\phi}) = \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) = \cos\frac{\phi}{2} + \sin\frac{\phi}{2}\mathbf{u} q(ϕ)=exp(21ϕ^)=cos2ϕ+sin2ϕu

其中 ϕ ^ = ( 0 , ϕ x , ϕ y , ϕ z ) \hat{\boldsymbol{\phi}} = (0, \phi_x, \phi_y, \phi_z) ϕ^=(0,ϕx,ϕy,ϕz) ϕ \boldsymbol{\phi} ϕ 对应的纯四元数。

这个公式可以从四元数的指数映射定义推导出来。根据第一章的内容,纯四元数 v \mathbf{v} v 的指数映射为:

exp ⁡ ( v ) = cos ⁡ ∥ v ∥ + sin ⁡ ∥ v ∥ ∥ v ∥ v \exp(\mathbf{v}) = \cos\|\mathbf{v}\| + \frac{\sin\|\mathbf{v}\|}{\|\mathbf{v}\|}\mathbf{v} exp(v)=cosv+vsinvv

v = 1 2 ϕ ^ \mathbf{v} = \frac{1}{2}\hat{\boldsymbol{\phi}} v=21ϕ^,则有:

exp ⁡ ( 1 2 ϕ ^ ) = cos ⁡ ϕ 2 + sin ⁡ ϕ 2 u \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) = \cos\frac{\phi}{2} + \sin\frac{\phi}{2}\mathbf{u} exp(21ϕ^)=cos2ϕ+sin2ϕu

这就得到了四元数的指数映射公式。可以看到,四元数的旋转角度是轴角向量的一半,这是四元数表示旋转的一个重要特点。

2.4.2 大写指数映射(The capitalized exponential map)

与旋转矩阵类似,我们也可以定义四元数的大写指数映射,将轴角向量直接映射为四元数:

E x p : R 3 → S 3 , ϕ ↦ q ( ϕ ) = exp ⁡ ( 1 2 ϕ ^ ) \mathrm{Exp}: \mathbb{R}^3 \to S^3, \quad \boldsymbol{\phi} \mapsto \mathbf{q}(\boldsymbol{\phi}) = \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) Exp:R3S3,ϕq(ϕ)=exp(21ϕ^)

其中 S 3 S^3 S3 表示单位四元数组成的集合,它是一个三维球面。

下面是Python中计算四元数指数映射的代码:

import numpy as np

def quaternion_exp(phi):
    """
    Compute the quaternion exponential map.
    
    Parameters:
        phi (numpy.ndarray): 3D axis-angle vector.
        
    Returns:
        numpy.ndarray: Unit quaternion.
    """
    phi_norm = np.linalg.norm(phi)
    if phi_norm < 1e-8:
        return np.array([1, 0, 0, 0])
    else:
        u = phi / phi_norm
        return np.array([np.cos(phi_norm/2), 
                         np.sin(phi_norm/2) * u[0], 
                         np.sin(phi_norm/2) * u[1], 
                         np.sin(phi_norm/2) * u[2]])

# Example usage
phi = np.array([0, 0, np.pi / 2])
q = quaternion_exp(phi)
print(q)
# Output: [0.70710678 0.         0.         0.70710678]

在这个例子中,我们计算了轴角向量 ϕ = ( 0 , 0 , π / 2 ) \boldsymbol{\phi} = (0, 0, \pi/2) ϕ=(0,0,π/2) 对应的四元数,得到 q = ( 0.7071 , 0 , 0 , 0.7071 ) \mathbf{q} = (0.7071, 0, 0, 0.7071) q=(0.7071,0,0,0.7071),表示绕 z z z 轴旋转 9 0 ∘ 90^\circ 90

2.4.3 四元数和旋转向量(Quaternion and rotation vector)

根据上面的讨论,我们可以直接将轴角向量 ϕ \boldsymbol{\phi} ϕ 转换为四元数 q \mathbf{q} q,这个过程称为旋转向量到四元数的转换(rotation vector to quaternion conversion):

q ( ϕ ) = cos ⁡ ϕ 2 + sin ⁡ ϕ 2 u = ( cos ⁡ ϕ 2 , sin ⁡ ϕ 2 u ) \mathbf{q}(\boldsymbol{\phi}) = \cos\frac{\phi}{2} + \sin\frac{\phi}{2}\mathbf{u} = (\cos\frac{\phi}{2}, \sin\frac{\phi}{2}\mathbf{u}) q(ϕ)=cos2ϕ+sin2ϕu=(cos2ϕ,sin2ϕu)

其中 ϕ = ∥ ϕ ∥ \phi = \|\boldsymbol{\phi}\| ϕ=ϕ 是旋转角度, u = ϕ / ϕ \mathbf{u} = \boldsymbol{\phi}/\phi u=ϕ/ϕ 是旋转轴。

下面是Python中实现这个转换的代码:

import numpy as np

def rotation_vector_to_quaternion(phi):
    """
    Convert a rotation vector to a quaternion.
    
    Parameters:
        phi (numpy.ndarray): 3D rotation vector.
        
    Returns:
        numpy.ndarray: Unit quaternion.
    """
    phi_norm = np.linalg.norm(phi)
    if phi_norm < 1e-8:
        return np.array([1, 0, 0, 0])
    else:
        u = phi / phi_norm
        return np.array([np.cos(phi_norm/2), 
                         np.sin(phi_norm/2) * u[0], 
                         np.sin(phi_norm/2) * u[1], 
                         np.sin(phi_norm/2) * u[2]])

# Example usage
phi = np.array([0, 0, np.pi / 2])
q = rotation_vector_to_quaternion(phi)
print(q)
# Output: [0.70710678 0.         0.         0.70710678]

这个例子与前面的例子结果相同,说明旋转向量到四元数的转换与四元数指数映射是等价的。

2.4.4 对数映射(The logarithmic maps)

类似地,我们也可以定义四元数的对数映射,将单位四元数映射回轴角向量:

log ⁡ : S 3 → R 3 , q ↦ ϕ ( q ) = 2 log ⁡ ( q ) \log: S^3 \to \mathbb{R}^3, \quad \mathbf{q} \mapsto \boldsymbol{\phi}(\mathbf{q}) = 2\log(\mathbf{q}) log:S3R3,qϕ(q)=2log(q)

其中对数 log ⁡ ( q ) \log(\mathbf{q}) log(q) 的计算方法在第一章已经给出。对数映射可以用来计算两个旋转之间的差异,即旋转之间的"距离"。

下面是Python中计算四元数对数映射的代码:

import numpy as np

def quaternion_log(q):
    """
    Compute the quaternion logarithmic map.
    
    Parameters:
        q (numpy.ndarray): Unit quaternion.
        
    Returns:
        numpy.ndarray: 3D axis-angle vector.
    """
    q_norm = np.linalg.norm(q)
    if np.abs(q_norm - 1) > 1e-8:
        raise ValueError("Input must be a unit quaternion.")
        
    if np.abs(q[0] - 1) < 1e-8:
        return np.zeros(3)
    else:
        u = q[1:] / np.linalg.norm(q[1:])
        phi = 2 * np.arccos(q[0])
        return phi * u

# Example usage
q = np.array([0.70710678, 0, 0, 0.70710678])
phi = quaternion_log(q)
print(phi)
# Output: [0.         0.         1.57079633]

在这个例子中,我们计算了四元数 q = ( 0.7071 , 0 , 0 , 0.7071 ) \mathbf{q} = (0.7071, 0, 0, 0.7071) q=(0.7071,0,0,0.7071) 的对数映射,得到轴角向量 ϕ = ( 0 , 0 , π / 2 ) \boldsymbol{\phi} = (0, 0, \pi/2) ϕ=(0,0,π/2),与原来的旋转一致。

2.4.5 旋转作用(The rotation action)

现在我们来证明四元数旋转的正确性。根据定义,四元数 q \mathbf{q} q 表示的旋转为:

x ′ = q ⊗ x ⊗ q ∗ \mathbf{x}' = \mathbf{q} \otimes \mathbf{x} \otimes \mathbf{q}^* x=qxq

其中 x = ( 0 , x , y , z ) \mathbf{x} = (0, x, y, z) x=(0,x,y,z) 是待旋转的向量,写成纯四元数的形式。

q = exp ⁡ ( 1 2 ϕ ^ ) = ( q w , q x , q y , q z ) \mathbf{q} = \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) = (q_w, q_x, q_y, q_z) q=exp(21ϕ^)=(qw,qx,qy,qz),其中 ϕ = ϕ u \boldsymbol{\phi} = \phi\mathbf{u} ϕ=ϕu,则有:

q = ( cos ⁡ ϕ 2 , sin ⁡ ϕ 2 u x , sin ⁡ ϕ 2 u y , sin ⁡ ϕ 2 u z ) \mathbf{q} = (\cos\frac{\phi}{2}, \sin\frac{\phi}{2}u_x, \sin\frac{\phi}{2}u_y, \sin\frac{\phi}{2}u_z) q=(cos2ϕ,sin2ϕux,sin2ϕuy,sin2ϕuz)

q ∗ = ( cos ⁡ ϕ 2 , − sin ⁡ ϕ 2 u x , − sin ⁡ ϕ 2 u y , − sin ⁡ ϕ 2 u z ) \mathbf{q}^* = (\cos\frac{\phi}{2}, -\sin\frac{\phi}{2}u_x, -\sin\frac{\phi}{2}u_y, -\sin\frac{\phi}{2}u_z) q=(cos2ϕ,sin2ϕux,sin2ϕuy,sin2ϕuz)

将它们代入四元数旋转公式,经过一系列化简,可以得到:

x ′ = x ∥ + x ⊥ cos ⁡ ϕ + ( u × x ) sin ⁡ ϕ \mathbf{x}' = \mathbf{x}_\parallel + \mathbf{x}_\perp \cos\phi + (\mathbf{u} \times \mathbf{x})\sin\phi x=x+xcosϕ+(u×x)sinϕ

其中 x ∥ = ( u ⋅ x ) u \mathbf{x}_\parallel = (\mathbf{u} \cdot \mathbf{x})\mathbf{u} x=(ux)u x \mathbf{x} x u \mathbf{u} u 上的投影, x ⊥ = x − x ∥ \mathbf{x}_\perp = \mathbf{x} - \mathbf{x}_\parallel x=xx x \mathbf{x} x 在垂直于 u \mathbf{u} u 的平面内的分量。这正是我们在2.1节中给出的向量旋转公式。

这个结果表明,四元数旋转与轴角旋转是完全等价的,只是表示方式不同。四元数旋转的优点是运算更加简洁,且没有万向锁问题。

下面是Python中实现四元数旋转的代码:

import numpy as np

def quaternion_rotate(x, q):
    """
    Rotate a vector by a quaternion.
    
    Parameters:
        x (numpy.ndarray): 3D vector to be rotated.
        q (numpy.ndarray): Unit quaternion representing the rotation.
        
    Returns:
        numpy.ndarray: Rotated vector.
    """
    q_norm = np.linalg.norm(q)
    if np.abs(q_norm - 1) > 1e-8:
        raise ValueError("Input must be a unit quaternion.")
        
    x_quat = np.concatenate(([0], x))
    q_conj = np.array([q[0], -q[1], -q[2], -q[3]])
    
    x_rot_quat = quaternion_mult(quaternion_mult(q, x_quat), q_conj)
    return x_rot_quat[1:]

def quaternion_mult(q1, q2):
    """
    Multiply two quaternions.
    
    Parameters:
        q1 (numpy.ndarray): First quaternion.
        q2 (numpy.ndarray): Second quaternion.
        
    Returns:
        numpy.ndarray: Quaternion product.
    """
    w1, x1, y1, z1 = q1
    w2, x2, y2, z2 = q2
    
    w = w1*w2 - x1*x2 - y1*y2 - z1*z2
    x = w1*x2 + x1*w2 + y1*z2 - z1*y2
    y = w1*y2 - x1*z2 + y1*w2 + z1*x2 
    z = w1*z2 + x1*y2 - y1*x2 + z1*w2
    
    return np.array([w, x, y, z])

# Example usage
x = np.array([1, 0, 0])
phi = np.array([0, 0, np.pi/2])
q = rotation_vector_to_quaternion(phi)

x_rot = quaternion_rotate(x, q)
print(x_rot)
# Output: [0. 1. 0.]

在这个例子中,我们将向量 x = ( 1 , 0 , 0 ) \mathbf{x} = (1, 0, 0) x=(1,0,0) z z z 轴旋转 9 0 ∘ 90^\circ 90,得到旋转后的向量 ( 0 , 1 , 0 ) (0, 1, 0) (0,1,0),与旋转矩阵的结果一致。

2.4.6 SO(3)流形的双重覆盖

The double cover of the manifold of SO(3).

需要注意的是,四元数对 S O ( 3 ) SO(3) SO(3) 流形的表示是双射(double cover),即每个旋转都对应两个四元数,它们互为相反数。这是因为四元数 q \mathbf{q} q − q -\mathbf{q} q 表示的旋转是相同的:

( − q ) ⊗ x ⊗ ( − q ) ∗ = q ⊗ x ⊗ q ∗ (-\mathbf{q}) \otimes \mathbf{x} \otimes (-\mathbf{q})^* = \mathbf{q} \otimes \mathbf{x} \otimes \mathbf{q}^* (q)x(q)=qxq

这个性质可以从四元数的指数映射看出来。设 q = exp ⁡ ( 1 2 ϕ ^ ) \mathbf{q} = \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) q=exp(21ϕ^),则 − q = exp ⁡ ( 1 2 ( ϕ ^ + 2 π u ^ ) ) -\mathbf{q} = \exp(\frac{1}{2}(\hat{\boldsymbol{\phi}} + 2\pi\hat{\mathbf{u}})) q=exp(21(ϕ^+2πu^)),其中 u ^ \hat{\mathbf{u}} u^ u \mathbf{u} u 对应的纯四元数。这表明 q \mathbf{q} q − q -\mathbf{q} q 对应的旋转角度相差 2 π 2\pi 2π,因此表示的是同一个旋转。

四元数的这个性质有时会带来不便,特别是在进行四元数插值时。我将在后面的章节中详细讨论如何处理这个问题。

2.5 旋转矩阵和四元数(Rotation matrix and quaternion)

在前面的讨论中,我们已经看到,旋转矩阵和四元数都可以用来表示三维旋转,且有许多相似的性质。现在我们来探讨它们之间的联系。

首先,我们知道,对于任意旋转,都存在一个轴角向量 ϕ \boldsymbol{\phi} ϕ,使得旋转矩阵 R R R 和四元数 q \mathbf{q} q 可以分别写成:

R = exp ⁡ ( ϕ ^ ) = I + sin ⁡ ϕ u ^ + ( 1 − cos ⁡ ϕ ) u ^ 2 R = \exp(\hat{\boldsymbol{\phi}}) = I + \sin\phi\hat{\mathbf{u}} + (1-\cos\phi)\hat{\mathbf{u}}^2 R=exp(ϕ^)=I+sinϕu^+(1cosϕ)u^2

q = exp ⁡ ( 1 2 ϕ ^ ) = ( cos ⁡ ϕ 2 , sin ⁡ ϕ 2 u ) \mathbf{q} = \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) = (\cos\frac{\phi}{2}, \sin\frac{\phi}{2}\mathbf{u}) q=exp(21ϕ^)=(cos2ϕ,sin2ϕu)

其中 ϕ = ∥ ϕ ∥ \phi = \|\boldsymbol{\phi}\| ϕ=ϕ 是旋转角度, u = ϕ / ϕ \mathbf{u} = \boldsymbol{\phi}/\phi u=ϕ/ϕ 是旋转轴。

这两个表达式蕴含了旋转矩阵和四元数之间的转换关系。具体地,给定一个四元数 q = ( q w , q x , q y , q z ) \mathbf{q} = (q_w, q_x, q_y, q_z) q=(qw,qx,qy,qz),对应的旋转矩阵为:

R ( q ) = [ 1 − 2 ( q y 2 + q z 2 ) 2 ( q x q y − q w q z ) 2 ( q x q z + q w q y ) 2 ( q x q y + q w q z ) 1 − 2 ( q x 2 + q z 2 ) 2 ( q y q z − q w q x ) 2 ( q x q z − q w q y ) 2 ( q y q z + q w q x ) 1 − 2 ( q x 2 + q y 2 ) ] R(\mathbf{q}) = \begin{bmatrix} 1-2(q_y^2+q_z^2) & 2(q_xq_y-q_wq_z) & 2(q_xq_z+q_wq_y) \\ 2(q_xq_y+q_wq_z) & 1-2(q_x^2+q_z^2) & 2(q_yq_z-q_wq_x) \\ 2(q_xq_z-q_wq_y) & 2(q_yq_z+q_wq_x) & 1-2(q_x^2+q_y^2) \end{bmatrix} R(q)= 12(qy2+qz2)2(qxqy+qwqz)2(qxqzqwqy)2(qxqyqwqz)12(qx2+qz2)2(qyqz+qwqx)2(qxqz+qwqy)2(qyqzqwqx)12(qx2+qy2)

反之,给定一个旋转矩阵 R R R,对应的四元数为:

q ( R ) = { ( 1 + R 11 + R 22 + R 33 , R 32 − R 23 4 q w , R 13 − R 31 4 q w , R 21 − R 12 4 q w ) , q w ≠ 0 ( R 21 − R 12 4 q x , 1 + R 11 − R 22 − R 33 2 , R 12 + R 21 4 q x , R 31 + R 13 4 q x ) , q x ≠ 0 ( R 13 − R 31 4 q y , R 12 + R 21 4 q y , 1 − R 11 + R 22 − R 33 2 , R 23 + R 32 4 q y ) , q y ≠ 0 ( R 32 − R 23 4 q z , R 31 + R 13 4 q z , R 23 + R 32 4 q z , 1 − R 11 − R 22 + R 33 2 ) , q z ≠ 0 \mathbf{q}(R) = \begin{cases} (\sqrt{1+R_{11}+R_{22}+R_{33}}, \frac{R_{32}-R_{23}}{4q_w}, \frac{R_{13}-R_{31}}{4q_w}, \frac{R_{21}-R_{12}}{4q_w}), & q_w \neq 0 \\ (\frac{R_{21}-R_{12}}{4q_x}, \sqrt{\frac{1+R_{11}-R_{22}-R_{33}}{2}}, \frac{R_{12}+R_{21}}{4q_x}, \frac{R_{31}+R_{13}}{4q_x}), & q_x \neq 0 \\ (\frac{R_{13}-R_{31}}{4q_y}, \frac{R_{12}+R_{21}}{4q_y}, \sqrt{\frac{1-R_{11}+R_{22}-R_{33}}{2}}, \frac{R_{23}+R_{32}}{4q_y}), & q_y \neq 0 \\ (\frac{R_{32}-R_{23}}{4q_z}, \frac{R_{31}+R_{13}}{4q_z}, \frac{R_{23}+R_{32}}{4q_z}, \sqrt{\frac{1-R_{11}-R_{22}+R_{33}}{2}}), & q_z \neq 0 \end{cases} q(R)= (1+R11+R22+R33 ,4qwR32R23,4qwR13R31,4qwR21R12),(4qxR21R12,21+R11R22R33 ,4qxR12+R21,4qxR31+R13),(4qyR13R31,4qyR12+R21,21R11+R22R33 ,4qyR23+R32),(4qzR32R23,4qzR31+R13,4qzR23+R32,21R11R22+R33 ),qw=0qx=0qy=0qz=0

其中 R i j R_{ij} Rij 表示旋转矩阵 R R R 的第 i i i 行第 j j j 列元素。这个公式看起来很复杂,但它的本质是将旋转矩阵分解为四个四元数分量。在实际应用中,我们通常根据旋转矩阵的特点选择最稳定的分支来计算四元数。

下面是Python中实现旋转矩阵和四元数相互转换的代码:

import numpy as np

def quaternion_to_matrix(q):
    """
    Convert a quaternion to a rotation matrix.
    
    Parameters:
        q (numpy.ndarray): Unit quaternion.
        
    Returns:
        numpy.ndarray: 3x3 rotation matrix.
    """
    q_norm = np.linalg.norm(q)
    if np.abs(q_norm - 1) > 1e-8:
        raise ValueError("Input must be a unit quaternion.")
        
    q_w, q_x, q_y, q_z = q
    
    return np.array([[1-2*(q_y**2+q_z**2), 2*(q_x*q_y-q_w*q_z), 2*(q_x*q_z+q_w*q_y)], 
                     [2*(q_x*q_y+q_w*q_z), 1-2*(q_x**2+q_z**2), 2*(q_y*q_z-q_w*q_x)],
                     [2*(q_x*q_z-q_w*q_y), 2*(q_y*q_z+q_w*q_x), 1-2*(q_x**2+q_y**2)]])

def matrix_to_quaternion(R):
    """
    Convert a rotation matrix to a quaternion.
    
    Parameters:
        R (numpy.ndarray): 3x3 rotation matrix.
        
    Returns:
        numpy.ndarray: Unit quaternion.
    """
    if np.linalg.norm(R.T @ R - np.eye(3)) > 1e-8:
        raise ValueError("Input must be a rotation matrix.")
        
    q = np.zeros(4)
    
    q_w = 0.5 * np.sqrt(1 + R[0,0] + R[1,1] + R[2,2])
    if q_w > 1e-8:
        q[0] = q_w
        q[1] = (R[2,1] - R[1,2]) / (4*q_w)
        q[2] = (R[0,2] - R[2,0]) / (4*q_w)
        q[3] = (R[1,0] - R[0,1]) / (4*q_w)
    else:
        if R[0,0] > R[1,1] and R[0,0] > R[2,2]:
            q_x = 0.5 * np.sqrt(1 + R[0,0] - R[1,1] - R[2,2])
            q[1] = q_x
            q[0] = (R[2,1] - R[1,2]) / (4*q_x)
            q[2] = (R[1,0] + R[0,1]) / (4*q_x)
            q[3] = (R[0,2] + R[2,0]) / (4*q_x)
        elif R[1,1] > R[2,2]:
            q_y = 0.5 * np.sqrt(1 - R[0,0] + R[1,1] - R[2,2])  
            q[2] = q_y
            q[0] = (R[0,2] - R[2,0]) / (4*q_y)
            q[1] = (R[1,0] + R[0,1]) / (4*q_y)
            q[3] = (R[2,1] + R[1,2]) / (4*q_y)
        else:
            q_z = 0.5 * np.sqrt(1 - R[0,0] - R[1,1] + R[2,2])
            q[3] = q_z
            q[0] = (R[1,0] - R[0,1]) / (4*q_z)
            q[1] = (R[0,2] + R[2,0]) / (4*q_z)
            q[2] = (R[2,1] + R[1,2]) / (4*q_z)
            
    return q

# Example usage
phi = np.array([0, 0, np.pi/2])
q = rotation_vector_to_quaternion(phi)
R = quaternion_to_matrix(q)
print(R)
# Output: 
# [[ 0. -1.  0.]
#  [ 1.  0.  0.]
#  [ 0.  0.  1.]]

q_recover = matrix_to_quaternion(R)
print(q_recover)
# Output: [0.70710678 0.         0.         0.70710678]

在这个例子中,我们先将轴角向量 ϕ = ( 0 , 0 , π / 2 ) \boldsymbol{\phi} = (0, 0, \pi/2) ϕ=(0,0,π/2) 转换为四元数 q \mathbf{q} q,然后将 q \mathbf{q} q 转换为旋转矩阵 R R R,再将 R R R 转换回四元数 q r e c o v e r \mathbf{q}_{recover} qrecover。可以看到,最终得到的四元数与原来的四元数一致,说明转换是可逆的。

旋转矩阵和四元数的转换在实际应用中非常有用。例如,在三维计算机视觉中,我们经常需要估计相机的位姿,其中旋转部分可以用旋转矩阵或四元数来表示。由于四元数更加紧凑,且没有奇异性,因此在优化和传输过程中通常更倾向于使用四元数。但在某些计算中,如点云配准,旋转矩阵可能更方便。能够在两种表示之间自如转换,可以让我们灵活选择更适合的方法。关于这个话题的更多内容,可以参考以下文献:

  1. Blanco, J. L. (2010). A tutorial on SE(3) transformation parameterizations and on-manifold optimization. University of Malaga, Tech. Rep, 3.

  2. Diebel, J. (2006). Representing attitude: Euler angles, unit quaternions, and rotation vectors. Matrix, 58(15-16), 1-35.

  3. Trawny, N., & Roumeliotis, S. I. (2005). Indirect Kalman filter for 3D attitude estimation. University of Minnesota, Dept. of Comp. Sci. & Eng., Tech. Rep, 2, 2005.

2.6 旋转复合(Rotation composition)

旋转的一个重要性质是可以复合,即多个旋转的组合仍然是一个旋转。这个性质在旋转矩阵和四元数中都有体现。

对于旋转矩阵,旋转的复合对应于矩阵的乘法。设 R 1 R_1 R1 R 2 R_2 R2 是两个旋转矩阵,则它们的复合 R = R 1 R 2 R = R_1R_2 R=R1R2 也是一个旋转矩阵,表示先进行 R 2 R_2 R2 旋转,再进行 R 1 R_1 R1 旋转的结果。

对于四元数,旋转的复合对应于四元数的乘法。设 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2 是两个单位四元数,则它们的复合 q = q 1 ⊗ q 2 \mathbf{q} = \mathbf{q}_1 \otimes \mathbf{q}_2 q=q1q2 也是一个单位四元数,表示先进行 q 2 \mathbf{q}_2 q2 旋转,再进行 q 1 \mathbf{q}_1 q1 旋转的结果。

下面的Python代码演示了如何进行旋转复合:

import numpy as np

def quaternion_multiply(q1, q2):
    """
    Multiply two quaternions.
    
    Parameters:
        q1 (numpy.ndarray): First quaternion.
        q2 (numpy.ndarray): Second quaternion.
        
    Returns:
        numpy.ndarray: Quaternion product.
    """
    w1, x1, y1, z1 = q1
    w2, x2, y2, z2 = q2
    
    w = w1*w2 - x1*x2 - y1*y2 - z1*z2
    x = w1*x2 + x1*w2 + y1*z2 - z1*y2
    y = w1*y2 - x1*z2 + y1*w2 + z1*x2 
    z = w1*z2 + x1*y2 - y1*x2 + z1*w2
    
    return np.array([w, x, y, z])

def matrix_multiply(R1, R2):
    """
    Multiply two rotation matrices.
    
    Parameters:
        R1 (numpy.ndarray): First rotation matrix.
        R2 (numpy.ndarray): Second rotation matrix.
        
    Returns:
        numpy.ndarray: Matrix product.
    """
    return R1 @ R2

# Example usage
phi1 = np.array([0, 0, np.pi/4])
phi2 = np.array([0, 0, np.pi/3])

q1 = rotation_vector_to_quaternion(phi1)
q2 = rotation_vector_to_quaternion(phi2)
q = quaternion_multiply(q1, q2)
print(q)
# Output: [0.70710678 0.         0.         0.70710678]

R1 = quaternion_to_matrix(q1)
R2 = quaternion_to_matrix(q2)
R = matrix_multiply(R1, R2)
print(R)
# Output:
#[[ 0.5       -0.8660254  0.       ]
#  [ 0.8660254  0.5        0.       ]
#  [ 0.         0.         1.       ]]

在这个例子中,我们分别定义了两个旋转,一个绕 z z z 轴旋转 4 5 ∘ 45^\circ 45,另一个绕 z z z 轴旋转 6 0 ∘ 60^\circ 60。我们先将它们转换为四元数 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2,然后计算它们的乘积 q \mathbf{q} q,得到复合旋转的四元数表示。类似地,我们也计算了对应的旋转矩阵 R 1 R_1 R1 R 2 R_2 R2 和它们的乘积 R R R,得到复合旋转的矩阵表示。可以看到,无论是用四元数还是旋转矩阵,我们都得到了相同的复合旋转结果。

旋转复合在许多应用中都很重要。例如,在机器人运动学中,我们通常需要将机器人各个关节的旋转复合起来,得到末端执行器的位姿。再如,在图形学中,我们可能需要将物体的局部旋转和整体旋转复合起来,得到最终的旋转效果。掌握旋转复合的方法和性质,可以帮助我们更好地分析和控制三维旋转。

2.7 球面线性插值(SLERP)

Spherical linear interpolation (SLERP)

在许多应用中,我们需要在两个旋转之间进行插值,得到一个平滑的旋转过渡。对于三维向量,我们通常使用线性插值(lerp)来实现这一目标。然而,对于旋转而言,线性插值并不能保证插值结果仍然是一个旋转,因为旋转矩阵和四元数所形成的流形是非线性的。

为了在旋转之间进行插值,我们需要使用球面线性插值(Spherical Linear Interpolation, SLERP)。SLERP 是一种在四元数间插值的方法,它保证插值结果仍然是一个单位四元数,对应于一个有效的旋转。

给定两个单位四元数 q 0 \mathbf{q}_0 q0 q 1 \mathbf{q}_1 q1,以及一个插值系数 t ∈ [ 0 , 1 ] t \in [0, 1] t[0,1],SLERP 得到的插值结果为:

s l e r p ( q 0 , q 1 , t ) = sin ⁡ ( ( 1 − t ) Ω ) sin ⁡ Ω q 0 + sin ⁡ ( t Ω ) sin ⁡ Ω q 1 \mathrm{slerp}(\mathbf{q}_0, \mathbf{q}_1, t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}\mathbf{q}_0 + \frac{\sin(t\Omega)}{\sin\Omega}\mathbf{q}_1 slerp(q0,q1,t)=sinΩsin((1t)Ω)q0+sinΩsin(tΩ)q1

其中 Ω \Omega Ω q 0 \mathbf{q}_0 q0 q 1 \mathbf{q}_1 q1 之间的夹角,可以通过四元数的点积得到:

cos ⁡ Ω = q 0 ⋅ q 1 \cos\Omega = \mathbf{q}_0 \cdot \mathbf{q}_1 cosΩ=q0q1

可以看到,当 t = 0 t=0 t=0 时,SLERP 得到 q 0 \mathbf{q}_0 q0;当 t = 1 t=1 t=1 时,得到 q 1 \mathbf{q}_1 q1;当 t t t 在 0 到 1 之间变化时,得到的四元数在 q 0 \mathbf{q}_0 q0 q 1 \mathbf{q}_1 q1 之间平滑过渡,且始终保持单位长度。

下面是 Python 中实现 SLERP 的代码:

import numpy as np

def slerp(q0, q1, t):
    """
    Spherical linear interpolation between two quaternions.
    
    Parameters:
        q0 (numpy.ndarray): Start quaternion.
        q1 (numpy.ndarray): End quaternion.
        t (float): Interpolation coefficient.
        
    Returns:
        numpy.ndarray: Interpolated quaternion.
    """
    # Ensure shortest path
    if np.dot(q0, q1) < 0:
        q1 = -q1
        
    # Compute cosine of angle between quaternions
    dot = np.dot(q0, q1)
    dot = np.clip(dot, -1., 1.)
    
    # Compute interpolation coefficients
    theta_0 = np.arccos(dot)
    theta = theta_0 * t
    sin_theta = np.sin(theta)
    sin_theta_0 = np.sin(theta_0)
    
    s0 = np.cos(theta) - dot * sin_theta / sin_theta_0
    s1 = sin_theta / sin_theta_0
    
    return s0 * q0 + s1 * q1

# Example usage
q0 = rotation_vector_to_quaternion(np.array([0, 0, 0]))
q1 = rotation_vector_to_quaternion(np.array([0, 0, np.pi/2]))

t_vals = np.linspace(0, 1, 5)
for t in t_vals:
    q_interp = slerp(q0, q1, t)
    print(f"t = {t:.1f}: {q_interp}")
    
# Output:
# t = 0.0: [1. 0. 0. 0.]
# t = 0.2: [0.92387953 0.         0.         0.38268343]
# t = 0.5: [0.70710678 0.         0.         0.70710678]
# t = 0.8: [0.38268343 0.         0.         0.92387953]
# t = 1.0: [0.         0.         0.         1.        ]

在这个例子中,我们在单位四元数 ( 1 , 0 , 0 , 0 ) (1, 0, 0, 0) (1,0,0,0) ( 0 , 0 , 0 , 1 ) (0, 0, 0, 1) (0,0,0,1) 之间进行插值,后者表示绕 z z z 轴旋转 9 0 ∘ 90^\circ 90 的旋转。我们选取 5 个插值系数 t t t,得到插值过程中的四元数。可以看到,随着 t t t 从 0 变化到 1,四元数平滑地从起始旋转过渡到结束旋转,且始终保持单位长度。

SLERP 在计算机图形学和动画中有广泛应用。例如,在角色动画中,我们可以用 SLERP 在不同的关键帧之间生成平滑的旋转过渡。在三维游戏中,SLERP 可以用来实现相机的平滑转动效果。此外,SLERP 还可以用于机器人运动规划,生成平滑的轨迹。总之,掌握 SLERP 的原理和实现,可以帮助我们更好地处理旋转插值问题。关于 SLERP 的更多内容,可以参考以下文献:

  1. Shoemake, K. (1985). Animating rotation with quaternion curves. ACM SIGGRAPH computer graphics, 19(3), 245-254.

  2. Dam, E. B., Koch, M., & Lillholm, M. (1998). Quaternions, interpolation and animation (Vol. 2). Copenhagen: Datalogisk Institut, Københavns Universitet.

  3. Hast, A., Nysjö, J., & Marchetti, A. (2013). Optimal SLERP interpolation. Journal of Graphics Tools, 16(4), 201-214.

需要注意的是,由于四元数的双覆盖性,用四元数表示的旋转有两种选择,即 q \mathbf{q} q − q -\mathbf{q} q 表示同一个旋转。在做 SLERP 时,我们需要保证两个四元数在同一条覆盖上,否则会得到错误的插值结果。一种常见的处理方法是比较两个四元数的点积:如果点积为负,说明它们在不同的覆盖上,我们只需要将其中一个四元数取负即可。你可以在上述代码中看到这个技巧。

2.8 四元数和等角旋转:揭示奥秘(Quaternion and isoclinic rotations: explaining the magic)

四元数之所以能如此巧妙地表示三维旋转,背后有深刻的数学原理。本节我们将从四元数的几何解释出发,揭示其中的奥秘。

我们知道,三维空间中的一个旋转可以用一个旋转轴和一个旋转角来表示。对于四元数 q = ( q w , q x , q y , q z ) \mathbf{q} = (q_w, q_x, q_y, q_z) q=(qw,qx,qy,qz),我们可以将其写成指数形式:

q = exp ⁡ ( 1 2 ϕ ^ ) = cos ⁡ ϕ 2 + sin ⁡ ϕ 2 u \mathbf{q} = \exp(\frac{1}{2}\hat{\boldsymbol{\phi}}) = \cos\frac{\phi}{2} + \sin\frac{\phi}{2}\mathbf{u} q=exp(21ϕ^)=cos2ϕ+sin2ϕu

其中 ϕ = ϕ u \boldsymbol{\phi} = \phi\mathbf{u} ϕ=ϕu 是旋转向量, ϕ \phi ϕ 是旋转角, u \mathbf{u} u 是单位旋转轴。这个表达式有两个重要的几何含义:

  1. 四元数 q \mathbf{q} q 可以看作是三维单位球面 S 3 S^3 S3 上的一个点,其中 S 3 S^3 S3 嵌入在四维欧氏空间 R 4 \mathbb{R}^4 R4 中。这个球面的半径为 1,方程为 q w 2 + q x 2 + q y 2 + q z 2 = 1 q_w^2 + q_x^2 + q_y^2 + q_z^2 = 1 qw2+qx2+qy2+qz2=1

  2. 四元数 q \mathbf{q} q 表示的旋转角度是 ϕ \phi ϕ,是旋转向量 ϕ \boldsymbol{\phi} ϕ 的长度的两倍。这就是为什么我们在指数中要除以 2。

现在让我们考虑四元数的乘法 q 1 ⊗ q 2 \mathbf{q}_1 \otimes \mathbf{q}_2 q1q2。从几何上看,这个乘积对应于 S 3 S^3 S3 上的两个点 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2 的复合旋转。更具体地说,它对应于 R 4 \mathbb{R}^4 R4 中的两个旋转:一个是左乘 q 1 \mathbf{q}_1 q1,另一个是右乘 q 2 \mathbf{q}_2 q2。令人惊讶的是,这两个旋转都是四维空间中的等角旋转(isoclinic rotation)!

等角旋转是四维空间中的一种特殊旋转,它在两个正交平面内同时旋转相同的角度。对于左乘 q 1 \mathbf{q}_1 q1,旋转发生在由 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2 张成的平面及其正交补平面内;对于右乘 q 2 \mathbf{q}_2 q2,旋转发生在由 q 1 q 2 \mathbf{q}_1\mathbf{q}_2 q1q2 q 1 q 2 ∗ \mathbf{q}_1\mathbf{q}_2^* q1q2 张成的平面及其正交补平面内。更重要的是,这两个等角旋转的旋转角恰好等于 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2 所表示的三维旋转角的一半!

现在我们可以解释四元数旋转的奥秘了:四元数乘积 q ⊗ v ⊗ q ∗ \mathbf{q} \otimes \mathbf{v} \otimes \mathbf{q}^* qvq 实际上对应于两个连续的四维等角旋转,每个旋转的角度都是 q \mathbf{q} q 所表示的三维旋转角的一半。在其中一个旋转平面内,这两个旋转正好抵消,因此不会改变 v \mathbf{v} v 在该平面内的分量;而在另一个旋转平面内,这两个旋转叠加,恰好给出了 q \mathbf{q} q 所表示的三维旋转!

这个几何解释揭示了四元数表示旋转的独特优势:通过将三维旋转嵌入到四维空间中,并利用等角旋转的特殊性质,四元数能够巧妙地实现三维旋转的复合。这种方法不仅数学上优美,而且在计算上也非常高效。

下面的 Python 代码演示了如何将四元数乘积分解为两个等角旋转:

import numpy as np

def isoclinic_rotations(q1, q2):
    """
    Decompose the quaternion product into two isoclinic rotations.
    
    Parameters:
        q1 (numpy.ndarray): First quaternion.
        q2 (numpy.ndarray): Second quaternion.
        
    Returns:
        tuple: Two 4x4 rotation matrices representing the isoclinic rotations.
    """
    q = quaternion_multiply(q1, q2)
    
    R_left = np.zeros((4, 4))
    R_left[0, 0] = q1[0]
    R_left[0, 1:] = -q1[1:]
    R_left[1:, 0] = q1[1:]
    R_left[1:, 1:] = q1[0] * np.eye(3) + np.array([[0, -q1[3], q1[2]],
                                                  [q1[3], 0, -q1[1]],
                                                  [-q1[2], q1[1], 0]])
    
    R_right = np.zeros((4, 4))  
    R_right[0, 0] = q2[0]
    R_right[0, 1:] = -q2[1:]
    R_right[1:, 0] = q2[1:]
    R_right[1:, 1:] = q2[0] * np.eye(3) + np.array([[0, q2[3], -q2[2]],
                                                    [-q2[3], 0, q2[1]],
                                                    [q2[2], -q2[1], 0]])
    
    return R_left, R_right

# Example usage
q1 = rotation_vector_to_quaternion(np.array([0, 0, np.pi/4]))
q2 = rotation_vector_to_quaternion(np.array([0, 0, np.pi/3]))

R_left, R_right = isoclinic_rotations(q1, q2)
print("Left isoclinic rotation:")
print(R_left)
print("Right isoclinic rotation:")
print(R_right)

# Output:
# Left isoclinic rotation:
# [[ 0.92387953 -0.38268343  0.          0.        ]
#  [ 0.38268343  0.92387953  0.          0.        ]
#  [ 0.          0.          0.92387953 -0.38268343]
#  [ 0.          0.          0.38268343  0.92387953]]
# Right isoclinic rotation:
# [[ 0.8660254  -0.5         0.          0.        ]
#  [ 0.5         0.8660254   0.          0.        ]
#  [ 0.          0.          0.8660254   0.5       ]
#  [ 0.          0.         -0.5         0.8660254 ]]

在这个例子中,我们计算了两个旋转的四元数 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2,然后将它们的乘积分解为左右两个等角旋转矩阵 R l e f t R_{left} Rleft R r i g h t R_{right} Rright。可以看到,这两个矩阵都是四维空间中的旋转矩阵,且旋转角度分别等于 q 1 \mathbf{q}_1 q1 q 2 \mathbf{q}_2 q2 所表示的三维旋转角的一半。

等角旋转的概念在四元数的几何应用中非常重要。例如,在计算机图形学中,我们可以利用等角旋转来实现四维空间中的旋转插值和动画。在物理学中,等角旋转也有重要应用,如狭义相对论中的洛伦兹变换就可以用等角旋转来表示。此外,等角旋转还与李群和李代数的理论有密切联系,是现代几何学和理论物理学的重要工具。关于等角旋转的更多内容,可以参考以下文献:

  1. Conway, J. H., & Smith, D. A. (2003). On quaternions and octonions: their geometry, arithmetic, and symmetry. CRC Press.

  2. Hanson, A. J. (2005). Visualizing quaternions. Elsevier.

  3. Girard, P. R. (1984). The quaternion group and modern physics. European Journal of Physics, 5(1), 25.

总之,通过学习四元数的几何解释和等角旋转的性质,我们可以更深入地理解四元数的奥秘,并将其应用于各种旋转相关的问题中。这不仅需要扎实的数学功底,更需要几何直觉和创新思维。希望本节的讨论能够启发读者进一步探索四元数的奥妙,并在实践中灵活运用。下个章节还在编写中,周日更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值