SLAM笔记专栏:https://blog.csdn.net/weixin_44543463/category_10925276.html

一、引言

对于一个移动机器人来说,我们往往需要它知道两件事:

  • 我在什么地方——定位
  • 周围环境是什么样——建图

1.1 定位方法

对于定位来说,有许多方法可以使机器人确定自己的位置。主要分为两类:

  1. 携带与机器人本体上的:轮式编码器、相机、激光等
  2. 安装于环境中的:导引线、二维码等

其中安装于环境中的设备约束了外部环境,对环境要求较高,无法提供通用普遍的解决方案。而携带于机器人本体的传感器则可适用于未知环境。

在视觉SLAM中,我们更加注重考虑如何用相机解决定位和建图的问题。

1.2 相机

相机按照工作方式可以分为三类。

单目相机:只使用一个摄像头进行SLAM。它获取的数据是一张张照片,照片以二维的形式反映了三维的世界,因此在单张图像里,无法确定一个物体的真实大小和物体的距离。如果我们想恢复三维结构,就必须转动相机的视角。

双目相机:使用两个摄像头测量。这样就消除了单目相机的尺度不确定性,可以测量物体的大小。并且两个摄像头间距(基线)越大,测量范围就越远。其缺点是配置和标定十分复杂,非常消耗计算资源。

深度相机:通过红外结构光或ToF原理,主动向物体发射并接受返回的光,测出物体离相机的距离。其通过物理手段测量,节省了大量的计算量,但缺点是测量范围窄、噪声大、事业小、易受日光干扰、无法测量投射材料等。

二、经典SLAM框架

2.1 传感数据读取

传感数据信息读取主要为图像信息的读取和预处理,还可能有马盘、惯性传感器等信息的读取和同步。

2.2 前端视觉里程计

视觉里程计的任务是估算相邻图像间相机的运动。由于是估计两张图间相机的运动,然后串联起来得到的机器人轨迹,所以仅通过视觉里程计来估算轨迹,不可避免会出现累计误差。为了解决这个问题所以有了后端优化和回环检测。

2.3 后端优化

后端优化的任务是接受视觉里程计测得的相机位姿,接合回环检测的新息,得到全局一致的轨迹和地图。
后端优化主要是指处理SLAM过程中噪声的问题,主要是滤波和非线性优化算法等。

2.4 回环检测

目的是判断机器人是否曾到达过先前的位置。主要解决位置随时间漂移的问题。

2.5 建图

根据估计的轨迹,建立于任务要求对应的地图。地图的形式主要有两种:

  • 度量地图:用稀疏和稠密对他们分类。稀疏地图进行了一定程度的抽象,可以满足定位的需求。而导航时,我们需要稠密的地图,稠密地图由小方块或小格子表示,每个小块有占据、空闲、未知三种状态,这种地图可以用于各种导航算法。
  • 拓扑地图:只由节点和边组成,只考虑节点之间的连通性。去掉了细节问题,是一种更紧凑的表达方式,但无法表达具有复杂结构的地图。

三、SLAM问题的数学表述

机器人携带传感器在未知环境内运动时,相机会在离散的时刻采集一系列数据。

3.1 运动方程——定位问题

在这些离散时刻机器人的位置,用$x$表示机器人的位置。
由于传感器的不同,某时刻机器人的位置没有确定的计算方程,但可以知道每一时刻的位置都取决于上一时刻的位置以及传感器采集的数据

其中,$u_k$为传感器读数,$w_k$为噪声。

3.2 观测方程——建图问题

设地图由多个路标组成,每个时刻传感器会测量到一部分路标点,得到这些点的观测数据。即机器人在$x_k$位置上测量到路标点$y_j$,产生了观测数据$z_{k,j}$。

其中$v_{k,j}$为观测时的噪声。

3.3 方程求解方法

按照运动和观测方程是否为线性,分为线性/非线性系统
按照噪声是否服从高斯分布分类,分为高斯/非高斯系统

  1. 对于线性高斯系统,无偏最优估计可由卡尔曼滤波器给出。
  2. 对于复杂的非线性非高斯系统,使用扩展卡尔曼滤波和非线性优化两类方法解决。

四、Linux基础

4.1 编写程序Hello SLAM

在根目录下创建文件夹/slam/ch01
使用vim或gedit或nano等编辑器,输入以下代码,保存为helloSLAM.cpp

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main()
{
cout << "Hello SLAM!" << endl;
return 0;
}

4.2 使用cmake

仍然在此目录下创建一个文件CMakeLists.txt,输入以下内容

1
2
3
4
5
6
7
8
#声明要求的cmake最低版本
cmake_minimum_required( VERSION 2.8 )

#声明一个cmake工程
project( HelloSLAM )

#添加一个可执行程序 语法:add_executable( 可执行程序名 源代码文件名 )
add_executable( helloSLAM helloSLAM.cpp )

创建一个文件夹mkdir build用于保存变异生成的中间文件
进入build文件夹,使用以下代码进行编译

1
2
cmake ..
make

此时产生了一个名为helloSLAM的可执行文件,使用./helloSLAM即可执行此程序看到正确的输出。

4.3 使用库

C++中只有带有main函数的文件才会生成可执行程序,而其他代码,我们只需把它打包成库,供程序调用即可。
(1)创建库
在刚才的ch01文件夹下,创建一个名为libHelloSLAM.cpp的文件

1
2
3
4
5
6
#include <iostream>
using namespace std;
void printHello()
{
cout << "Hello SLAM!!" << endl;
}

这个库文件提供了一个printHello函数,但它没有main函数,因此不会生成可执行文件,我们需要告诉cmake,我想把这个文件编译成叫“hello”的库。在CmakeLists.txt内添加:

1
add_library(hello_shared SHARED libHelloSLAM.cpp)

这里创建的是共享库。
在Linux中,库文件分成静态库动态库两种。
静态库以.a后缀,每次被调用都会生成一个副本。
共享库以.so后缀,只有一个副本,更省空间。

此时编译的话,可以得到一个libhello_shared.so的库文件

(2)创建头文件
创建一个名为libHelloSLAM.cpp的文件

1
2
3
4
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
void printHello();
#endif

(3)创建主程序
创建一个名为useHello.cpp的文件

1
2
3
4
5
6
#include "libHelloSLAM.h"
int main()
{
printHello();
return 0;
}

在CMakeLists.txt中添加生成可执行程序的生成命令,链接到刚才我们使用的库上。

1
2
add_executable( useHello useHello.cpp )
target_link_libraries( useHello hello_shared )

进行编译,得到useHello这个可执行文件。