☰
Ubuntu 20.04 上 GAC-Mapping 从零部署与调优实战
2026/10/26 19:03:45 网站建设 项目流程

1. 为什么要在 Ubuntu 20.04 上折腾 GAC-Mapping

GAC-Mapping 这套东西,第一次接触的人多半会被它的名字唬住。全称里带 Mapping,直觉上会以为是个纯建图工具,实际跑起来才发现它更像一套“几何-外观联合优化”的完整流水线:前端要吃图像和深度,中间要做特征匹配与位姿估计,后端还要维护一个可查询的地图结构。我第一次在 Ubuntu 20.04 上部署它的时候,光是环境就来回折腾了两天,中间踩的坑比代码本身还多。所以这篇东西不打算写成官方文档的翻译版,而是把我从裸机到跑通全流程的实操记录摊开讲,包括每一步为什么这么做、参数怎么定、报错怎么查。

先说清楚这套流程适合谁看。如果你手上有一台装了 Ubuntu 20.04 的机器,想复现 GAC-Mapping 的完整运行链路,并且希望少走弯路,那这篇内容基本可以照着抄。如果你只是想了解它的算法原理,那这篇偏工程,原理部分只会点到为止。我默认读者有基本的 Linux 命令行操作能力,会用 apt、会看 CMake 报错、知道什么是动态链接库,剩下的细节我会尽量补全。

Ubuntu 20.04 这个版本选择不是随便定的。GAC-Mapping 依赖的一批库——尤其是 PCL、OpenCV、Eigen 这几个——在 20.04 的 apt 源里版本比较合适,18.04 偏老、22.04 又太新,中间这个 LTS 版本是兼容性最稳的甜点区。我实测过在 22.04 上编译,Eigen 的 API 变动会导致好几处模板报错,改起来很烦。所以如果你还在选系统,直接上 20.04,省事。

整个跑通过程我拆成四块来讲:整体设计与依赖选型、核心模块的细节解析、从零到跑通的完整实操、以及我实际遇到的一堆问题和排查手法。每一块都会给出具体的命令、配置和判断依据,不玩虚的。

2. 整体设计与依赖选型拆解

2.1 这套流水线的模块划分与数据流

GAC-Mapping 的运行链路,我习惯把它拆成三层来看:数据输入层、几何外观联合优化层、地图维护层。数据输入层负责把图像序列、深度图、相机内参读进来并做预处理;联合优化层是核心,做特征提取、匹配、位姿求解,同时把几何约束和外观约束揉在一起优化;地图维护层则负责把优化后的结果落成可查询的结构,支持后续的定位和回环。

为什么强调这个分层?因为部署时绝大多数报错都能归到某一层里。比如“段错误”多半出在输入层的格式不对或者空指针,“优化不收敛”通常是联合优化层的参数没调好,“地图查询结果为空”则是维护层的索引没建起来。你脑子里有这张分层图,排查的时候就能快速定位到是哪一层的问题,而不是对着终端干瞪眼。

数据流的方向是单向的:图像和深度先进预处理,转成统一的内部格式,然后喂给特征提取,提取结果进匹配模块,匹配对再送进优化器,优化输出位姿和地图增量,最后写回地图结构。这条链上任何一个环节的输出格式变了,下游就会崩。所以我在实操里会习惯性地在每个环节之间加一个“格式自检”,比如打印一下特征点的数量和维度,确认没跑偏再往下走。

2.2 依赖库的版本选择逻辑

依赖这块是重灾区。GAC-Mapping 官方给的依赖列表不算长,但每个都有版本脾气。我把关键依赖和我在 20.04 上实际用的版本列出来,方便你对照:

依赖库推荐版本选择理由踩坑提示
Eigen3.3.720.04 apt 默认版本,API 稳定别升 3.4,模板报错多
OpenCV4.2.0与 20.04 源匹配,contrib 齐全自己编译要带 contrib
PCL1.1020.04 默认,点云接口够用注意和 VTK 的链接顺序
Ceres2.0.0优化器核心,2.0 对 Eigen 3.3 友好编译时关掉 SuiteSparse 可选依赖
CMake3.16+20.04 自带 3.16.3 够用低于 3.10 会报策略错误

这里重点说 Ceres。GAC-Mapping 的联合优化层重度依赖 Ceres 做非线性最小二乘,Ceres 2.0 和 2.1 在 API 上有细微差别,尤其是LocalParameterization到Manifold的迁移。如果你图省事装了 2.1,代码里老式的参数化写法会编译不过。我的建议是老老实实用 2.0.0,配合 Eigen 3.3.7,这套组合我跑下来最稳。

OpenCV 这块也有讲究。20.04 的 apt 源里libopencv-dev是 4.2.0,但默认不带 contrib 模块。GAC-Mapping 里如果用到 SIFT 或者某些特征描述子,contrib 是必须的。所以要么自己源码编译带 contrib,要么确认代码里没用到 contrib 的接口。我一开始用 apt 版跑,结果在特征提取那步报“未定义符号”,查了半天才发现是缺 contrib。后来干脆源码编译了一遍,虽然费时间,但一劳永逸。

2.3 为什么不用 Docker 一把梭

有人会问,这么麻烦为什么不直接拉个 Docker 镜像。我试过,结论是:调试阶段别用 Docker。原因很实际——GAC-Mapping 跑不通的时候,你需要频繁改代码、重编译、看中间变量,Docker 里的文件挂载和调试工具链会让这个过程变得笨重。等你把流程完全跑通、参数都调好了,再打成镜像做部署,那才是 Docker 的正确用法。前期在宿主机上裸装,改一行编译一行,反馈最快。

另外,Docker 里的 GUI 转发(如果你要看可视化结果)在 20.04 上配置起来也有额外成本,X11 转发偶尔会抽风。所以我的实操路径是:宿主机裸装跑通,确认无误后再考虑容器化。这个顺序别搞反。

2.4 编译系统的组织方式

GAC-Mapping 用的是 CMake,工程结构一般是src/放源码、include/放头文件、CMakeLists.txt在根目录。我建议在根目录下建一个build/做 out-of-source 编译,别在源码目录里直接cmake .,否则生成的中间文件会把目录搞得一团糟,后面想清理都难。

CMakeLists 里有几个关键点要盯:find_package的顺序、CMAKE_BUILD_TYPE的设置、以及链接库的顺序。链接顺序在 Linux 下很敏感,被依赖的库要放在后面。我遇到过 PCL 和 VTK 链接顺序反了导致的一堆 undefined reference,调换顺序就好了。CMAKE_BUILD_TYPE建议先设Debug,方便出问题时用 gdb 看栈;跑性能测试再切Release。

3. 核心细节解析与实操要点

3.1 相机内参与数据格式的对齐

GAC-Mapping 对输入数据的格式要求比较死。图像一般是灰度或 RGB 的 8 位图,深度图是 16 位无符号整型(单位毫米)或者 32 位浮点(单位米),相机内参是一个 3x3 的矩阵,包含焦距和主点。这里最容易出错的是深度图的单位。我见过有人把毫米当米喂进去,结果建出来的地图尺度差了 1000 倍,位姿全飘。

实操上,我会在数据加载后立刻做一次校验:读一张深度图,打印它的最大值和最小值,再结合场景的物理尺度判断单位对不对。比如室内场景,深度值如果是几万,那基本是毫米;如果是几,那就是米。这个判断花不了几秒钟,但能省掉后面几小时的调试。

相机内参的格式也要对齐。有些数据集给的是fx, fy, cx, cy四个数,有些给的是完整的 3x3 矩阵。GAC-Mapping 内部一般用矩阵形式,所以加载时要做好转换。转换公式很简单:

K = [[fx, 0, cx], [0, fy, cy], [0, 0, 1]]

但要注意cx, cy是像素坐标,别和归一化坐标搞混。我踩过一次坑,把归一化的主点当像素主点传进去,结果特征匹配的几何校验全错,匹配对数量骤降。

3.2 特征提取与匹配的参数调优

特征提取这块,GAC-Mapping 支持多种描述子,具体用哪个取决于你的场景。纹理丰富的场景用 SIFT 或 ORB 都行,弱纹理场景可能要靠深度信息补。参数上,我重点关注三个:特征点数量上限、匹配距离阈值、以及几何校验的阈值。

特征点数量上限别设太大。设成几千,提取慢不说,匹配阶段的计算量也爆炸。我一般从 1000 到 2000 起步,看场景复杂度再调。匹配距离阈值(如果是基于描述子距离的匹配)决定了多少匹配对能留下来,设太严匹配对不够,设太松误匹配多。我的经验是先设一个中等值,跑一遍看内点率,内点率低于 30% 就说明阈值或者特征质量有问题。

几何校验这块,本质是用对极几何或者 PnP 来剔除误匹配。阈值一般设 1 到 3 个像素。这个值太小会把正确匹配也剔掉,太大又起不到过滤作用。我实测下来 2 像素是个比较稳的起点。

提示:调参时别一次改多个参数,改一个跑一次看结果,否则你根本不知道是哪个参数起的作用。这是我在无数次日调试里总结出来的铁律。

3.3 联合优化的收敛控制

联合优化层是 GAC-Mapping 的核心,也是最能体现它和普通建图工具区别的地方。它把几何残差和外观残差放在一个优化问题里一起解,所以收敛行为比单一残差复杂。控制收敛的关键参数是最大迭代次数、残差下降阈值、以及各残差项的权重。

最大迭代次数我一般设 50 到 100。设太小没收敛就停了,设太大浪费时间还可能过拟合。残差下降阈值设 1e-6 量级,意思是连续两次迭代的残差变化小于这个值就认为收敛了。权重这块最玄学,几何和外观的权重比要根据场景调,纹理好的场景外观权重可以高一点,弱纹理场景就得靠几何撑。

我遇到过一次优化不收敛的情况,残差一直在震荡。查下来是外观权重设太高,而那个场景光照变化剧烈,外观残差本身就不靠谱。把外观权重降下来,几何权重提上去,立刻就收敛了。所以权重不是固定值,得看你的数据特性。

3.4 地图结构的初始化与查询

地图维护层容易被忽视,但它决定了你跑完之后能不能用结果。GAC-Mapping 的地图一般是个带索引的点云或者体素结构,初始化时要设定分辨率。分辨率太高内存吃不消,太低地图精度不够。室内场景我一般用 5 厘米的体素分辨率,室外可以放到 20 到 50 厘米。

初始化之后,每帧优化完的位姿和观测要增量写进地图。这里要注意线程安全,如果地图维护和优化在不同线程里跑,写地图的时候得加锁。我见过有人没加锁,跑着跑着就段错误,查了半天是数据竞争。

查询这块,地图建好后要能根据位姿查局部地图,或者根据坐标查最近的体素。索引建得好,查询就快。我建议在地图初始化时就把空间索引(比如 KD 树或者八叉树)建起来,别等查询时临时建,那样每次查询都慢。

4. 从零到跑通的完整实操

4.1 系统准备与基础依赖安装

拿到一台干净的 Ubuntu 20.04,第一步是把系统更新到最新,然后装基础编译工具。命令如下:

sudo apt update && sudo apt upgrade -y sudo apt install -y build-essential cmake git pkg-config

build-essential带上了 gcc、g++、make,pkg-config在找库的时候很有用。这几样是地基,缺了后面全塌。

接着装核心依赖。Eigen、OpenCV、PCL 我优先用 apt 装,省得自己编译:

sudo apt install -y libeigen3-dev libopencv-dev libpcl-dev

装完确认一下版本:

pkg-config --modversion opencv4 dpkg -l | grep eigen3

OpenCV 应该显示 4.2.0,Eigen 是 3.3.7。如果版本对不上,说明源有问题,先别往下走,把源理清楚。

Ceres 我建议源码编译,因为 apt 源里的版本不一定对。编译 Ceres 2.0.0 的步骤:

sudo apt install -y libgoogle-glog-dev libgflags-dev libatlas-base-dev libsuitesparse-dev git clone --branch 2.0.0 https://ceres-solver.googlesource.com/ceres-solver cd ceres-solver && mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF make -j$(nproc) && sudo make install

-DBUILD_TESTING=OFF能省不少编译时间,测试用例对部署没用。libsuitesparse-dev是 Ceres 的可选依赖,装上能让稀疏求解更快,但如果你遇到链接问题,可以在 cmake 时加-DUSE_SUITESPARSE=OFF绕开。

4.2 源码获取与工程配置

把 GAC-Mapping 的源码拉到本地,进根目录建 build 目录:

git clone <项目仓库地址> gac-mapping cd gac-mapping && mkdir build && cd build

配置 CMake 之前,先看一眼根目录的CMakeLists.txt,确认find_package里要求的版本和你装的对得上。如果它要求 OpenCV 4.5 而你只有 4.2,要么改 CMakeLists 里的版本号(如果 API 兼容),要么升级 OpenCV。我倾向于先试着改版本号,跑不通再升级。

配置命令:

cmake .. -DCMAKE_BUILD_TYPE=Debug

Debug 模式方便调试。配置过程中盯住终端输出,find_package失败会明确告诉你缺什么。常见的失败是找不到 Ceres 或者 PCL 的某个组件,按提示补装对应包即可。

配置成功后编译:

make -j$(nproc)

-j$(nproc)用满所有核心,编译快。如果编译报错,先看第一个错误,后面的往往是连锁反应。第一个错误通常是头文件找不到或者 API 不匹配,针对性解决。

4.3 数据集准备与配置文件修改

GAC-Mapping 跑通需要一个数据集。我用的是一个模拟的室内序列,包含 RGB 图、深度图和对应的内参文件。数据目录结构一般是这样:

data/ rgb/0001.png, 0002.png, ... depth/0001.png, 0002.png, ... camera.txt

camera.txt里放内参,格式按代码要求来,通常是fx fy cx cy一行。确认深度图是 16 位 PNG,用file命令或者 Python 读一下验证:

import cv2 d = cv2.imread('data/depth/0001.png', cv2.IMREAD_UNCHANGED) print(d.dtype, d.min(), d.max())

输出应该是uint16,最大值在几千到几万之间(毫米单位)。如果是uint8,说明格式不对,得转换。

然后改配置文件。GAC-Mapping 一般有个config.yaml或者类似的参数文件,里面要设数据路径、内参路径、特征提取参数、优化参数。我建议先把路径和内参填对,其他参数用默认值跑第一遍,确认链路通了再调参。

4.4 首次运行与结果验证

运行命令一般是:

./gac_mapping ../config/config.yaml

第一次跑,别指望结果完美,目标是不崩、有输出。终端会打印每帧的处理信息,包括特征点数量、匹配对数量、优化迭代次数、当前位姿。盯住这些数字:特征点数量应该在几百到几千,匹配对数量别是个位数,优化迭代次数别每次都顶到最大值(顶到说明没收敛)。

跑完看输出目录,应该有轨迹文件(每帧位姿)和地图文件(点云或者体素)。轨迹文件用文本编辑器打开,看位姿数值有没有突变或者 NaN。有 NaN 说明某一步数值计算炸了,回去查那一步的输入。地图文件可以用 PCL 的工具或者简单的可视化脚本看一眼,确认点云不是空的、形状大致合理。

我第一遍跑的时候,轨迹里出现了几帧位姿跳变,查下来是那几帧的深度图有大量无效值(0 或者极大值),导致优化时残差异常。在预处理里加了无效深度过滤,问题就没了。所以首次运行后的结果验证很重要,别跑完就完事。

5. 常见问题与排查技巧实录

5.1 编译期报错速查

编译期的坑我整理成一张表,按报错关键词查:

报错关键词可能原因解决手法
undefined reference to cv::...OpenCV 缺 contrib 或链接顺序错源码编译带 contrib,调整链接顺序
Eigen::... no memberEigen 版本过高降回 3.3.7
Ceres Manifold相关Ceres 版本 2.1 用了新 API换 Ceres 2.0.0
PCL ... VTK链接错误PCL 和 VTK 链接顺序被依赖库放后面
Could NOT find ...依赖没装或路径没配按提示装包,设CMAKE_PREFIX_PATH

CMAKE_PREFIX_PATH这个环境变量很关键,自己编译安装的库(比如 Ceres)默认装在/usr/local,CMake 有时找不到,得手动指定:

cmake .. -DCMAKE_PREFIX_PATH=/usr/local

5.2 运行期崩溃与段错误定位

运行时段错误是最烦的,因为终端只给你一句Segmentation fault,啥也不说。定位手法是用 gdb 跑:

gdb --args ./gac_mapping ../config/config.yaml

进去后输入run,崩了之后输入bt看调用栈。栈顶那几帧就是问题所在。我遇到过的段错误,一半是空指针(某帧数据没读到,代码没判空),一半是数组越界(特征点索引超了)。看栈能快速定位到具体函数,再回去看那个函数的输入。

还有一种隐蔽的崩溃是内存耗尽。地图分辨率设太高、特征点设太多,跑着跑着内存吃满,系统直接杀进程。用htop盯着内存,如果持续上涨不回落,就是有泄漏或者参数太大。降分辨率、减特征点数量能缓解。

5.3 优化不收敛与轨迹漂移

优化不收敛的表现是残差震荡或者迭代次数顶满。排查顺序:先看输入数据质量(深度有没有大量无效值、图像有没有严重模糊),再看权重参数(几何和外观的比例),最后看初值(上一帧位姿作为初值,如果上一帧就错了,这一帧也难对)。

轨迹漂移则是另一回事,表现为位姿慢慢偏离真实轨迹。原因可能是累积误差没被约束住,或者回环检测没生效。GAC-Mapping 如果有回环模块,确认它开着;如果没有,那漂移是正常的,只能靠更频繁的全局优化来压。我实测下来,室内短序列漂移可控,长序列必须靠回环或者全局优化兜底。

注意:调优化参数时,先把最大迭代次数调大,确认能收敛,再逐步收紧阈值。反过来做你会误以为参数有问题,其实是迭代不够。

5.4 我踩过的三个典型坑

第一个坑是深度图和彩色图没对齐。有些数据集的深度和 RGB 是不同视角拍的,直接当对齐的用,特征点的深度值全错。解决方法是确认数据集是否已对齐,没对齐的话要做配准,或者换用已对齐的数据集。

第二个坑是内参文件的行列顺序。有的文件是fx fy cx cy一行,有的是fx 0 cx换行0 fy cy的矩阵形式。读错了主点位置,几何校验全废。读内参时打印出来和数据集文档对一遍,几秒钟的事。

第三个坑是多线程下的数据竞争。地图维护和优化并行跑,没加锁,跑几分钟就崩。用-fsanitize=thread编译一版跑,能直接报出竞争位置。定位到之后加锁或者改成串行,问题解决。这个坑最隐蔽,因为崩的时间不固定,容易误以为是随机 bug。

6. 性能调优与后续扩展的一点经验

跑通之后如果想让它跑得更快或者更准,有几个方向可以试。编译优化上,把CMAKE_BUILD_TYPE切到Release,加-O3 -march=native,能榨出不少性能,尤其是优化器那部分。但注意-march=native编出来的二进制换机器可能跑不了,部署到别的机器要重新编。

算法层面,特征提取可以降分辨率做,匹配在粗尺度上先筛一遍,再在细尺度上精匹配,这样计算量能降下来。优化器那边,如果残差项很多,可以用 Ceres 的稀疏求解器,配合libsuitesparse效果明显。

地图维护上,增量式建图比每帧重建快得多,但要注意增量更新的正确性。我一般会在增量更新后定期做一次全局一致性检查,防止误差累积。

后续如果要扩展,比如加语义信息或者多传感器融合,建议先把当前的纯几何外观流水线跑稳,再往上叠。基础不稳,加什么都是空中楼阁。我在实际项目里就是先把单目加深度这条链路调透,确认轨迹和地图都靠谱了,才敢往里加别的传感器。

最后分享一个我个人的习惯:每次改完参数或者代码,把配置文件复制一份带时间戳存档,跑出来的结果也对应存档。这样出了问题能回溯到上一个能跑的版本,不至于改着改着把能跑的配置弄丢了。这个习惯救过我好几次,尤其是在调参调到怀疑人生的时候。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询