
#百人创作先锋团#第2讲 初识slam 原创
第2讲 初识slam
2.1 引子:小萝卜的例子
实现自主移动需要知道两件事情:
- 自身的状态(位置)
- 外在的环境(地图)
传感器分类:
-
携带于机器人本体上:轮式编码器、相机、激光
-
安装于环境中:导轨、二维码标志
相机分类:
- 单目相机:
- 优点:结构简单,成本低
- 缺点:平移之后才能确定深度(或距离),以及无法确定真实尺度,即尺度不确定性
- 双目相机:
- 优点:距离估计是比较左右眼的图像获得的,并不依赖其他传感设备;室内室外都可应用
- 缺点:配置与标定均较为复杂,其深度量程和精度受双目的基线与分辨率所限,而且视差的计算非常消耗计算资源,需要使用GPU和FPGA设备加速后,才能实时输出整张图像的距离信息。
- 深度相机
- 利用红外结构光或Time-of-Flight(ToF)原理,通过主动向物体发射并接收返回的光,测出物体与相机之间的距离。通过物理的测量手段计算距离,相对于双目相机可节省大量的计算。
- 缺点:存在测量范围窄、噪声大、视野小、易受日光干扰、无法测量投射材质等诸多问题。
- 应用场景:主要是室内,室外较难应用
<br><br>
2.2 经典视觉slam框架
-
传感器信息读取。主要为相机图像信息的读取和预处理。
-
视觉里程计(Visual Odometry,VO,前端)。任务是估算相邻图像间相机的运动,以及局部地图的样子。
-
后端优化(Optimization,后端)。后端接受不同时刻视觉里程计测量的相机位姿,以及回环检测的信息,对它们进行优化,得到全局一致的轨迹和地图。
-
回环检测(Loop Closing)。回环检测判断机器人是否到达先前的位置。如果检测到回环,它会把信息提供给后端进行处理。
-
建图(Mapping)。根据估计的轨迹,建立与任务要求对应的地图。
<br>
2.2.1 视觉里程计
视觉里程计关心的是相邻图像之间的相机运动,最简单的情况是两张图像之间的运动关系。
VO能够通过相邻帧间的图像估计相机运动,并恢复场景的空间结构。
假定我们已有一个视觉里程计,估计了两张图像间的相机运动。那么,只要把相邻时刻的运动串起来,就构成了机器人的运动轨迹,从而解决了定位问题。另一方面,我们根据每个时刻的相机位置,计算出各像素对应的空间点的位置,就得到了地图。
存在问题:累积漂移
解决方案:回环检测和后端优化
<br>
2.2.2 后端优化
前端给后端提供待优化的数据,以及这些数据的初始值。
而后端负责整体的优化过程,它往往面对的只有数据。
后端主要是滤波和非线性优化算法。
<br>
2.2.3 回环检测
主要解决位置估计随时间漂移的问题
可以判断图像间的相似性来完成回环检测
消除累积误差,得到全局一致的轨迹和地图。
<br>
2.2.4 建图
指构建地图的过程
分类:
-
度量地图(Metric Map)
- 稀疏(Sparse):由路标组成的地图
- 稠密(Dense):着重于建模所有能看到的东西。用于导航
-
拓扑地图(Topological Map)
- 强调地图元素之间的关系;由结点和边组成,只考虑结点间的连通性
<br>
2.3 SLAM问题的数学描述
运动方程和观测方程
<br>
<br>
2.4 实践:编程基础
2.4.1 安装Linux操作系统
安装git
sudo apt install git
克隆slam14讲源代码,这里连接不上就多试几遍
git clone https://github.com/gaoxiang12/slambook
<br>
下面我把每小节用到的代码贴上去,当前小节没用到的就不贴
2.4.2 Hello SLAM
slambook/ch2/2.4.2/helloSLAM.cpp
#include <iostream>
using namespace std;
int main( int argc, char** argv )
{
cout<<"Hello SLAM!"<<endl;
return 0;
}
终端如下:
g++ helloSLAM.cpp
./a.out
<br>
2.4.3 使用cmake
安装cmake
sudo apt install cmake
<br>
slambook/ch2/2.4.3/helloSLAM.cpp
#include <iostream>
using namespace std;
int main( int argc, char** argv )
{
cout << "Hello SLAM!" << endl;
return 0;
}
slambook/ch2/2.4.3/CMakeLists.txt
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )
# 声明一个 cmake 工程
project( HelloSLAM )
# 添加一个可执行程序
# 语法:add_executable( 程序名 源代码文件 )
add_executable( helloSLAM helloSLAM.cpp )
编译
mkdir build
cd build
cmake ..
make
运行
./helloSLAM
<br>
2.4.4 使用库
静态库:以.a
作为后缀名
slambook/ch2/2.4.4/helloSLAM.cpp
#include <iostream>
using namespace std;
int main( int argc, char** argv )
{
cout<<"Hello SLAM!"<<endl;
return 0;
}
slambook/ch2/2.4.4/libHelloSLAM.cpp
#include "libHelloSLAM.h"
// 使用 libHelloSLAM.h 中的 printHello() 函数
int main( int argc, char** argv )
{
printHello();
return 0;
}
slambook/ch2/2.4.4/CMakeLists.txt
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )
# 声明一个 cmake 工程
project( HelloSLAM )
# 添加一个可执行程序
# 语法:add_executable( 程序名 源代码文件 )
add_executable( helloSLAM helloSLAM.cpp )
# 添加一个库
add_library( hello libHelloSLAM.cpp )
最后生成libhello.a文件
<br>
共享库:以.so
作为后缀名
在上面静态库代码基础上修改了CMakeLists.txt
slambook/ch2/2.4.4_2/CMakeLists.txt
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )
# 声明一个 cmake 工程
project( HelloSLAM )
# 添加一个可执行程序
# 语法:add_executable( 程序名 源代码文件 )
add_executable( helloSLAM helloSLAM.cpp )
# 共享库
add_library( hello_shared SHARED libHelloSLAM.cpp ) #多了关键字
最后生成libhello_shared.so文件
<br>
使用共享库
在上面代码基础上添加了libHelloSLAM.h
slambook/ch2/2.4.4_3/libHelloSLAM.h
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
// 上面的宏定义是为了防止重复引用这个头文件而引起的重定义错误
void printHello();
#endif
并修改了CMakeLists.txt
slambook/ch2/2.4.4_3/CMakeLists.txt
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )
# 声明一个 cmake 工程
project( HelloSLAM )
# 设置编译模式
set( CMAKE_BUILD_TYPE "Debug" )
# 共享库
add_library( hello_shared SHARED libHelloSLAM.cpp )
add_executable( useHello useHello.cpp )
# 将库文件链接到可执行程序上
target_link_libraries( useHello hello_shared )
