四元数与旋转矩阵互转实战:用Python代码搞定机器人姿态计算

在机器人开发和3D视觉工程中,姿态表示是一个基础但至关重要的概念。四元数和旋转矩阵作为两种最常用的姿态表示方法,各有优缺点:四元数计算高效且无万向节锁问题,而旋转矩阵则更直观且易于组合。本文将深入探讨这两种表示方法的相互转换原理,并提供可直接用于ROS、Unity等项目的Python实现代码。

1. 四元数与旋转矩阵基础

1.1 四元数的数学本质

四元数由Hamilton于1843年提出,是复数的扩展形式。一个单位四元数可以表示为:

q = w + xi + yj + zk

其中:

  • w为实部
  • (x,y,z)为虚部
  • 满足w² + x² + y² + z² = 1

在三维旋转中,四元数可以解释为绕单位轴n=(nx,ny,nz)旋转角度θ,其分量关系为:

w = cos(θ/2)
x = nx * sin(θ/2)
y = ny * sin(θ/2) 
z = nz * sin(θ/2)

1.2 旋转矩阵的几何意义

旋转矩阵是一个3×3的正交矩阵,其每一列代表旋转后坐标系各轴在原坐标系中的方向余弦。对于绕任意轴n旋转θ角的变换,旋转矩阵的基本形式为:

R(n,θ) = [(1-cosθ)nx²+cosθ, (1-cosθ)nxny-nzsinθ, (1-cosθ)nxnz+nysinθ;
           (1-cosθ)nxny+nzsinθ, (1-cosθ)ny²+cosθ, (1-cosθ)nynz-nxsinθ;
           (1-cosθ)nxnz-nysinθ, (1-cosθ)nynz+nxsinθ, (1-cosθ)nz²+cosθ]

2. 四元数转旋转矩阵

2.1 数学推导

通过三角恒等式变换,我们可以将旋转矩阵完全用四元数分量表示。关键推导步骤包括:

  1. 对角线元素化简:

    m11 = 1 - 2y² - 2z² = w² + x² - y² - z²
    m22 = 1 - 2x² - 2z² = w² - x² + y² - z²  
    m33 = 1 - 2x² - 2y² = w² - x² - y² + z²
    
  2. 非对角线元素化简:

    m12 = 2xy - 2wz
    m21 = 2xy + 2wz
    m13 = 2xz + 2wy
    m31 = 2xz - 2wy
    m23 = 2yz - 2wx
    m32 = 2yz + 2wx
    

最终得到的旋转矩阵形式为:

R = [[1-2y²-2z²,   2xy-2wz,     2xz+2wy],
     [2xy+2wz,     1-2x²-2z²,   2yz-2wx],
     [2xz-2wy,     2yz+2wx,     1-2x²-2y²]]

2.2 Python实现

使用NumPy实现四元数到旋转矩阵的转换:

import numpy as np

def quat_to_rot(q):
    """ 单位四元数转旋转矩阵 """
    w, x, y, z = q
    return np.array([
        [1-2*y*y-2*z*z,   2*x*y-2*w*z,     2*x*z+2*w*y],
        [2*x*y+2*w*z,     1-2*x*x-2*z*z,   2*y*z-2*w*x],
        [2*x*z-2*w*y,     2*y*z+2*w*x,     1-2*x*x-2*y*y]
    ])

注意:输入四元数必须为单位四元数。实际应用中应先进行归一化:

q = q / np.linalg.norm(q)

3. 旋转矩阵转四元数

3.1 转换原理

从旋转矩阵提取四元数的关键在于矩阵对角线元素的和与差:

  1. 计算迹(trace):

    tr(R) = m11 + m22 + m33 = 4w² - 1
    ⇒ w = √(tr(R)+1)/2
    
  2. 通过对称元素组合求其他分量:

    x = (m32 - m23)/(4w)
    y = (m13 - m31)/(4w)
    z = (m21 - m12)/(4w)
    

3.2 数值稳定性处理

当w接近0时,上述方法会出现数值不稳定。更鲁棒的实现应选择四个分量中绝对值最大的作为计算基准:

def rot_to_quat(R):
    """ 旋转矩阵转单位四元数 """
    m00, m01, m02 = R[0]
    m10, m11, m12 = R[1] 
    m20, m21, m22 = R[2]
    
    # 计算各分量平方
    w_sq = 0.25 * (1 + m00 + m11 + m22)
    x_sq = 0.25 * (1 + m00 - m11 - m22)
    y_sq = 0.25 * (1 - m00 + m11 - m22)
    z_sq = 0.25 * (1 - m00 - m11 + m22)
    
    # 选择最大分量避免除零
    max_val = max(w_sq, x_sq, y_sq, z_sq)
    
    if max_val == w_sq:
        w = np.sqrt(w_sq)
        x = (m21 - m12) / (4*w)
        y = (m02 - m20) / (4*w)
        z = (m10 - m01) / (4*w)
    elif max_val == x_sq:
        x = np.sqrt(x_sq)
        w = (m21 - m12) / (4*x)
        y = (m01 + m10) / (4*x)
        z = (m02 + m20) / (4*x)
    elif max_val == y_sq:
        y = np.sqrt(y_sq)
        w = (m02 - m20) / (4*y)
        x = (m01 + m10) / (4*y)
        z = (m12 + m21) / (4*y)
    else:
        z = np.sqrt(z_sq)
        w = (m10 - m01) / (4*z)
        x = (m02 + m20) / (4*z)
        y = (m12 + m21) / (4*z)
    
    return np.array([w, x, y, z])

4. 工程实践中的关键问题

4.1 数值误差处理

在实际应用中,累积的数值误差可能导致旋转矩阵不再严格正交。建议添加正交化步骤:

def orthonormalize(R):
    """ 施密特正交化旋转矩阵 """
    u, s, vh = np.linalg.svd(R)
    return u @ vh

4.2 四元数规范化

从旋转矩阵转换得到的四元数应始终满足单位约束:

q = q / np.linalg.norm(q)  # 强制单位化

4.3 特殊情形处理

当旋转角度接近π时,存在数值稳定性问题。此时可采用以下策略:

if abs(w) < 1e-6:  # 非常小的旋转角度
    angle = 2 * np.arcsin(np.sqrt(x*x + y*y + z*z))
    axis = np.array([x, y, z]) / np.sin(angle/2)
    # 使用轴角表示进行插值等操作

5. 完整代码示例

以下是一个集成了所有功能的Python类,适用于机器人项目:

import numpy as np

class RotationConverter:
    @staticmethod
    def quat_to_rot(q, normalize=True):
        if normalize:
            q = q / np.linalg.norm(q)
        w, x, y, z = q
        return np.array([
            [1-2*y*y-2*z*z, 2*x*y-2*w*z, 2*x*z+2*w*y],
            [2*x*y+2*w*z, 1-2*x*x-2*z*z, 2*y*z-2*w*x],
            [2*x*z-2*w*y, 2*y*z+2*w*x, 1-2*x*x-2*y*y]
        ])
    
    @staticmethod
    def rot_to_quat(R):
        R = np.asarray(R)
        if R.shape != (3,3):
            raise ValueError("Input must be 3x3 matrix")
            
        m00, m01, m02 = R[0]
        m10, m11, m12 = R[1]
        m20, m21, m22 = R[2]
        
        tr = m00 + m11 + m22
        
        if tr > 0:
            S = np.sqrt(tr + 1.0) * 2
            w = 0.25 * S
            x = (m21 - m12) / S
            y = (m02 - m20) / S 
            z = (m10 - m01) / S
        elif (m00 > m11) and (m00 > m22):
            S = np.sqrt(1.0 + m00 - m11 - m22) * 2
            w = (m21 - m12) / S
            x = 0.25 * S
            y = (m01 + m10) / S
            z = (m02 + m20) / S
        elif m11 > m22:
            S = np.sqrt(1.0 + m11 - m00 - m22) * 2
            w = (m02 - m20) / S
            x = (m01 + m10) / S
            y = 0.25 * S
            z = (m12 + m21) / S
        else:
            S = np.sqrt(1.0 + m22 - m00 - m11) * 2
            w = (m10 - m01) / S
            x = (m02 + m20) / S
            y = (m12 + m21) / S
            z = 0.25 * S
            
        q = np.array([w, x, y, z])
        return q / np.linalg.norm(q)
    
    @staticmethod
    def test_conversion(q, tol=1e-6):
        R = RotationConverter.quat_to_rot(q)
        q_recon = RotationConverter.rot_to_quat(R)
        return np.allclose(q, q_recon, atol=tol) or np.allclose(q, -q_recon, atol=tol)

6. 在ROS和Unity中的应用

6.1 ROS中的实现

ROS中常用tf库进行坐标变换。以下示例展示如何在ROS中使用我们的转换方法:

import tf
from geometry_msgs.msg import Quaternion

def ros_quat_to_numpy(ros_quat):
    return np.array([ros_quat.w, ros_quat.x, ros_quat.y, ros_quat.z])

def numpy_to_ros_quat(np_quat):
    q = Quaternion()
    q.w, q.x, q.y, q.z = np_quat
    return q

# 使用示例
ros_quat = ... # 从ROS话题获取
np_quat = ros_quat_to_numpy(ros_quat)
R = RotationConverter.quat_to_rot(np_quat)
# 进行矩阵运算后转回四元数
new_quat = RotationConverter.rot_to_quat(R)
ros_new_quat = numpy_to_ros_quat(new_quat)

6.2 Unity中的实现

Unity使用左手坐标系且四元数分量顺序略有不同:

def unity_to_standard(q_unity):
    """ 转换Unity四元数到标准数学表示 """
    return np.array([q_unity.w, q_unity.x, q_unity.y, q_unity.z])

def standard_to_unity(q):
    """ 转换标准四元数到Unity表示 """
    return {"w": q[0], "x": q[1], "y": q[2], "z": q[3]}

# 在Unity脚本中的应用示例
unity_quat = ... # 从Unity获取
np_quat = unity_to_standard(unity_quat)
R = RotationConverter.quat_to_rot(np_quat)
# 进行矩阵操作后转回四元数
new_quat = RotationConverter.rot_to_quat(R)
unity_new_quat = standard_to_unity(new_quat)

7. 性能优化与进阶技巧

7.1 利用SIMD指令加速

对于需要处理大量四元数的应用,可以使用NumPy的向量化运算:

def batch_quat_to_rot(quats):
    """ 批量转换四元数到旋转矩阵 """
    quats = np.asarray(quats)
    if quats.ndim == 1:
        quats = quats[np.newaxis, :]
        
    w, x, y, z = quats.T
    xx = x * x
    yy = y * y 
    zz = z * z
    xy = x * y
    xz = x * z
    yz = y * z
    wx = w * x
    wy = w * y
    wz = w * z
    
    R = np.empty((len(quats), 3, 3))
    R[:, 0, 0] = 1 - 2 * (yy + zz)
    R[:, 0, 1] = 2 * (xy - wz)
    R[:, 0, 2] = 2 * (xz + wy)
    R[:, 1, 0] = 2 * (xy + wz)
    R[:, 1, 1] = 1 - 2 * (xx + zz)
    R[:, 1, 2] = 2 * (yz - wx)
    R[:, 2, 0] = 2 * (xz - wy)
    R[:, 2, 1] = 2 * (yz + wx)
    R[:, 2, 2] = 1 - 2 * (xx + yy)
    
    return R.squeeze()

7.2 四元数插值(Slerp)

在动画和轨迹规划中,四元数球面线性插值(Slerp)比矩阵插值更高效:

def quat_slerp(q1, q2, t):
    """ 四元数球面线性插值 """
    q1 = q1 / np.linalg.norm(q1)
    q2 = q2 / np.linalg.norm(q2)
    
    dot = np.dot(q1, q2)
    
    # 确保取最短路径
    if dot < 0:
        q2 = -q2
        dot = -dot
        
    DOT_THRESHOLD = 0.9995
    if dot > DOT_THRESHOLD:
        result = q1 + t * (q2 - q1)
        return result / np.linalg.norm(result)
        
    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 * q1) + (s1 * q2)

7.3 四元数微分方程

对于姿态估计和控制系统,常需要处理四元数的微分:

def quat_derivative(q, omega):
    """ 计算四元数导数 given角速度omega """
    w, x, y, z = q
    mat = np.array([
        [-x, -y, -z],
        [ w, -z,  y],
        [ z,  w, -x],
        [-y,  x,  w]
    ])
    return 0.5 * mat @ omega

8. 测试与验证

为确保转换的正确性,建议实现以下测试用例:

def test_rotation_conversions():
    # 测试随机旋转
    np.random.seed(42)
    for _ in range(100):
        axis = np.random.randn(3)
        axis /= np.linalg.norm(axis)
        angle = np.random.uniform(0, 2*np.pi)
        
        # 创建测试四元数
        w = np.cos(angle/2)
        x, y, z = axis * np.sin(angle/2)
        q = np.array([w, x, y, z])
        
        # 转换测试
        R = RotationConverter.quat_to_rot(q)
        q_recon = RotationConverter.rot_to_quat(R)
        
        # 四元数q和-q表示相同旋转
        assert np.allclose(q, q_recon) or np.allclose(q, -q_recon)
        
        # 测试旋转一致性
        v = np.random.randn(3)
        v_rot1 = R @ v
        q_v = np.hstack([0, v])
        q_rot = quat_mult(quat_mult(q, q_v), quat_conj(q))
        v_rot2 = q_rot[1:]
        assert np.allclose(v_rot1, v_rot2)
    
    print("所有测试通过!")

def quat_mult(q1, q2):
    """ 四元数乘法 """
    w1, x1, y1, z1 = q1
    w2, x2, y2, z2 = q2
    return np.array([
        w1*w2 - x1*x2 - y1*y2 - z1*z2,
        w1*x2 + x1*w2 + y1*z2 - z1*y2,
        w1*y2 - x1*z2 + y1*w2 + z1*x2,
        w1*z2 + x1*y2 - y1*x2 + z1*w2
    ])

def quat_conj(q):
    """ 四元数共轭 """
    return np.array([q[0], -q[1], -q[2], -q[3]])
Logo

更多推荐