这一讲主要介绍视觉SLAM的结构,并完成第一个SLAM程序:HelloSLAM。
目录
2.1 小萝卜的例子
单目相机
双目相机
深度相机
2.2 经典视觉SLAM框架
2.3 SLAM问题的数学表述
2.4 编程实践
Hello SLAM
使用cmake
使用库
【高翔】视觉SLAM十四讲
这一节高翔博士首先介绍了自己组装的“小萝卜”机器人,小萝卜要想具有自主运动的能力,首先要知道自身的姿态,然后是了解外界的环境。这两类问题都需要使用传感器,视觉SLAM中常用的传感器是相机,根据相机的工作工作方式不同,可以分为三类:单目相机(Monocular)、双目相机(Stereo)、深度相机(RGB-D)。
单目相机结构简单,成本较低。本质上是拍照时的场景在相机的成像平面上留下一个投影,以二维的形式记录了三维的世界。
单目SLAM估计的轨迹和地图,将与真实的轨迹’地图,相差一个因子,也就是所谓的尺度。由于单目SLAM无法仅凭图像确定这个真实尺寸,所以又称为尺度不确定性。 本质原因是通过单张图像无法确定深度,为了得到这个深度,人们开始使用双目相机和深度相机。
双目相机由两个单目相机组成,但这两个相机之间的距离(称为基线)是已知的。我们通过这个基线来估计每个像素的空间位置,基线距离越大,能够测量到的就越远,双目与多目的缺点是配置与标定均较为复杂,其深度量程和精度受双目的基线与分辨率的限制,而且视觉计算非常消耗计算资源,需要使用GPU和FPGA设备加速后,才能实时输出整张图像的距离信息。因此在现有的条件下,计算量是双目的主要问题之一。
深度相机又称RGB-D相机,它最大的特点是可以通过红外结构光或Time-of-Flight(ToF)原理,像激光传感器那样,通过主动向物体发射光并接收返回的光,测出物体离相机的距离。
目前常用的RGB-D相机还存在测量范围窄、噪声大、视野小、易受日光干扰、无法测量透射材质等诸多问题,在SLAM方面,主要用于室内。
下图为视觉SLAM的经典框架,视觉SLAM流程分为如下步骤:
- 传感器信息读取:在视觉SLAM中主要为相机图像信息的读取和预处理。
- 前端视觉里程计:其任务为估算相邻图像间相机的运动,以及局部地图的样子。
- 后端非线性优化: 后端接受不同时刻视觉里程计测量的相机位姿,以及回环检测的信息,对它们进行优化,得到全局一致的轨迹和地图。
- 回环检测:判断机器人是否到达过先前的位置,如果检测到回环,会把信息提供给后端进行处理。
- 建图:根据估计的轨迹,建立与任务要求对应的地图。
关于这几个模块,在后面的张杰会详细介绍,这里先了解即可。
这里使用数学语言来描述SLAM的过程,SLAM过程可总结为两个方程:
上式为运动方程,其中为时刻的位置,为传感器的读书,为过程中加入的噪声,方程含义为:根据机器人上一时刻的位置和传感器当前的输入来判断下一时刻机器人的位置。与运动方程相对应,还有一个观测方程:
其中是观测噪声,为机器人在位置上看到的路标,方程描述的是:根据机器人在位置上看到的路标时,产生的观测数据。
这两个方程描述了最基本的SLAM问题:当知道运动测量的读数,以及传感器的读数时,如何求解定位问题(估计)和建图问题(估计),这样就把SLAM问题建模成了一个状态估计问题?
像任何教科书一样,这里从最基本的程序开始,这里使用的是C++代码,这个程序很简单,不多解释了:
#include
using namespace std;
int main(int argc, char **argv) {
cout << "Hello SLAM!" << endl;
return 0;
}
在终端执行以下命令来编译,生成一个可执行文件:
g++ hellSLAM.cpp
生面的程序只有一个文件,使用g++可能比较方便,但是当文件越来越多时,就不那么方便了,因为这时输入的编译命令会越来越长。目前,工程上cmake是使用最广泛的,可以很方便的生成一个makefile文件,然后再使用make命令来编译整个工程。我们先创建一个CMakeLists.txt文件,内容如下:
# 声明要求的 cmake 最低版本
cmake_minimum_required(VERSION 2.8)
# 声明一个 cmake 工程
project(HelloSLAM)
# 添加一个可执行程序
# 语法:add_executable( 程序名 源代码文件 )
add_executable(helloSLAM helloSLAM.cpp)
在终端使用如下命令调用camke对该工程进行cmake编译,下面的点表示在当前目录下进行cmake。
cmake .
现在可以使用make编译,会看到生成可执行程序helloSLAM
make
下面演示如何生成一个库,源文件如下:
//这是一个库文件
#include
using namespace std;
void printHello() {
cout << "Hello SLAM" << endl;
}
在CMakeLists.txt文件中添加如下内容,生成hello库,然后和上面一样,使用cmake编译工程,不过这时生成的是静态库libhello.a。
# 添加hello库
add_library(hello libHelloSLAM.cpp)
下面我们生成动态库libhello_shared.so。
# 共享库
add_library(hello_shared SHARED libHelloSLAM.cpp)
库文件只是编译好的二进制文件,如果想使用它,还需要对应的头文件,头文件如下:
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
// 上面的宏定义是为了防止重复引用这个头文件而引起的重定义错误
// 打印一句hello的函数
void printHello();
#endif
最后写一个新文件来使用这个库:
#include "libHelloSLAM.h"
// 使用 libHelloSLAM.h 中的 printHello() 函数
int main(int argc, char **argv) {
printHello();
return 0;
}
在CMakeLists.txt文件中添加以下内容,链接刚才生成的动态库:
# 添加可执行程序调用hello库中函数
add_executable(useHello useHello.cpp)
# 将库文件链接到可执行程序上
target_link_libraries(useHello hello_shared)
最后,再编译即可生成可执行文件。