简介:本资源是面向导航定位领域科研人员与高校师生的GNSS信号接收模拟工具包,聚焦GPS信号建模、多径效应分析及INS/GNSS组合导航算法验证。NaveGo-master由circusjwz与coriolis_master团队开发,提供从卫星信号生成、IMU误差建模到卡尔曼滤波融合的完整仿真链路,可支撑接收机算法调试、教学实验设计及系统性能评估。压缩包共65个文件(50.4MB),含55个MATLAB源码(.m)实现核心导航解算(如kalman.m、ins_gnss.m、coriolis.m)、5个.mat数据文件用于仿真验证、2个说明文档(README.md、LICENSE)、1个.kml地理可视化文件及1个.txt许可文件;目录结构清晰划分为examples、conversions、allan-variance等模块,覆盖合成数据生成、Allan方差分析、ECEF/NED坐标转换等关键环节。目前已有129人学习下载,适合具备MATLAB基础的中高级用户开展GNSS/INS仿真研究与课程实践。 顺着 NaveGo-master.zip 这个文件名点进来的朋友,多半和我一样,是想用 MATLAB 做 GNSS/INS 组合导航验证的。这个压缩包在许多论坛和网盘里流传,文件名经常被系统自动续成 NaveGo-master.zip_GNSS MASTER_NaveGo-master_circusjwz_coriolis m,我第一次看到时也是满头问号:coriolis 不是物理里那个科里奥利效应吗,怎么会出现在一个 GNSS 工具箱的名字里?等我把包里的代码真正跑通,才发现这个看似不起眼的关键词,恰恰是惯性导航机械编排里最容易忽略、也最影响模型完整性的一个角落。这篇东西不是把 README 复述一遍,而是我从下载、解压、喂数据到看结果这一路上踩过的坑和梳理清楚的东西,给打算用 NaveGo 做组合导航的朋友一个可以直接参照的路径。
1. 我为什么会盯上NaveGo:一个能跑起来的GNSS/INS集成工具箱
先说背景。去年我手头要做的是一个低成本车载导航验证项目:一块 MEMS 级别 IMU,一个普通单频 GNSS 模块,目标是评估松组合方案到底能把定位精度做到什么水平。一开始我图省事,打算在有名的商业导航软件里跑一圈仿真,但一是授权问题麻烦,二是黑盒方案里我根本看不到姿态、速度、位置每一步是怎么算的,出了问题只能干瞪眼。后来在 GitHub 上翻到了 NaveGo,名字是 Navigation Goes 的缩写,一个用 MATLAB 写的开源 GNSS/INS 集成工具箱,瞬间觉得这就是我要的东西。
NaveGo 的价值,不在于它有多炫酷的界面,而在于它把整套组合导航链路完整地摊开了。你给它喂进去两类数据:一类是 IMU 输出的加速度和角速度,另一类是 GNSS 输出的经纬高和速度,它内部先用惯性机械编排把姿态、速度、位置推算出来,再用扩展卡尔曼滤波(EKF)拿 GNSS 观测去修正惯导误差。整个过程是松组合的典型结构,代码量不大,但每一步都有对应的函数,非常适合想弄懂"组合导航到底是怎么把两个传感器揉在一起"的人。
适合看这篇文章的,大致有三类人:一是正在学惯性导航理论、想把课本上的机械编排方程变成可运行代码的学生;二是做车载、机器人、无人机导航,需要一个开源基线做对比验证的工程师;三是像我这样,手里有真实 IMU 和 GNSS 模块,想快速搭一个原型评估精度的开发者。如果你只是需要"一键出图"的成品工具,那 NaveGo 可能反而会让你失望,因为它对数据格式、坐标系、时间同步都有要求——但这些要求恰恰是组合导航里最该学的东西。
我之前也试过自己从零写机械编排,写到速度更新方程时总是怀疑符号对不对,尤其是科里奥利项那部分,动不动就把 (2\omega_{ie}^n+\omega_{en}^n) 的符号弄反。NaveGo 的好处就是提供了一个经过很多人验证过的参考实现,等效于有个经验丰富的同事把他的代码摊在桌面上说:"你看,这里应该这么处理。"
2. 拆开NaveGo-master.zip:目录里的每个文件是干什么的
把压缩包解压之后,目录结构大致是这样的(不同版本可能有小差异,但核心模块基本一致):
NaveGo-master/ ├── README.md ├── LICENSE ├── data/ │ ├── dataset1.mat │ └── dataset2.mat ├── functions/ │ ├── ins.m │ ├── kf_init.m │ ├── kf_prediction.m │ ├── kf_update.m │ ├── imu_read.m │ ├── gnss_read.m │ ├── coriolis.m │ ├── align_hb.m │ └── plot_results.m ├── scripts/ │ ├── run_example.m │ └── ... └── doc/ └── NaveGo_documentation.pdf第一次拿到这个包的人,最容易犯的错是直接双击run_example.m然后报错找不到函数。原因很简单:MATLAB 不会自动搜索子目录里的函数,你必须先把整个目录加入路径,我通常执行这行:
addpath(genpath('NaveGo-master'));这里多提一句,如果你把压缩包放在中文路径下,或者文件名里带空格,某些 MATLAB 版本偶尔会出一些莫名其妙的加载问题。我的习惯是统一放到D:\work\NaveGo-master这种纯英文、无空格的路径里,省得后面排查半天。
接下来分清主次。functions目录是绝对核心,其中ins.m是惯性导航机械编排,它接收上一时刻的姿态、速度、位置和当前 IMU 采样,输出新的姿态、速度、位置,整个惯导推算的主循环就靠它。kf_prediction.m和kf_update.m是 EKF 的时间更新和量测更新,前者把惯导误差协方差往前推,后者在 GNSS 观测到来时对误差状态做修正。kf_init.m负责给滤波器赋初值,包括初始姿态、初始协方差矩阵和噪声矩阵。
我特别想提醒的是coriolis.m,也就是标题里那个关键词。很多初看代码的人会以为它是个中间辅助函数,不重要,结果真正动手改代码、把科里奥利项删掉测试时才发现,它对速度更新的影响是结构性的。这个我们下一节专门展开讲。
另外,data目录里通常带几个.mat数据集,是官方用来演示的仿真数据。不要一上来就用自己的 IMU 报文去测试,那样会把"数据格式错误"和"算法问题"混在一起,特别难排查。先把内置数据跑通一遍,再把数据替换成自己的,这个顺序能省掉大量无意义的调试时间。
3. 真正决定精度的是一个函数:coriolis、地球自转与INS机械编排
为什么一个工具箱里会专门出现 coriolis 这个词?因为在惯性导航里,我们是在地球这个不断自转的参考系里解算载体运动的。想象一下你站在旋转木马上,沿着木马半径往外走,即使你走的速度很慢,也会感觉到一股横向的力把你推向一边,这就是科里奥利效应。地球就是一个转速很慢的旋转木马,约 7.292115e-5 rad/s,但任何相对地球运动的物体,只要导航时间足够长、运动速度足够快,这个效应都会积累成可观测的误差。
在 NED 坐标系下,惯导速度微分方程的标准形式是:
[ \dot{v}^n = C_b^n f^b - (2\omega_{ie}^n + \omega_{en}^n) \times v^n + g^n ]
其中 (C_b^n f^b) 是比力投影,(g^n) 是重力矢量,中间那一项就是科里奥利加速度和运输率的合成。(2\omega_{ie}^n) 是地球自转角速度在导航坐标系下的投影,(\omega_{en}^n) 是载体在地球表面运动导致的导航系相对地球系的转动,两者共同作用在速度向量上,产生一个虚拟加速度。
NaveGo 里不管用什么名字封装,本质上都在处理这个式子。用伪代码表示,它在某个函数里做的事情大致是:
% 地球自转角速度在NED下的投影,L为纬度 w_ie_n = [wie*cos(L); 0; -wie*sin(L)]; % 运输率,v为NED速度,R_M/R_N为子午圈和卯酉圈曲率半径 w_en_n = [v(2)/(R_N+h); -v(1)/(R_M+h); -v(2)*tan(L)/(R_N+h)]; % 科里奥利加速度 coriolis_acc = cross(2*w_ie_n + w_en_n, v);很多人看到这个伪代码会有一个疑问:MEMS IMU 噪声那么大,这个科里奥利项到底有多大?我算过一个具体数字:假设载体以 20 m/s 的速度在纬度 45 度的地方向北行驶,那么 (2\omega_{ie}^n \times v^n) 带来的哥式加速度大约是 (2 \times 7.292 \times 10^{-5} \times 20 \times \sin(45^\circ)),差不多 (0.002 m/s^2)。这个量级确实比 MEMS 加速度计噪声小,但如果机械编排里完全忽略它,速度会持续积累一个偏置,一分钟就能带来约 0.12 m/s 的速度误差,再积分到位置上就是可观的漂移。
在 GNSS/INS 松组合系统里,GNSS 虽然能通过滤波不断修正速度误差,但如果你在模型里漏掉了科里奥利项,EKF 的预测模型和真实物理过程就不匹配,滤波器会拿错误的增益去折中所有误差,最终导致位置解出现难以解释的残差。我在调试时试过把科里奥利项置零,结果单靠 GNSS 更新也能勉强收敛,但组合解的位置误差明显变大,尤其在车辆转弯、速度方向快速变化的时候,误差波动非常剧烈。所以这个项不是"高精度光纤陀螺系统才需要考虑"的东西,模型完备性对任何精度的 IMU 都有意义。
还有一个很容易犯的误导是坐标系混用。不同教材和工具箱,有的用 NED,有的用 ENU,符号和顺序完全不同。NaveGo 默认的导航坐标系是 NED,角度单位是弧度,加速度单位是 m/s²,角速度单位是 rad/s。如果你从自己的 IMU 驱动里读出来的是度每秒、或者把数据喂成了 ENU 顺序,出来的轨迹要么发散、要么每个转弯方向都是反的。我建议拿到代码后,第一件事是确认坐标系定义,把单位换算逻辑写清楚,不要指望后面滤波自己"纠偏"。
4. 让NaveGo在你的MATLAB上跑起来:数据准备和实操记录
下面说点能直接照做的内容。以我下载的版本为例,整个运行流程分四步。
第一步是准备数据。NaveGo 的惯例是把 IMU 和 GNSS 各存一个数据文件,IMU 数据通常包含时间、姿态角(有的版本是四元数)、三轴加速度、三轴角速度;GNSS 数据包含时间、纬度、经度、高度、北向速度、东向速度、地向速度。如果你用的是内置数据集,直接load('dataset1.mat')就能拿到结构体;如果你要用自己的数据,就必须按照它的格式整理成同样的结构,这是整个项目里最枯燥但最关键的环节。
第二步是读取和初始化。常见的主程序逻辑大致是:
addpath(genpath('NaveGo-master')); % 读取IMU和GNSS数据,具体函数名和参数以你下载的版本为准 imu = imu_read('my_imu_data.txt'); gnss = gnss_read('my_gnss_data.txt'); % 初始化滤波器,设置初始姿态、协方差、噪声矩阵 nav = kf_init(imu, gnss);第三步是主循环。松组合的基本逻辑是:每个 IMU 采样时刻,先用ins.m做机械编排,把姿态、速度、位置往前推一步;如果有新的 GNSS 观测,就调用kf_update.m做量测更新,把误差状态修正回惯导解算结果。简化的循环长这样:
for i = 1:length(imu) % IMU机械编排:姿态、速度、位置更新 nav = ins(nav, imu(i)); % 判断当前时刻有没有GNSS观测 k = find(gnss.time >= imu(i).time, 1, 'first'); if ~isempty(k) % 如果这一拍有GNSS数据,就做EKF量测更新 nav = kf_update(nav, gnss(k)); end end这里我要单独提醒一句:如果你在实际运行中看到各种维度对不上的报错,大概率不是算法问题,而是数据结构问题。NaveGo 的函数对字段名很敏感,比如 IMU 结构体里加速度字段是acc还是accel、角速度字段是gyro还是omega,不同版本可能不一样。最稳妥的办法是先disp(imu)和disp(gnss)看一眼实际字段名,再对照函数内部代码里的引用方式,改数据时直接对齐字段,而不是去改函数的读取逻辑。
第四步是画图和保存结果。plot_results.m或者你自己写绘图代码,把组合导航解算出来的轨迹叠加在 GNSS 原始轨迹上,输出位置、速度误差曲线。这一步不是可有可无的,因为只看最终数据你是看不出滤波器发没发散的,必须靠曲线判断。
我在实操中发现一个非常实用的技巧:第一次跑通时,先用官方内置数据集,然后观察纯惯导输出和组合输出之间的差异。纯惯导在几十秒内就会明显漂移,组合输出应该紧贴 GNSS 轨迹,这个对比能帮你快速判断主循环到底通没通。如果组合输出和 GNSS 轨迹差得离谱,十有八九是单位换算或者坐标系方向出了问题,而不是滤波参数不行。
5. 跑完仿真之后,如何确认结果不是在自嗨
很多新手把仿真跑完,看到曲线出来了就认为项目完成了,但实际上确认结果"对"比让程序"跑起来"更考验基本功。我在用 NaveGo 做评估时,一般按下面这个顺序检查输出。
先看轨迹整体趋势。把组合导航位置叠加到 GNSS 原始轨迹上,看是否重合。松组合系统里,GNSS 的位置观测对惯导漂移有强修正作用,所以组合轨迹不应该偏离 GNSS 轨迹太远。如果整体形状对、但局部有锯齿,通常是时间同步或者杆臂补偿做得不够好。如果整体形状都不对,比如向北走变成了向东走,那就是坐标系或者姿态初始值有问题。
再看速度误差和位置误差的时间序列。NaveGo 内置数据通常有真值轨迹,可以直接算误差。我习惯把误差画在一个图上,同时叠加滤波协方差 P 矩阵对角线开方得到的标准差带。一个正常的滤波输出,误差应该大致落在 3 倍标准差带内;如果误差曲线不断穿出带子,或者标准差带不收敛,说明滤波器的噪声模型和实际数据不匹配。这个时候不要急着调 Q 和 R,先回头检查单位、坐标系和数据结构,这些问题不解决,调参就是白费力气。
还要特别关注 GNSS 观测频率和 IMU 采样频率的比例。常见 IMU 是 100 Hz 到 200 Hz,GNSS 是 1 Hz 到 10 Hz,这个差距很正常。但要注意时间戳精度,IMU 和 GNSS 时间戳如果来自不同时钟,系统会有固定的时间延迟。我遇到过 GNSS 数据比 IMU 晚 100 毫秒的情况,直接导致每到一个 GNSS 观测时,滤波更新用的位置和惯导推算值之间存在系统性偏差,最终位置误差表现为一圈一圈的振荡。解决方法是先做时间同步对齐,或者干脆在读取数据时做插值,让每个 GNSS 观测时刻都有严格对齐的 IMU 状态。
最后,也是我特别想强调的:不要只盯着组合解,要看纯惯导的漂移特性。把 GNSS 更新去掉,用同一段 IMU 数据跑一遍机械编排,观察纯惯导在 10 秒、30 秒、60 秒的位置漂移。这一步能暴露 IMU 零偏、标度因数误差和初始对准误差,而这些误差在组合解里往往被 GNSS 掩盖了。只有先把纯惯导的漂移特征摸清楚,才能理解为什么滤波器会出现某些修正行为,也才能判断最后的组合结果是不是合理。
6. 从NaveGo到自己的组合导航原型:可以接着做的事
一旦你把 NaveGo 的内置数据跑通,下一步大概率是想让它处理自己传感器的数据。这里有几条实际的路可以走,按难度和收益排列。
第一件事,解析真实 GNSS 模块的 NMEA 数据。市面上的 GNSS 模组输出的基本都是 NMEA 语句,最常见的是 GGA 和 RMC。GGA 里有经纬度、高程和定位状态,RMC 里有地面速度、航向和日期时间。要把这些数据变成 NaveGo 能用的格式,需要把 NMEA 里的度分格式换算成十进制度,把节换算成米每秒。这个转换逻辑不复杂,但精度很容易被忽略,比如纬度的 DDMM.MMMM 格式,如果按十进制度直接解析,误差会大到直接把滤波器打爆。建议写一个小的解析脚本,先把原始 NMEA 转成标准 CSV,再对照 NaveGo 的读取函数做字段映射。
第二件事,处理真实 IMU 的原始报文。不同 IMU 模组的输出格式差异很大,有的直接给物理单位,有的是原始 ADC 码值,需要根据量程和灵敏度换算。这里最容易出错的是坐标轴方向,IMU 的 x/y/z 和 NaveGo 的 NED 坐标系往往不是一回事,可能需要对调或者反号。我自己的经验是先在静止状态下采集一段数据,看加速度计三个轴的输出是不是稳定对应重力方向,陀螺仪零偏是不是接近零,然后再动起来做转动测试,确认每个轴的方向和正负号。
第三件事,杆臂补偿。GNSS 天线的相位中心和 IMU 的测量中心不可能完全重合,两者之间有一个固定的空间向量。在车载场景下,这个向量可能只有几十厘米,但如果你对位置精度要求高,必须把它考虑进去。否则车辆转弯时,GNSS 位置和 IMU 位置之间的几何差异会被当成误差引入滤波器。NaveGo 的模型里不一定默认包含杆臂补偿,需要你自己在量测更新前做一步坐标变换。
第四件事,调 EKF 的噪声参数。Q 矩阵描述 IMU 的加速度计噪声和陀螺仪噪声,R 矩阵描述 GNSS 位置和速度测量噪声。NaveGo 的kf_init.m会把这两个矩阵设成一组默认值,这组默认值只对内置数据集有效。换了自己的传感器之后,Q 和 R 必须根据实际数据重新标定。一个比较笨但有效的做法是:拿一段静止数据,统计加速度计和陀螺仪的 Allan 方差或者标准差,作为 Q 矩阵的原始依据;拿一段移动数据,对比 GNSS 输出和已知轨迹之间的误差,估算 R 矩阵。调参的过程很枯燥,但它直接决定了滤波器的收敛速度和最终精度。
我个人的体会是,NaveGo 这个工具箱最值钱的地方,不是它自带的那几个仿真数据集,而是它把整套组合导航的骨架搭好之后,逼着你去理解每一个数据字段、每一个坐标系、每一个噪声参数背后的物理含义。我最初只是为了快速出一个结果,结果在把真实 IMU 和 GNSS 数据接入 NaveGo 的过程中,反而把教科书里那些公式和实际工程问题串起来了。建议你也这样走一遍:先跑通内置数据,再替换真实数据,最后再去动滤波参数,这个顺序下来,组合导航里那些"死知识"基本就变成你自己的了。
本文还有配套的精品资源,点击获取