1.前言
经过漫长的SLAM系列文章更新,视觉SLAM基础入门与学习部分进入最后一章,这一章,我会总结一下之前讲过的各个部分的基础知识,并带领大家完成一个完整SLAM系统的设计并展望一下未来的研究方向。
首先,依然带给大家来自百度百科关于SLAM的严格定义:
> SLAM (simultaneous localization and mapping),也称为CML (Concurrent Mapping and Localization), 即时定位与地图构建,或并发建图与定位。问题可以描述为:将一个机器人放入未知环境中的未知位置,是否有办法让机器人一边移动一边逐步描绘出此环境完全的地图,所谓完全的地图(a consistent map)是指不受障碍行进到房间可进入的每个角落。
依据大多数的研究定义,通常把SLAM分为4大部分:
- 前端估计
- 后端优化
- 回环检测
- 地图重建
- 前端估计,又称,前端跟踪、帧间估计等;
- 后端优化,又称,后端估计等;
- 回环检测,又称,重定位、场景识别等;
- 地图重建,又称,地图构建等。
2.系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x7scL8to-1590474971425)(https://b02.ickimg.com/202005/582f29f930de257ce460cb0432b6afe8.png)]
一个完整的SLAM系统由以上这4部分组成,其中:前端估计,我们从传感器、特征点、特征提取、特征匹配、重投影、李群李代数、旋转矩阵和平移矩阵等部分进行了讲解;后端优化,我们从BA优化、最小二乘法、梯度法、牛顿法、卡尔曼滤波等部分进行了讲解说明;回环检测,我们从图优化、词袋法、字典法等部分进行了讲解;地图重建、我们从位姿地图、八叉树地图、单目建图等部分进行了讲解。
SLAM系统复杂、内容众多,学习门槛较大、难度较高。
希望这些讲解可以为大家打开SLAM的大门。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PfblQuxY-1590474971432)(https://b05.ickimg.com/202005/970f3ebc331653e24b8fc597759ba565.png)]
3.实现
这里我会带领大家通过双目摄像头实现一个完整的SLAM系统。
首先,如下为相关的设计参数、接口等:
namespace myslam {
// forward declare
struct MapPoint;
struct Feature;
/@@**
* 帧
* 每一帧分配独立id,关键帧分配关键帧ID
*/
struct Frame {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
typedef std::shared_ptr Ptr;
unsigned long id_ = 0; // id of this frame
unsigned long keyframe_id_ = 0; // id of key frame
bool is_keyframe_ = false; // 是否为关键帧
double time_stamp_; // 时间戳,暂不使用
SE3 pose_; // Tcw 形式Pose
std::mutex pose_mutex_; // Pose数据锁
cv::Mat left_img_, right_img_; // stereo images
// extracted features in left image
std::vector<std::shared_ptr<feature>> features_left_;
// corresponding features in right image, set to nullptr if no corresponding
std::vector<std::shared_ptr<feature>> features_right_;
public: // data members
Frame() {}
Frame(long id, double time_stamp, const SE3 &pose, const Mat &left,
const Mat &right);
// set and get pose, thread safe
SE3 Pose() {
std::unique_lock<std::mutex> lck(pose_mutex_);
return pose_;
}
void SetPose(const SE3 &pose) {
std::unique_lock<std::mutex> lck(pose_mutex_);
pose_ = pose;
}
/// 设置关键帧并分配并键帧id
void SetKeyFrame();
/// 工厂构建模式,分配id
static std::shared_ptr CreateFrame();
};
} // namespace myslam
#endif // MYSLAM_FRAME_H
如下,是特征提取的部分以及关于特征点的部分:
namespace myslam {
struct Frame;
struct MapPoint;
/@@**
* 2D 特征点
* 在三角化之后会被关联一个地图点
*/
struct Feature {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
typedef std::shared_ptr<feature> Ptr;
std::weak_ptr frame_; // 持有该feature的frame
cv::KeyPoint position_; // 2D提取位置
std::weak_ptr<mappoint> map_point_; // 关联地图点
bool is_outlier_ = false; // 是否为异常点
bool is_on_left_image_ = true; // 标识是否提在左图,false为右图
public:
Feature() {}
Feature(std::shared_ptr frame, const cv::KeyPoint &kp)
: frame_(frame), position_(kp) {}
};
} // namespace myslam
#endif // MYSLAM_FEATURE_H
其次,如下是关于图像三维点的部分:
/@@**
* 路标点类
* 特征点在三角化之后形成路标点
*/
struct MapPoint {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
typedef std::shared_ptr<mappoint> Ptr;
unsigned long id_ = 0; // ID
bool is_outlier_ = false;
Vec3 pos_ = Vec3::Zero(); // Position in world
std::mutex data_mutex_;
int observed_times_ = 0; // being observed by feature matching algo.
std::list<std::weak_ptr<feature>> observations_;
MapPoint() {}
MapPoint(long id, Vec3 position);
Vec3 Pos() {
std::unique_lock<std::mutex> lck(data_mutex_);
return pos_;
}
void SetPos(const Vec3 &pos) {
std::unique_lock<std::mutex> lck(data_mutex_);
pos_ = pos;
};
void AddObservation(std::shared_ptr<feature> feature) {
std::unique_lock<std::mutex> lck(data_mutex_);
observations_.push_back(feature);
observed_times_++;
}
void RemoveObservation(std::shared_ptr<feature> feat);
std::list<std::weak_ptr<feature>> GetObs() {
std::unique_lock<std::mutex> lck(data_mutex_);
return observations_;
}
// factory function
static MapPoint::Ptr CreateNewMappoint();
};
} // namespace myslam
#endif // MYSLAM_MAPPOINT_H
如下,是对所有地图相关的代码:
class Map {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
typedef std::shared_ptr<map> Ptr;
typedef std::unordered_map<unsigned long,="" mappoint::ptr=""> LandmarksType;
typedef std::unordered_map<unsigned long,="" frame::ptr=""> KeyframesType;
Map() {}
/// 增加一个关键帧
void InsertKeyFrame(Frame::Ptr frame);
/// 增加一个地图顶点
void InsertMapPoint(MapPoint::Ptr map_point);
/// 获取所有地图点
LandmarksType GetAllMapPoints() {
std::unique_lock<std::mutex> lck(data_mutex_);
return landmarks_;
}
/// 获取所有关键帧
KeyframesType GetAllKeyFrames() {
std::unique_lock<std::mutex> lck(data_mutex_);
return keyframes_;
}
/// 获取激活地图点
LandmarksType GetActiveMapPoints() {
std::unique_lock<std::mutex> lck(data_mutex_);
return active_landmarks_;
}
/// 获取激活关键帧
KeyframesType GetActiveKeyFrames() {
std::unique_lock<std::mutex> lck(data_mutex_);
return active_keyframes_;
}
/// 清理map中观测数量为零的点
void CleanMap();
private:
// 将旧的关键帧置为不活跃状态
void RemoveOldKeyframe();
std::mutex data_mutex_;
LandmarksType landmarks_; // all landmarks
LandmarksType active_landmarks_; // active landmarks
KeyframesType keyframes_; // all key-frames
KeyframesType active_keyframes_; // all key-frames
Frame::Ptr current_frame_ = nullptr;
// settings
int num_active_keyframes_ = 7; // 激活的关键帧数量
};
} // namespace myslam
简单介绍一些逻辑:
双目的立体匹配用来提取三维特征点;
左通道用来估计光流,右通道连续帧间不使用;
光流估计的特征点较少的时候,为关键帧。
4.未来
视觉slam未来的发展方向主要集中在传感器的多传感器融合、机器人的庞大知识、,云计算机器人、slam和深度学习的相关融合方向上。其中多传感器融合的数据处理是目前SLAM当中常用的一种传感器数据融合方式,因为传统的slam传感器有于传感器的限制,导致其数据维度和感知并不是很强,通过多传感器融合可以达到强大的环境感知,提高机器人的感知能力。
对于机器人的知识库,我们可以让机器人完成更多的内容,通过slam的构建,对环境进行感知,构建惊人庞大知识库。其次,由于影响及机器人实时运作的主要关系是机器人的控制系统,但是当我们将机器人的控制放在云端服务器的时候,这样我们就可以使机器人的实时性更加良好,其中就包含了云计算机器人。
随着云技术的发展,云计算机器人将成一个热门的研究方向。除此之外,slam和深度学习的融合也成为发展方向,因为深度学习在传统的图像领域表现良好,但是在slam应用当中依然有很多缺陷和很多不足,所以现在这也是一个很大的热门方向。
5.总结
首先感谢大家阅读SLAM这一系列的文章,这一个系列目标就是SLAM的基础讲解与入门,当然,由于时间较短和内容众多,文章还是有很多的不足,希望大家可以在留言区批评指正、多多交流。
除此之外呢,首先感谢高翔的《视觉slam14讲》,作为视觉slam的在国内为数不多的基础教材,为很多slam基础研究者提供了基础学习的书籍。其次,要感谢很多开源社区提供一些代码以供大家学习。希望大家在后续学习当中可以将自己的研究成果和代码开源出来,以供大家学习,共同促进SLAM的发展与进步。
> 参考资料:《视觉SLAM十四讲》