坐标系基础:地球坐标系、导航坐标系、载体坐标系

做惯导这么多年,我见过太多新手一上来就盯着四元数、姿态矩阵猛啃,结果坐标系都没搞明白。说实话,坐标系这东西就像房子的地基——你地基歪了,上面算法再漂亮也是白搭。

今天咱们就把这三个坐标系彻底聊透。我保证,搞懂了它们之间的转换关系,后面你写代码会顺很多。

1. 地球坐标系(e系)

地球坐标系,说白了就是固定在地球上的一个坐标系。它跟着地球一起转,所以也叫地固坐标系。

我个人习惯用 ECEF(Earth-Centered, Earth-Fixed) 来称呼它。它的原点在地球质心,Z轴指向北极,X轴指向本初子午线与赤道的交点,Y轴按右手定则确定。

关键点:地球坐标系是旋转的。它跟着地球自转,角速度大约是 7.292115 × 10⁻⁵ rad/s。

你可能会问:「为什么不用惯性坐标系?」嗯,这个问题问得好。惯性坐标系(i系)是不动的,而地球坐标系是动的。我们做导航时,很多时候需要知道相对于地球的位置,比如经纬高,这时候就得用地球坐标系。

2. 导航坐标系(n系)

导航坐标系,也叫当地水平坐标系。它的原点在载体所在位置,通常取东北天(ENU)或者北东地(NED)两种定义。

我在项目中常用的是 东北天(ENU)

  • X轴(E):指向东
  • Y轴(N):指向北
  • Z轴(U):指向天(垂直于当地水平面向上)

为什么叫「导航坐标系」?因为我们的惯导解算,最终输出的姿态、速度、位置,都是相对于这个坐标系的。说白了,你坐在飞机里,想知道自己朝哪个方向飞、飞得多快,这些信息都是在导航坐标系下表达的。

我的经验:做组合导航时,导航坐标系的选择会影响很多细节。比如用ENU时,Z轴向上,重力加速度是负的;用NED时,Z轴向下,重力加速度是正的。我曾经因为搞混这个,调试了整整两天才发现问题。

3. 载体坐标系(b系)

载体坐标系是固定在运动物体上的。它的原点在载体的质心,通常定义成:

  • X轴:指向载体前进方向(右)
  • Y轴:指向载体左侧(前)
  • Z轴:指向载体上方(上)

这个坐标系,说白了就是「你坐在载体里感受到的坐标系」。IMU(惯性测量单元)输出的加速度和角速度,都是在载体坐标系下测量的。

举个例子:你拿着手机往前跑,手机里的加速度计测到的数据,就是载体坐标系下的。它可不知道地球在哪,它只知道「我往前加速了」。

4. 坐标系之间的转换关系

好了,三个坐标系都认识了。现在的问题是:它们之间怎么转?

我画了一张图,帮你理清它们的关系:

地球坐标系 (e系) 原点:地心 固定在地球上 导航坐标系 (n系) 原点:载体位置 东北天/北东地 载体坐标系 (b系) 原点:载体质心 IMU测量所在系 Cₑⁿ 经纬高→ENU Cₙᵇ 姿态矩阵 核心转换关系 • 地球→导航:Cₑⁿ = f(纬度, 经度) • 导航→载体:Cₙᵇ = 姿态矩阵(欧拉角/四元数) • 载体→导航:Cᵇₙ = (Cₙᵇ)ᵀ(正交矩阵转置即逆)

5. 转换矩阵与代码实现

坐标系之间的转换,核心就是旋转矩阵。我直接上代码,你感受一下:

import numpy as np

def ecef_to_enu(lat, lon):
    """
    地球坐标系到导航坐标系的转换矩阵
    lat, lon: 纬度、经度(弧度)
    返回 C_e_n (3x3)
    """
    slat = np.sin(lat)
    clat = np.cos(lat)
    slon = np.sin(lon)
    clon = np.cos(lon)
    
    C_e_n = np.array([
        [-slon,          clon,          0],
        [-slat*clon,    -slat*slon,     clat],
        [ clat*clon,     clat*slon,     slat]
    ])
    return C_e_n

def attitude_to_dcm(roll, pitch, yaw):
    """
    欧拉角转方向余弦矩阵(导航→载体)
    roll, pitch, yaw: 横滚、俯仰、航向(弧度)
    返回 C_n_b (3x3)
    """
    cr = np.cos(roll)
    sr = np.sin(roll)
    cp = np.cos(pitch)
    sp = np.sin(pitch)
    cy = np.cos(yaw)
    sy = np.sin(yaw)
    
    C_n_b = np.array([
        [cp*cy,              cp*sy,              -sp],
        [sr*sp*cy - cr*sy,  sr*sp*sy + cr*cy,   sr*cp],
        [cr*sp*cy + sr*sy,  cr*sp*sy - sr*cy,   cr*cp]
    ])
    return C_n_b

注意:旋转矩阵是正交矩阵,它的逆等于转置。所以 C_b_n = C_n_b.T。这个性质在代码里非常有用,千万别搞反了。

6. 避坑指南

做惯导这些年,我在坐标系上踩过的坑,说出来都是泪:

  • 旋转顺序:欧拉角的旋转顺序很重要。我习惯用 Z-Y-X(航向→俯仰→横滚),但不同厂家可能不一样。拿到IMU数据,先确认它的旋转顺序。
  • 角度正负:航向角通常北偏东为正,但有些系统用北偏西为正。我曾经因为这个问题,让无人机在空中画了个8字。
  • 单位统一:弧度还是角度?我建议代码里全部用弧度,只在显示时转成角度。否则你会在调试时疯掉。

我的习惯:每次写新项目,第一件事就是写一个坐标系转换的单元测试。输入一组已知数据,手动算一遍结果,再跟代码输出对比。这一步花不了10分钟,但能省下后面几天的调试时间。

7. 小结

好了,坐标系基础就聊到这儿。你想想看,其实就三个坐标系:地球坐标系(固定在地球上)、导航坐标系(当地水平)、载体坐标系(跟着载体转)。它们之间的转换,就是旋转矩阵的事。

搞懂了这些,后面讲姿态解算、速度更新、位置更新,你就能跟上节奏了。嗯,今天就先这样。


公众号:蓝海资料掘金营,微信deep3321