从ORB-SLAM2代码详解02: 特征点提取器ORBextractor_ncepu_Chen的博客-CSDN博客_orbextractor 由流程图可知,不无论是单目相机,还是双目相机和RGBD相机,都需要先提取特征点,这是进入跟踪流程的第一步。
ORBextractor类
ORBextractor()构造函数
ORBextractor
通过构建图像金字塔将输入图片逐级缩放进行存储计算,金字塔层级越高,图片分辨率越低,ORB特征点越大。图像金字塔是对尺度的一种描述,例如,当我们在上一个图像金字塔的上层与下一个图像金字塔的下层匹配到特征点时,说明相机后退了(金字塔层数越大,图片越模糊,距离越远)
构造函数步骤:
1. 初始化图像金字塔相关变量
2. 初始化用于计算描述子的
pattern
变量,pattern
是用于计算描述子的256
对坐标3. 计算一个半径为
16
的圆的近似坐标
构建图像金字塔ComputePyramid()
逐层计算图像金字塔,对于每层图像进行以下两步:
- 图片缩放到
mvInvScaleFactor
对应尺寸.- 在图像外补一圈厚度为
19
的padding
(提取FAST
特征点需要特征点周围半径为3
的圆域,计算ORB
描述子需要特征点周围半径为16
的圆域).
- 深灰色为缩放后的原始图像.
- 包含绿色边界在内的矩形用于提取
FAST
特征点.- 包含浅灰色边界在内的整个矩形用于计算
ORB
描述
void ORBextractor::ComputePyramid(cv::Mat image) {
for (int level = 0; level < nlevels; ++level) {
// 计算缩放+补padding后该层图像的尺寸
float scale = mvInvScaleFactor[level];
Size sz(cvRound((float)image.cols*scale), cvRound((float)image.rows*scale));
Size wholeSize(sz.width + EDGE_THRESHOLD * 2, sz.height + EDGE_THRESHOLD * 2);
Mat temp(wholeSize, image.type());
// 缩放图像并复制到对应图层并补边
mvImagePyramid[level] = temp(Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height));
if( level != 0 ) {
resize(mvImagePyramid[level-1], mvImagePyramid[level], sz, 0, 0, cv::INTER_LINEAR);
copyMakeBorder(mvImagePyramid[level], temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
BORDER_REFLECT_101+BORDER_ISOLATED);
} else {
copyMakeBorder(image, temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
BORDER_REFLECT_101);
}
}
}
提取特征点并进行筛选ComputeKeyPointsOctTree ()
金字塔层数越高,对应图像的分辨率越低,能提取到的特征数就越少。为了均匀分摊特征点数目,按面积比例进行划分。
参考:https://zhuanlan.zhihu.com/p/61738607
为了力求特征点均匀分布在图像的所有部分,使用了两个技巧:
1. 分块搜索,如果某个块特征点响应值比较小,降低分数再搜索
2. 将所有特征点进行四叉树筛选,若某区域内特征点数目过于密集,则只取其中响应值最大的那个。
每个块的大小是30×30,且相邻两块之间会有6个像素的重叠,这是因为提取FAST特征点需要计算周围半径3内的像素点信息,实际产生特征点的区域会比搜索区域小3个像素。
四叉树筛选特征点
计算特征点的方向computeOrientation()
使用特征点周围半径19的圆的重心方向作为特征点的方向,每一个特征点都对应一个主方向。这样做的目的是在计算描述子时,将特征点周围像素旋转到主方向上计算,方便进行特征匹配。