视觉SLAM笔记(48) 局部地图
视觉SLAM中局部地图用于提高定位效率,仅保留相机当前位置附近的特征点以减少计算负担。与全局地图相比,局部地图规模较小且实时性更好。通过关键帧概念进行优化,每隔一定间隔记录相机位姿并更新地图点;同时删除视野外或可见度低的地图点以保持规模合理。优化过程中结合三角测量和匹配技术以提升精度并降低漂移风险。
视觉SLAM笔记(48) 局部地图
- 1. 优化过程中存在的挑战
- 2. 通过提取关键特征点来构建地图
- 3. 地图上的关键点数据类型
- 4. 基于视觉定位的地图数据类型
-
-
4.1. 核心帧位置
- 4.2. 地图优化流程
- 4.3. 特征配准过程
-
4. 结果对比
-
5. 缺点
-
1. 迭代优化的问题
应用迭代优化方法后,在估计结果的质量方面较纯粹基于RANSAC PnP的方法表现出显著提升。然而,在仅利用两两帧间信息的前提下(即仅依赖有限的数据量),所获得的姿态却变得更加精确且稳定。通过改进发现优化策略的重要性。然而,在视觉里程(VO)算法中仍然存在受限因素——即0VO方法仍然受到两个连续帧之间匹配能力的限制影响。一旦视频序列中的某一帧丢失后,则会导致后续的所有帧都无法与前一帧进行匹配。下一步建议将地图引入VO系统中进行验证。
2. 特征点引入地图
将定位至 VO 匹配的特征点放置于地图中,并同时将当前帧与地图中的点进行匹配以估算姿态。
这种方法相较于之前的方法在图中可观察到显著差异。
将定位至 VO 匹配的特征点放置于地图中,并同时将当前帧与地图中的点进行匹配以估算姿态。
这种方法相较于之前的方法在图中可观察到显著差异。

在相邻两个帧之间进行比较时, 仅考虑当前iframe与其前一个参考iframe之间的特征对应关系及运动变换, 并将该iframe更新为新的基准iframe. 在地图视觉 Odometry (VO) 过程中, 每个新 iframe 向地图系统注入动态更新的信息, 包括新增关键点或更新现有关键点的位置记录. 其中的地图上的关键点位置通常基于全局坐标系表示.
进而,在当前帧到达的时候,我们首先进行特征匹配,并同时分析其运动特性。从而实现了对Tcw的精确计算。
这种方法的好处在于能保持实时更新的地图。
无论当前地图是否正确,在某一帧出现错误后仍有可能求得后续各帧的正确位置。
请注意的是,在此之前我们尚未深入探讨SLAM技术的建图问题。
因此这里的地图实际上只是临时的一个概念——各帧特征点被缓存到特定存储空间中形成特征点集合。
2. 地图
地图可分为局部(Local)地图和全局(Global)地图两种,在实际应用中通常分别进行研究与应用
基于名称的概念下,在局部地图中包含了附近区域的关键特征信息:
仅保留与相机位置较近范围内的关键特征点,并舍弃远端或超出视野范围的关键特征点。
这些关键特征点将被用于快速实现定位过程以确定相机位置。
另一方面,在SLAM运行过程中所有特征点的记录下,
显见地更大一些,则主要表征整个环境。
然而,在全局地图上定位会带来相当大的计算负担,
它主要应用于回环检测和地图构建。
在视觉里程计系统中,系统主要侧重于能够直接用于定位的小区域局部地图(若希望使用地图,则需权衡精度与效率)。随着相机运动过程的进行,在地图中记录新增特征点的同时也去除先前多余或重复的特征点。需要注意的是,在决定是否采用地图时要平衡好精度与效率之间的关系。
从效率的角度来看, 采用两两非结构化的VO方法.
此外, 在提升精度的同时构建局部地图, 并对地图进行优化.
局部地图的一件棘手问题在于对其尺寸进行有效的管理。为了确保实时性不受影响,在实际应用中需要将地图的大小控制在合理范围内(避免过于庞大的尺寸可能导致匹配过程耗时过长)。除此之外,在单个帧与地图进行特征匹配时可以采用一些优化措施来提高效率。然而由于这些优化措施涉及较多的技术细节较为复杂,在此不做详细阐述。
3. 地图点类
接下来要实现的是地图点相关功能。该功能位于/include/myslam/mappoint.h文件中。其核心内容主要涉及构造函数与生成函数的实现。
#ifndef MAPPOINT_H
#define MAPPOINT_H
#include "myslam/common_include.h"
namespace myslam
{
class Frame;
class MapPoint
{
public:
typedef shared_ptr<MapPoint> Ptr;
unsigned long id_; // ID
static unsigned long factory_id_;
bool good_; // 是否是好点
Vector3d pos_; // 世界坐标系上的坐标
Vector3d norm_; // 观察方向法线
Mat descriptor_; // 描述符匹配
list<Frame*> observed_frames_; // 观察时间
int matched_times_; // 匹配时间
int visible_times_; // 一帧时间
MapPoint();
MapPoint(
unsigned long id,
const Vector3d& position,
const Vector3d& norm,
Frame* frame = nullptr,
const Mat& descriptor = Mat()
);
inline cv::Point3f getPositionCV() const {
return cv::Point3f(pos_(0, 0), pos_(1, 0), pos_(2, 0));
}
// 建立MapPoint
static MapPoint::Ptr createMapPoint();
static MapPoint::Ptr createMapPoint(
const Vector3d& pos_world,
const Vector3d& norm_,
const Mat& descriptor,
Frame* frame);
};
}
#endif // MAPPOINT_H
AI助手
3. 视觉里程类
主要改动集中在VisualOdometry类中。
由于算法流程发生调整, 对其进行了优化, 包括修复一个关键错误以及增强数据处理能力等改进措施。
在每次循环中需要执行地图增删操作, 并统计每个地图点被观测到的次数等基础运算。
3.1. 关键帧
在提取第一帧的特征点之后,将第一帧的所有特征点全部放入地图中
void VisualOdometry::addKeyFrame()
{
if (map_->keyframes_.empty())
{
// 第一个关键帧,添加所有3d点到地图
for (size_t i = 0; i < keypoints_curr_.size(); i++)
{
double d = curr_->findDepth(keypoints_curr_[i]);
if (d < 0)
continue;
Vector3d p_world = ref_->camera_->pixel2world(
Vector2d(keypoints_curr_[i].pt.x, keypoints_curr_[i].pt.y), curr_->T_c_w_, d
);
Vector3d n = p_world - ref_->getCamCenter();
n.normalize();
MapPoint::Ptr map_point = MapPoint::createMapPoint(
p_world, n, descriptors_curr_.row(i).clone(), curr_.get()
);
map_->insertMapPoint(map_point);
}
}
map_->insertKeyFrame(curr_);
ref_ = curr_;
}
AI助手
为了提升精度,在现有地图的基础上
以下是按照要求进行的同义改写
3.2. 优化地图
在后续的帧中应用OptimizeMap函数对地图进行优化处理,并包括对视野外的点予以删除,在匹配数量缩减时补充新增点等操作
void VisualOdometry::optimizeMap()
{
// 除去几乎看不见的和不可见的点
for (auto iter = map_->map_points_.begin(); iter != map_->map_points_.end(); )
{
if (!curr_->isInFrame(iter->second->pos_))
{
iter = map_->map_points_.erase(iter);
continue;
}
float match_ratio = float(iter->second->matched_times_) / iter->second->visible_times_;
if (match_ratio < map_point_erase_ratio_)
{
iter = map_->map_points_.erase(iter);
continue;
}
double angle = getViewAngle(curr_, iter->second);
if (angle > M_PI / 6.)
{
iter = map_->map_points_.erase(iter);
continue;
}
if (iter->second->good_ == false)
{
// 试着对这个地图点进行三角测量
}
iter++;
}
if (match_2dkp_index_.size() < 100)
addMapPoints();
if (map_->map_points_.size() > 1000)
{
// 地图太大,删除一些
map_point_erase_ratio_ += 0.05;
}
else
map_point_erase_ratio_ = 0.1;
cout << "map points: " << map_->map_points_.size() << endl;
}
AI助手
3.3. 特征匹配
在匹配之前,在地图上提取一些候选位置信息(这些位置信息位于当前视野范围内)。随后将这些候选点与当前帧的特征描述子进行比对。
void VisualOdometry::featureMatching()
{
boost::timer timer;
vector<cv::DMatch> matches;
// 在map中选择候选项
Mat desp_map;
vector<MapPoint::Ptr> candidate;
for (auto& allpoints : map_->map_points_)
{
MapPoint::Ptr& p = allpoints.second;
// 检查p是否在当前帧图像中
if (curr_->isInFrame(p->pos_))
{
// 添加到候选
p->visible_times_++;
candidate.push_back(p);
desp_map.push_back(p->descriptor_);
}
}
matcher_flann_.match(desp_map, descriptors_curr_, matches);
// 选择最佳匹配
float min_dis = std::min_element(
matches.begin(), matches.end(),
[](const cv::DMatch& m1, const cv::DMatch& m2)
{
return m1.distance < m2.distance;
})->distance;
match_3dpts_.clear();
match_2dkp_index_.clear();
for (cv::DMatch& m : matches)
{
if (m.distance < max<float>(min_dis*match_ratio_, 30.0))
{
match_3dpts_.push_back(candidate[m.queryIdx]);
match_2dkp_index_.push_back(m.trainIdx);
}
}
cout << "good matches: " << match_3dpts_.size() << endl;
cout << "match cost time: " << timer.elapsed() << endl;
}
AI助手
4. 结果对比
运行此程序

如果位姿估计正确的话, 它们看起来似乎应当是静止不动的. 如果感觉到某个特征点呈现出不自然的运动轨迹, 那么很可能是因为相机的位姿估计不够精准, 或者该特征点的位置未能被精确捕捉.

5. 缺点
视觉里程计具有能力估计短时间间隔内的相机运动并同时定位出关键点位置。然而这种基于局部的方法存在明显的不足。
一旦丢失后,则有两种处理方式:第一种是相机重新启动后等待新图像的生成,并通过捕获当前图像并将其作为参考帧来与后续的每一帧进行对比分析;第二种则是触发重新初始化整个视觉追踪系统(VO),以便其能够根据新获取的图像数据进行更新
- 轨迹漂移
主要原因在于每一次估计的误差会被积累到下一次估计中,从而造成长时间轨迹不准确
稍微大一点的局部地图能够部分缓解这一问题,但它始终是一个客观存在的挑战
值得注意的是,在一些特定场景下仅需关注短暂运动的情况下
参考:
相关推荐:
视觉SLAM笔记(47) 提升 PnP 的效果
视觉SLAM笔记(46) 基本的 VO 原理与实现
视觉SLAM笔记(45) 搭建 VO 框架及核心内容
视觉SLAM笔记(44) RGB-D 直接法的实际应用
视觉SLAM笔记(43) 直接法的理论基础与改进
