[SLAM]视觉SLAM十四讲学习笔记

本书概要

《视觉SLAM十四讲》的出版日期 2017.3
Ubuntu 14.04
C++
Kdevelop

本书框架

部分/章节 内容
第1部分:数学基础篇
第1~6讲
⑴前言
⑵SLAM系统概述
⑶三维空间运动
⑷李群和李代数
⑸针孔相机模型以及图像在计算机中的表达
⑹非线性优化
第2部分:SLAM技术篇
第7~14讲
⑺特征点法的视觉里程计
⑻直接法的视觉里程计
⑼视觉里程计的实践
⑽后端优化
⑾后端优化中的位姿图
⑿回环检测
⒀地图构建
⒁当前的开源SLAM项目以及未来的发展方向

实例索引

主题 内容 备注
CH2:编程基础 g++编译、cmake编译、Kdevelop集成开发环境

CH1 预备知识

SLAM(Simultaneous Localization and Mapping),同时定位与地图构建
搭载特定传感器的主体,在没有环境先验信息的情况下,于运动过程中建立环境的模型,同时估计自己的运行
传感器主要为相机,称为“视觉SLAM

两个问题:

  • 定位:估计传感器自身的位置
  • 建图:建立周围环境的模型

要求:

  • 实时地
  • 没有先验知识

CH2 初识SLAM

引入

感知的“内外之分”:

功能 解释 备注
定位 我在什么地方? 自身的状态(位置)
建图 周围环境是什么样? 外在的环境(地图)

两类传感器:

传感器 举例 优劣
携带于机器人本体上 机器人的轮式编码器、相机、激光传感器 测量的是间接的物理量而不是直接的位置数据,可通过间接手段推算位置,好处是没有对环境提出任何要求,使定位方案可适用于未知环境
安装于环境中 导轨、二维码标志 测量的是直接的位置信息,简单可靠,但传感器约束了外部环境,无法提供普遍、通用的解决方案

视觉SLAM:以一定速率拍摄周围的环境,形成一个连续的视频流

三大类相机:

相机 说明 缺点
单目相机(Monocular) 一个摄像头 ①平移之后才能计算深度
②无法确定真实尺度(尺度不确定性)
双目相机(Stereo) 两个摄像头 计算量大
深度相机(RGB-D) 红外结构光或Tof(Time-of-Flight)
彩色图片 + 每个像素与相机之间的距离
①测量范围窄、视野小
②噪声大、易受日光干扰(主要用于室内
③无法测量透射材质

照片本质上是拍照时的场景(Scene)在相机的成像平面上留下的一个投影,以二维的形式反映了三维的世界

单目相机拍摄的图像只是三维空间的二维投影。这个过程丢掉了深度(或距离)

  • 在单目相机中,无法通过单张图片来计算场景中物体与我们之间的距离(远近)
  • 在单张图像里,无法确定一个物体的真实大小,可能是一个很大但很远的物体,也可能是很近但很小的物体

经典视觉SLAM框架

模块 内容 备注
传感器信息读取 视觉SLAM中主要为相机图像信息的读取和预处理
在机器人中,还有码盘、惯性传感器等信息的读取和同步
视觉里程计
(Visual Odometry,VO)
估算相邻图像间相机的运动,以及局部地图的样子
又称为前端(Front End)
会不可避免地出现累积漂移(Accumulating Drift),故需要后端优化和回环检测
后端优化
(Optimization)
接受不同时刻视觉里程计测量的相机位姿,以及回环检测的信息,对它们进行优化,得到全局一致的轨迹和地图
又称为后端(Back End)
从带有噪声的数据中估计整个系统的状态,以及这个状态估计的不确定性有多大(最大后验概率估计,MAP,Maximum-a-Posteriori)
回环检测
(Loop Closing)
判断机器人是否到达过先前的位置。如果检测到回环,会把信息提供给后端进行处理 解决位置估计随时间漂移的问题
视觉回环检测本质是一种计算图像数据相似性的算法
建图
(Mapping)
根据估计的轨迹,建立与任务要求对应的地图 地图是对环境的描述,描述不固定,视SLAM的应用而定
建图没有固定的形式和算法

前端与后端:

模块 作用 领域
前端 给后端提供待优化的数据、以及这些数据的初始值 视觉SLAM中前端和计算机视觉研究领域相关
后端 负责整体的优化过程,不关心数据来自什么传感器 主要是滤波非线性优化算法

地图的种类:

地图 特点 备注
度量地图 精确地表示地图中物体的位置关系 稀疏(Sparse)地图——定位:不需要表达所有的物体,表达一部分具有代表意义的东西(路标,Landmark)
稠密(Dense)地图——导航:建模所有看到的东西,按照某种分辨率,由许多小块组成
拓扑地图 更强调地图元素之间的关系 是图(Graph),由节点和边组成,只考虑节点间的连通性
不擅长表达具有复杂结构的地图

SLAM问题的本质:对运动主体自身和周围环境空间不确定性的估计

如果把工作环境限定在静态、刚体、光照变换不明显、没有人为干扰的场景,SLAM系统是相当成熟的了

SLAM问题的数学表述

两个基本方程:

方程 直观含义
运动方程 时刻到时刻,机器人的位置是如何变化的
观测方程 机器人在位置上看到某个路标点,产生了一个观测数据

符号解释:

  • 相机采集数据的离散时刻
  • 各时刻机器人的位置记为,构成了机器人的轨迹
  • 路标点共有个,用表示
  • 运动方程中,运动传感器的读数(不一定是位置之差,还可能是加速度、角速度等)
  • 运动方程中,噪声
  • 观测方程中,噪声
  • 观测方程中,观测数据

这两个方程描述了最基本的SLAM问题:
当知道运动测量的数据、传感器的读数时,如何求解定位问题(估计)和建图问题(估计
把SLAM问题建模成了一个状态估计问题:如何通过带有噪声的观测数据,估计内部的、隐藏着的状态变量
状态估计问题的求解,与两个方程的具体形式(线性/非线性)、噪声服从哪种分布(高斯/非高斯)有关

线性高斯系统(Linear Gaussian,LG系统)最简单,它的无偏的最优估计可以由卡尔曼滤波器(Kalman Filter,KF)给出
非线性非高斯系统(Non-Linear Non-Gaussian,NLNG系统)较为复杂,使用扩展卡尔曼滤波器(Extended Kalman Filter,EKF)和非线性优化两大类方法求解
时至今日,主流视觉SLAM使用以图优化(Graph Optimization)为代表的优化方法

实践:编程基础

Linux C++程序

使用g++编译

目录结构:
├─ ch2
│ └─ helloSLAM.cpp

helloSLAM.cpp中输入以下代码:

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

int main( int argc, char** argv )
{
cout<<"Hello SLAM!"<<endl;
return 0;
}

g++将其编译成可执行文件,并运行可执行文件:

1
2
g++ helloSLAM.cpp
./a.out

“command not found”的解决方案:安装g++:

1
sudo apt-get install g++

使用cmake编译

目录结构:
├─ ch2
│ ├─ helloSLAM.cpp
│ └─ CMakeLists.txt

在一个cmake工程中,用cmake命令生成一个makefile文件,然后用make命令根据makefile文件的内容编译整个工程

  • cmake过程:处理工程文件之间的依赖关系
  • make过程:实际调用g++来编译程序

新建CMakeLists.txt,内容如下:

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

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

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

在当前目录下,调用cmake对CMakeLists.txt进行分析,并用make命令编译,再运行程序:

1
2
3
cmake .
make
./helloSLAM

删除中间文件的版本的命令组合:

1
2
3
4
5
mkdir build
cd build
cmake ..
make
./helloSLAM

使用库

步骤:

  1. 程序代码由头文件源文件组成
  2. 带有main函数的源文件编译成可执行程序
    其他的编译成库文件
  3. 如果可执行程序想调用库文件中的函数,需要参考该库提供的头文件,以明白调用的格式
    同时,要把可执行程序链接到库文件上

Linux中,库文件分为两种:

库文件 后缀 区别
静态库 .a 每次被调用都会生成一个副本
共享库 .so 只有一个副本

目录结构:
├─ ch2
│ ├─ CMakeLists.txt
│ ├─ libHelloSLAM.cpp
│ ├─ libHelloSLAM.h
│ └─ useHello.cpp

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )

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

# 设置编译模式
set( CMAKE_BUILD_TYPE "Debug" )

# 添加一个库
# 静态库
# 把libHelloSLAM.cpp编译成一个叫作“hello”的库
add_library( hello libHelloSLAM.cpp )
# 共享库
add_library( hello_shared SHARED libHelloSLAM.cpp )

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

# 将库文件链接到可执行程序上
target_link_libraries( useHello hello_shared )

libHelloSLAM.cpp

1
2
3
4
5
6
7
8
//这是一个库文件
#include <iostream>
using namespace std;

void printHello()
{
cout<<"Hello SLAM"<<endl;
}

libHelloSLAM.h

1
2
3
4
5
6
7
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
// 上面的宏定义是为了防止重复引用这个头文件而引起的重定义错误

void printHello();

#endif

useHello.cpp

1
2
3
4
5
6
7
8
#include "libHelloSLAM.h"

// 使用 libHelloSLAM.h 中的 printHello() 函数
int main( int argc, char** argv )
{
printHello();
return 0;
}

使用IDE

安装Kdevelop:

1
sudo apt-get install kdevelop

断点调试功能
Project → Open/Import Project
Run → Configure Launches

快捷键:

快捷键 功能 备注
注释 Ctrl + D
取消注释 Ctrl + Shift + D
单步运行 F10 单步调试
单步跟进 F11 单步调试
单步跳出 F12 单步调试

CH3 三维空间刚体运动

旋转矩阵

刚体:位置、姿态

  • 位置:相机在空间中的哪个地方
  • 姿态:相机的朝向

实践:Eigen

旋转向量和欧拉角

四元数

*相似、仿射、射影变换

实践:Eigen几何模块

可视化演示

CH4 李群与李代数

李群与李代数基础

指数与对数映射

李代数求导与扰动模型

实践:Sophus

*相似变换群与李代数

CH5 相机与图像

相机模型

图像

实践:图像的存取与访问

实践:拼接点云

CH6 非线性优化

状态估计问题

非线性最小二乘

实践:Ceres

实践:g2o

CH7 视觉里程计1

特征点法

实践:特征提取和匹配

2D-2D:对极几何

实践:对极约束求解相机运动

三角测量

实践:三角测量

3D-2D:PnP

实践:求解PnP

3D-3D:ICP

实践:求解ICP

参考

参考内容 参考方面
《视觉SLAM十四讲》 主体内容
《视觉SLAM十四讲》的代码 主体代码参考