Bootstrap

【slam十四讲第二版】【课本例题代码向】【第二讲初识SLAM】【SLAM基础知识】【linux下C++编译】【cmake基础使用】

0 前言

经典开始

1 SLAM是什么

  • 自主运动的两大基本问题(p14)
  1. 我在什么地方?-定位
  2. 周围环境是什么样子?-建图
  3. 定位与建图=内外兼修 定位侧重对自身的了解,建图侧重对外在的了解
  • 相互关联
  1. 准确的定位需要精确的地图
  2. 精确的地图来自准确的定位

2 传感器

  1. 机器人感知外界环境的手段
  2. 种类:内置的/外置的
    内置:感受机器人本体信息;IMU、激光、相机
    外置:安装于环境中的;二维码Marker、GPS、导轨
  • 环境限制了传感器的形式
  1. GPS:需要接受到卫星信号的环境
  2. Marker、导轨:需要环境允许安装
  • 相比之下,激光、相机等携带式传感器更加自由,没有对环境提出任何要求,从而使得这种定位方案可适用于未知环境。
  1. 轮式编码器:测量轮子转动的角度
  2. IMU:测量运动的角速度和加速度
  3. 相机和激光传感器:读取外部环境的某种观测数据

3 相机

  • 相机
    以一定速率采集图像、形成视频
  • 分类
  1. 单目相机 Monocular:仅一个摄像头
  2. 双目相机(立体相机)Stereo:两个摄像头
  3. 深度相机 RGB-D:多个摄像头,除了采集到彩色照片,还可以读出每个像素与相机之间的距离。
  4. 其他 全景、Event Camera
  • 相机的特点
  1. 以二维投影形式记录了三维世界的信息
  2. 该过程丢掉了一个维度:距离
  • 各类相机的区别
  1. 单目:无深度 深度需要其他手段估计
  2. 双目:通过视差计算深度
  3. RGB-D:通过物理方法测量深度

3.1 单目相机 Monocular

  • 特点:结构简单、成本低
  • 以二维的形式记录了三维的世界,这个过程中丢掉了深度(距离)信息。
  • 单目SLAM中,必须移动相机,才能估计它的运动(motion),同时估计场景中物体的远近和大小。
  • 视差(Disparity):物体在图像上的运动形成了视差,通过视差判断物体的远近,此为一个相对的值。
  • 尺度(Scale):单目SLAM估计的轨迹和地图将与真实的轨迹和地图相差一个因子,也就是所谓的尺度。
  • 尺度不确定性(Scale Ambiguity):单目SLAN无法仅凭图像确定真实尺度的性质。
  • 缺点:平移之后才可以计算深度,以及无法确定真实尺度;根本原因是通过单张图片无法确定深度。

3.2 双目相机 Stereo

  • 使用目的:通过某种手段测量物体与相机之间的距离,克服单目相机无法知道距离的缺点;一旦知道距离,场景的三维结构就可以通过单个图像恢复,同时消除尺度不确定性。
  • 基线(Baseline):双目相机的两个单目相机之间的距离,通过这个距离可以估计每个像素的空间位置。
  • 双目相机测量到的深度范围与基线相关;基线距离越大,能够测量到的物体就越远,这也是无人车搭载的双目相机很大的原因。
  • 缺点:配置与标定较为复杂;其深度量程和精度受双目的基线与分辨率所限,而且视差的计算非常消耗资源,需要GPU和FPGA加速,才可以实时输出整张图象的距离信息。

3.3 深度相机(RGB-D相机)

  • 2010年前后兴起
    特点:通过红外结构光或Time-of-Flight(ToF)原理,像激光传感器那样,通过主动向物体发射光并接受返回的光,测出物体与相机之间的距离。硬件解决距离,节省计算资源。
    缺点:测量范围窄,噪声大、视野小、易受日光干扰、无法测量透射材质等诸多问题
    应用:主要用于室内,室外较难应用。

4 经典视觉SLAM框架(p19)

在这里插入图片描述

  • 整个SLAM流程包括:
  1. 传感器信息读取:在视觉SLAM中主要为相机图像信息的读取和预处理;机器人中,还可能有码盘、惯性传感器等信息的读取和同步。
  2. **前端视觉里程计(Visual odometry,VO):**视觉里程计的任务是估算相邻图像间相机的运动,以及局部地图的样子。VO又称为前端(Front End)。
  3. 后端(非线性)优化(Optimization):后端接受不同时刻视觉里程计测量的相机位姿,以及回环检测的信息,对他们进行优化,得到全局一致的轨迹和地图。由于接在VO之后,又称为后端(Back)
  4. **回环检测(Loop Closure Detection):**回环检测判断机器人是否到达过先前的位置,如果检测到回环,它会把信息提供给后端进行处理。
  5. **建图(Mapping):**根据估计的轨迹,建立与任务要求对应的地图。
  • 如果把环境限定在静态、刚体、光照变换不明显、没有人为干扰的场景,那么这种场景下SLAM技术已经相当成熟。

4.1 视觉里程计

  • 仅凭视觉里程计估计轨迹会有累积漂移(Accumulating Drift),为了解决漂移,需要后端优化和回环检测。回环检测负责把“机器人回到原始位置”的事情检测出来,而后端优化则根据该信息,校正整个轨迹的形状。

4.2 后端优化

  • 笼统地说,后端优化主要指处理SLAM过程中的噪声问题。
  • 最大后验概率估计(Maximum-a-Posteriori,MAP):后端优化要考虑的问题,就是如何从这些带有噪声的数据中估计整个系统的状态,以及这个状态估计的不确定性有多大。
  • 后端负责整体的优化过程,它往往面对的只有数据,不必关心这些数据到底来自什么传感器。
  • 在视觉SLAM中,前端和计算机视觉研究领域更为相关,比如图像的特征提取与匹配等,后端则主要是滤波与非线性优化算法。
  • SLAM问题的本质:对运动主题自身和周围环境空间不确定性的估计。

4.3 回环检测

  • 通过二维码、判断图像间的相似性来完成回环检测,检测到回环之后,会把“A与B是同一个点”这样的信息告诉后端优化算法,然后后端根据这些新的信息,把轨迹和地图调整到符合回环检测结果的样子,这样就可以消除累积误差,得到全局一致的轨迹和地图

4.5 建图

4.5.1 度量地图(Metric Map)

  • 度量地图强调精确地表示地图中物体的位置关系,通常用稀疏(Sparse)与稠密(Dense)对其分类。
  1. 稀疏地图(Sparse)进行了一定程度的抽象,并不需要表达全部的物体。选择一部分具有代表意义的东西,称之为路标(Landmark),那么一张稀疏地图就是由路标组成的地图,而不是路标的部分就可以忽略。
  2. 稠密地图(Dense)着重于建模所有可以看到的东西。二位度量地图中的小格子(Grid),三维度量地图中的小方块(Voxel),一个小块有占据、空闲、未知三种状态,以表示该格内是否有物体,当查询某个空间位置时,地图能够给出该位置是否可以通过的信息。可用于各种导航算法,如A*、D*等。
    缺点是:但是会消耗大量的存储空间,而且多数信息都是无用的;在大规模建图的时候,会出现一致性问题,很小的一点转向误差,可能会导致两间屋子的墙出现重叠,使地图失效。
  • 定位时用稀疏地图就足够了;但是导航时需要稠密地图

4.5.2 拓扑地图(Topological Map)

  • 相比于度量地图的精确性,拓扑地图更强调地图元素之间的关系。拓扑地图是一个图(Graph),由节点和边组成,只考虑节点间的连通性。放松了地图对精度位置的要求,去掉了地图的细节,是一种更为紧凑的表达方式。
  • 然而拓扑地图不擅长表达具有复杂结构的地图,如何对地图进行分割,形成节点与边,又如何使用拓扑地图进行导航与路径规划,仍是待研究的问题。

5 SLAM问题的数学表述(p24)

  • 运动方程
  • 观测方程

6 实践:编程基础

6.1 安装linux操作系统

虚拟机参考:【第一天】【ROS操作系统】【1】Ubuntu安装方法(虚拟机-18.04)
双系统参考:Windows + Ubuntu 双系统(超详细图文教程)

6.2 Hello SLAM

  1. 首先安装g++,也可以用clang,但这里没有用,比g++好一点
sudo apt-get install g++
  1. 创建并打开一个文件main.cpp
touch main.cpp

vim main.cpp
  1. 然后写入代码,如下:
#include <iostream>
using namespace std;


int main(int argc,char argv)
{       
        cout << "Hello SLAM!" << endl;
       return 0;
}    
  1. 编译
g++ main.cpp

g++ -o main main.cpp

会生成a.out文件(默认命令,指定main),其具有执行权限(终端里面颜色不同,深色)

  1. 运行
bupo@bupo-vpc:~/shenlan/slam14_my/cap1/c_bian_yi$ ./a.out 
Hello SLAM!

6.3 使用cmake

  1. 安装cmake
sudo apt-get install cmake
  1. 创建CMakeLists.txt
touch CMakeLists.txt
  1. CMakeLists.txt中写入内容,如下:
#声明要求的cmake最低版本
cmake_minimum_required(VERSION 2.8)

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

# 添加一个可执行程序
# 语法:add_executable(程序名 源代码文件)
add_executable(helloSLAM main.cpp)
  1. 编译
  • 进行编译,并指定所生成中间文件的位置为当前目录下,其中MakeFile最重要,但不必修改它;cmake处理了工程文件之间的关系
cmake .
  • 编译生成可执行文件helloSLAM,实际上调用了g++来编译程序
make
  1. 运行
./helloSLAM 
Hello SLAM!
  • 通常为了优化,我们会把中间文件放进build文件夹中,则上述过程就变味了从第四步开始为4.
mkdir build
cd build
cmake ..
make

6.4 使用库

  • 只有带main函数的文件会生成可执行文件
  • 而库(library),是被打包为一个东西,供其他程序调用。

6.4.1 编写自己的库(纯学习,无用处)

  • 下面演示如何自己编写一个库
  1. 创建主文件夹
mkdir hello_ku
cd hello_ku
mdkir src
  1. 创建libHelloSLAM.cpp
cd src
touch libHelloSLAM.cpp

内容如下:

//这是一个库文件
#include <iostream>
using namespace std;

void printHello()
{
        cout << "Hello SLAM !"<< endl;
}
  1. 创建CMakeLists.txt文件
touch CMakeLists.txt

内容如下:

#静态库
add_library(hello src/libHelloSLAM.cpp)

#动态库
#add_library(hello_shared SHARED src/libHelloSLAM.cpp)

静态库:静态库以.a作为后缀名,每次被调用都会生成一个副本
共享库:共享库以.so结尾,只有一个副本,更省空间

  1. 编译库
mkdir build
cd build
cmake ..
make
  1. 为了更好的使用,提供头文件libHelloSLAM.h
cd src
touch libHelloSLAM.h

内容如下:

#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
//上面的宏定义是为了防止重复引用这个头文件而引起的重定义错>误
//打印一句hello的函数
void printHello();

#endif

6.4.2 调用自己的库(在工程include中的,不是下载到系统中的)

  1. 创建文件夹
mkdir hello_diaoyong_ku
cd hello_diaoyong_ku
mkdir src
  1. 创建main文件
cd src
mkdir useHello.cpp

内容如下:

printHello()函数
int main(int argc,char ** argv)
{
	printHello();
	return 0;

}
  1. 在include文件夹中创建库
mkdir include
cd include
touch libHelloSLAM.cpp

内容如下:


#include "libHelloSLAM.h"
#include <iostream>
using namespace std;

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

创建头文件:

touch libHelloSLAM.h

内容如下:

#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
//上面的宏定义是为了防止重复引用这个头文件而引起的重定义错误
//打印一句hello的函数
void printHello();

#endif
  1. 创建CMakeLists.txt文件
mkdir CMakeLists.txt

内容如下:

cmake_minimum_required(VERSION 2.8)
project(useHello)

include_directories("include")#说明include是作为一个引用头文件的地方
add_library(hello include/libHelloSLAM.cpp)

add_executable(useHello src/useHello.cpp)
target_link_libraries(useHello hello)

  1. 编译
mkdir build
cd build
cmake ..
make
  1. 运行
cd build
./useHello
;