【slam十四讲第二版】【课本例题代码向】【第二讲初识SLAM】【SLAM基础知识】【linux下C++编译】【cmake基础使用】
0 前言
经典开始
1 SLAM是什么
- 自主运动的两大基本问题(p14)
- 我在什么地方?-定位
- 周围环境是什么样子?-建图
- 定位与建图=内外兼修 定位侧重对自身的了解,建图侧重对外在的了解
- 相互关联
- 准确的定位需要精确的地图
- 精确的地图来自准确的定位
2 传感器
- 机器人感知外界环境的手段
- 种类:内置的/外置的
内置:感受机器人本体信息;IMU、激光、相机
外置:安装于环境中的;二维码Marker、GPS、导轨
- 环境限制了传感器的形式
- GPS:需要接受到卫星信号的环境
- Marker、导轨:需要环境允许安装
- 相比之下,激光、相机等携带式传感器更加自由,没有对环境提出任何要求,从而使得这种定位方案可适用于未知环境。
- 轮式编码器:测量轮子转动的角度
- IMU:测量运动的角速度和加速度
- 相机和激光传感器:读取外部环境的某种观测数据
3 相机
- 相机
以一定速率采集图像、形成视频 - 分类
- 单目相机 Monocular:仅一个摄像头
- 双目相机(立体相机)Stereo:两个摄像头
- 深度相机 RGB-D:多个摄像头,除了采集到彩色照片,还可以读出每个像素与相机之间的距离。
- 其他 全景、Event Camera
- 相机的特点
- 以二维投影形式记录了三维世界的信息
- 该过程丢掉了一个维度:距离
- 各类相机的区别
- 单目:无深度 深度需要其他手段估计
- 双目:通过视差计算深度
- 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流程包括:
- 传感器信息读取:在视觉SLAM中主要为相机图像信息的读取和预处理;机器人中,还可能有码盘、惯性传感器等信息的读取和同步。
- **前端视觉里程计(Visual odometry,VO):**视觉里程计的任务是估算相邻图像间相机的运动,以及局部地图的样子。VO又称为前端(Front End)。
- 后端(非线性)优化(Optimization):后端接受不同时刻视觉里程计测量的相机位姿,以及回环检测的信息,对他们进行优化,得到全局一致的轨迹和地图。由于接在VO之后,又称为后端(Back)
- **回环检测(Loop Closure Detection):**回环检测判断机器人是否到达过先前的位置,如果检测到回环,它会把信息提供给后端进行处理。
- **建图(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)对其分类。
- 稀疏地图(Sparse)进行了一定程度的抽象,并不需要表达全部的物体。选择一部分具有代表意义的东西,称之为路标(Landmark),那么一张稀疏地图就是由路标组成的地图,而不是路标的部分就可以忽略。
- 稠密地图(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
- 首先安装g++,也可以用clang,但这里没有用,比g++好一点
sudo apt-get install g++
- 创建并打开一个文件main.cpp
touch main.cpp
vim main.cpp
- 然后写入代码,如下:
#include <iostream>
using namespace std;
int main(int argc,char argv)
{
cout << "Hello SLAM!" << endl;
return 0;
}
- 编译
g++ main.cpp
g++ -o main main.cpp
会生成a.out文件(默认命令,指定main),其具有执行权限(终端里面颜色不同,深色)
- 运行
bupo@bupo-vpc:~/shenlan/slam14_my/cap1/c_bian_yi$ ./a.out
Hello SLAM!
6.3 使用cmake
- 更详细的使用方法,可以看文章【Cmake】【Cmake实践】【cmake的使用学习记录】
- 安装cmake
sudo apt-get install cmake
- 创建CMakeLists.txt
touch CMakeLists.txt
- CMakeLists.txt中写入内容,如下:
#声明要求的cmake最低版本
cmake_minimum_required(VERSION 2.8)
# 声明一个cmake工程
project(HelloSLAM)
# 添加一个可执行程序
# 语法:add_executable(程序名 源代码文件)
add_executable(helloSLAM main.cpp)
- 编译
- 进行编译,并指定所生成中间文件的位置为当前目录下,其中MakeFile最重要,但不必修改它;cmake处理了工程文件之间的关系
cmake .
- 编译生成可执行文件helloSLAM,实际上调用了g++来编译程序
make
- 运行
./helloSLAM
Hello SLAM!
- 通常为了优化,我们会把中间文件放进build文件夹中,则上述过程就变味了从第四步开始为4.
mkdir build
cd build
cmake ..
make
6.4 使用库
- 只有带main函数的文件会生成可执行文件
- 而库(library),是被打包为一个东西,供其他程序调用。
6.4.1 编写自己的库(纯学习,无用处)
- 下面演示如何自己编写一个库
- 创建主文件夹
mkdir hello_ku
cd hello_ku
mdkir src
- 创建libHelloSLAM.cpp
cd src
touch libHelloSLAM.cpp
内容如下:
//这是一个库文件
#include <iostream>
using namespace std;
void printHello()
{
cout << "Hello SLAM !"<< endl;
}
- 创建CMakeLists.txt文件
touch CMakeLists.txt
内容如下:
#静态库
add_library(hello src/libHelloSLAM.cpp)
#动态库
#add_library(hello_shared SHARED src/libHelloSLAM.cpp)
静态库:静态库以.a作为后缀名,每次被调用都会生成一个副本
共享库:共享库以.so结尾,只有一个副本,更省空间
- 编译库
mkdir build
cd build
cmake ..
make
- 为了更好的使用,提供头文件libHelloSLAM.h
cd src
touch libHelloSLAM.h
内容如下:
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
//上面的宏定义是为了防止重复引用这个头文件而引起的重定义错>误
//打印一句hello的函数
void printHello();
#endif
6.4.2 调用自己的库(在工程include中的,不是下载到系统中的)
- 创建文件夹
mkdir hello_diaoyong_ku
cd hello_diaoyong_ku
mkdir src
- 创建main文件
cd src
mkdir useHello.cpp
内容如下:
的printHello()函数
int main(int argc,char ** argv)
{
printHello();
return 0;
}
- 在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
- 创建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)
- 编译
mkdir build
cd build
cmake ..
make
- 运行
cd build
./useHello