02 特征点提取器 ORBextractor

文章目录

    • 02 特征点提取器 ORBextractor
      • 2.0 基础知识
        • 2.0.1 图像金字塔
        • 2.0.2 ORB 特征点的关键点和描述子
      • 2.1 构造函数:ORBextractor()
      • 2.2 构建图像金字塔 ComputePyramid()
      • 2.3 提取特征点并筛选 ComputeKeyPointsOctTree()
      • 2.4 筛选特征点 DistributeOctTree()
      • 2.5 计算特征点方向 computeOrientation()
      • 2.6 计算特征点描述子 computeOrbDescriptor()
      • 2.7 ORBextractor 类总结
        • 2.7.1 主要流程
        • 2.7.2 与其他类的关系

02 特征点提取器 ORBextractor

2.0 基础知识

2.0.1 图像金字塔

(1)尺度不变性:一幅图片中的某个物体的大小永远是那么大(设置一个标准尺度来度量),而不是因为它所在图片的放大缩小而改变。这就是尺度不变性。

(2)图像金字塔
02 特征点提取器 ORBextractor_第1张图片

将原始图像按照 1:1.2 的比例依次缩小,得到 n 幅图像,形如金字塔。观察这 n 幅图像,我们似乎离场景越来越远,这样就用一幅图像制造出了深度变化的效果。

在 SLAM 中,我们对这 n 张图像分别提取特征点,就相当于得到了不同距离的特征,涵盖了更多的尺度。试想一下,假设第一次我们在 10m 处获取一幅图像,经过图像金字塔,相当于得到了 10 1.2 \frac{10}{1.2} 1.210 10 1. 2 2 \frac{10}{1.2^2} 1.2210 10 1. 2 3 \frac{10}{1.2^3} 1.2310… 等处共 n 个不同距离的图像;当我们下次再处于不同距离拍摄该处场景时,就有更多的图像与特征点进行匹配。这样就解决了尺度导致的特征点匹配问题。

参考:https://blog.csdn.net/u011341856/article/details/103707313

2.0.2 ORB 特征点的关键点和描述子

ORB 的关键点是在 FAST 关键点基础上进行了改进,主要是增加了特征点的主方向,称之为 Oriented FAST。

描述子是在 BRIEF 描述子基础上加入了上述方向信息,称之为 Rotated BRIEF。

2.1 构造函数:ORBextractor()

构造函数原型

ORBextractor::ORBextractor(int _nfeatures, float _scaleFactor, int _nlevels, int _iniThFAST, int _minThFAST)

(1)从 yaml 文件中读取参数(以 KITTI00-02.yaml 为例)

成员变量 意义 在yaml中变量名
int _nfeatures 期望提取的特征点个数 ORBextractor.nFeatures 2000
float _scaleFactor 金字塔相邻层级缩放因子 ORBextractor.scaleFactor 1.2
int _nlevels 金字塔层数 ORBextractor.nLevels 8
int _iniThFAST 提取 FAST 特征点的默认阈值 ORBextractor.iniThFAST 20
int _minThFAST 如果使用默认阈值提取不到特征点则使用最小阈值再次提取 ORBextractor.minThFAST 7

根据上述变量的值计算出下述成员变量:

变量 意义
std::vector mvScaleFactor 各层级缩放系数 {1, 1.2, 1.44, 1.728, 2.074, 2.488, 2.986, 3.583
std::vector mvInvScaleFactor 各层级缩放系数的倒数 {1, 0.833, 0.694, 0.579, 0.482, 0.402, 0.335, 0.2791}
std::vector mvLevelSigma2 各层级缩放系数的平方 {1, 1.44, 2.074, 2.986, 4.300, 6.190, 8.916, 12.838}
std::vector mvInvLevelSigma2 各层级缩放系数的平方的倒数 {1, 0.694, 0.482, 0.335, 0.233, 0.162, 0.112, 0.078}
std::vector mnFeaturesPerLevel 每一层期望提取的特征点个数(正比于图层边长,总和为 nfeatures {122, 146, 174, 210, 252, 302, 362, 432}

(2)初始化用于计算描述子的 pattern 变量,也就是用于计算描述子的 256 对坐标

static int bit_pattern_31_[256*4] =
{
    8,-3, 9,5/*mean (0), correlation (0)*/,
    4,2, 7,-12/*mean (1.12461e-05), correlation (0.0437584)*/,
    -11,9, -8,2/*mean (3.37382e-05), correlation (0.0617409)*/,
    ...
}

共 256 行,每一行表示一对坐标点,如第一行为 (8, -3)(9, 5)

(3) 在提取 Oriented FAST 关键点后,还需要计算每个点的描述子。即以关键点为圆心,在半径为 16 的圆的范围内,计算特征点主方向和描述子。

02 特征点提取器 ORBextractor_第2张图片

(注:图片中半径为 8,仅作示意)

成员变量 std::vector umax 中存储的是逼近圆的第一象限内 1 4 \frac{1}{4} 41 圆周上每个 v 坐标对应的 u 坐标。为保证严格对称性,先计算下 45° 圆周上点的坐标,再根据对称性补全上 45° 圆周上点的坐标。

int vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1); 	// 45°射线与圆周交点的纵坐标
int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);			// 45°射线与圆周交点的纵坐标

// 先计算下半45度的umax
for (int v = 0; v <= vmax; ++v) {
	umax[v] = cvRound(sqrt(15 * 15 - v * v));	
}

// 根据对称性补出上半45度的umax
for (int v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v) {
    while (umax[v0] == umax[v0 + 1])
        ++v0;
    umax[v] = v0;
    ++v0;
}

2.2 构建图像金字塔 ComputePyramid()

变量 访问控制 意义
std::vector mvImagePyramid public 存储图像金字塔每层的图像
const int EDGE_THRESHOLD 全局变量 为计算描述子和提取特征点补的 padding 厚度

函数原型

void ORBextractor::ComputePyramid(cv::Mat image)
{
	//开始遍历所有的图层,levels是yaml文件里面的
    for (int level = 0; level < nlevels; ++level)
    {
		//获取本层图像的缩放系数,mvInvScaleFactor[level]是从orbextrator得到的
        float scale = mvInvScaleFactor[level];
		//计算本层图像的像素尺寸大小
        Size sz(cvRound((float)image.cols*scale), cvRound((float)image.rows*scale));
		//全尺寸图像。包括无效图像区域的大小。将图像进行“补边”,EDGE_THRESHOLD区域外的图像不进行FAST角点检测
        Size wholeSize(sz.width + EDGE_THRESHOLD*2, sz.height + EDGE_THRESHOLD*2);
		// temp是扩展了边界的图像,是一个构造函数,拷贝了wholeSize的图像
        Mat temp(wholeSize, image.type()), masktemp;
        // mvImagePyramid 刚开始时是个...空的vector
		// 将扩充后的图像拷贝给mvImagePyramid容器
        mvImagePyramid[level] = temp(Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height));

        // Compute the resized image
		//计算第0层以上resize后的图像
        if( level != 0 )
        {
			//将上一层金字塔图像根据前文设定sz缩放到当前层级
            resize(mvImagePyramid[level-1],	//输入图像
				   mvImagePyramid[level], 	//输出图像
				   sz, 						//输出图像的尺寸
				   0, 						//水平方向上的缩放系数,留0表示自动计算
				   0,  						//垂直方向上的缩放系数,留0表示自动计算
				   cv::INTER_LINEAR);		//图像缩放的差值算法类型,这里的是线性插值算法


			//把源图像拷贝到目的图像的中央,四面填充指定的像素。图片如果已经拷贝到中间,只填充边界
			//这样做是为了能够正确提取边界的FAST角点
			//EDGE_THRESHOLD指的这个边界的宽度,由于这个边界之外的像素不是原图像素而是算法生成出来的,所以不能够在EDGE_THRESHOLD之外提取特征点			
            copyMakeBorder(mvImagePyramid[level], 					//源图像
						   temp, 									//目标图像(此时其实就已经有大了一圈的尺寸了)
						   EDGE_THRESHOLD, EDGE_THRESHOLD, 			//top & bottom 需要扩展的border大小
						   EDGE_THRESHOLD, EDGE_THRESHOLD,			//left & right 需要扩展的border大小
                           BORDER_REFLECT_101+BORDER_ISOLATED);     //扩充方式,opencv给出的解释:						
        }
        else
        {
			//对于第0层未缩放图像,直接将图像深拷贝到temp的中间,并且对其周围进行边界扩展。此时temp就是对原图扩展后的图像
            copyMakeBorder(image,			//这里是原图像
						   temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD,
                           BORDER_REFLECT_101);            
        }
    }
}

包括两步:

  • 将图像缩放到 mvInvScaleFactor 对应尺寸;

  • 在图像四周补一圈厚度为 EDGE_THRESHOLD 的 padding(提取 FAST 特征点需要特征点周围半径为 3 的圆域,计算描述子需要特征点周围半径为 16 的圆域),copyMakeBorder() 函数实现。

02 特征点提取器 ORBextractor_第3张图片

  • 深灰色 为缩放后的原始图像;

  • 包含绿色边界在内的矩形 用于提取 FAST 特征点;

  • 包含浅灰色边界在内的整个矩形 用于计算 ORB 描述子。

为什么要扩充图像边界呢?

利用 FAST 算法在提取特征点时,图像边缘的特征点半径为3的圆无法取到(边界外无像素点),为了解决此问题,我们对图像边界进行填充。

参考:https://www.pudn.com/news/62f50f18f97302478e3581fc.html

2.3 提取特征点并筛选 ComputeKeyPointsOctTree()

02 特征点提取器 ORBextractor_第4张图片

我们希望 特征点均匀地分布在图像的所有部分。所以在提取时会将图片分成 30*30(单位像素)的一个一个小格子(cell)来提取特征点。并且在提取 FAST 角点时,我们设计了两个阈值 _iniThFAST_minThFAST,这样在每个 cell 中,就可以根据实际情况进行调整,尽可能保证每个 cell 中都能提取到特征点。
02 特征点提取器 ORBextractor_第5张图片
代码实现主要有两步:

  • 划分 cell,先用默认阈值提取特征点,如果找不到,就降低阈值,用 _minThFAST 搜索特征点;

  • 对得到的所有特征点进行八叉树筛选,若某区域内特征点数目过于密集,则只取其中响应值最大的那个。
    02 特征点提取器 ORBextractor_第6张图片

2.4 筛选特征点 DistributeOctTree()

筛选完特征点后,还是可能出现某些 cell 中特征点密集,某些 cell 中稀疏甚至没有特征点。因此采用类似八叉树的方法重新分发角点,使一个区域只有一个特征点(注意,这里的区域是重新划分的,不是之前的 cell)。
02 特征点提取器 ORBextractor_第7张图片

(没有特征点或只有一个特征点的区域不再分裂。)

2.5 计算特征点方向 computeOrientation()

使用特征点周围半径为 19 的圆的重心方向作为特征点方向。

M 00 = ∑ X = − R R ∑ Y = − R R I ( x , y ) M 10 = ∑ X = − R R ∑ X = − R R x I ( x , y ) M 01 = ∑ X = − R R ∑ X = − R R y I ( x , y ) Q X = = M 10 M 00 , Q Y = M 01 M 00 C = ( m 10 m 00 , m 00 m 00 ) θ = atan ⁡ 2 ( m 01 , m 10 ) \begin{aligned} & M_{00}=\sum_{X=-R}^R \sum_{Y=-R}^R I(x, y) \\ & M_{10}=\sum_{X=-R}^R \sum_{X=-R}^R x I(x, y) \\ & M_{01}=\sum_{X=-R}^R \sum_{X=-R}^R y I(x, y) \\ & Q_{X=}=\frac{M_{10}}{M_{00}}, Q_Y=\frac{M_{01}}{M_{00}} \\ & C=\left(\frac{m_{10}}{m_{00}}, \frac{m_{00}}{m_{00}}\right) \\ & \theta=\operatorname{atan} 2\left(m_{01}, m_{10}\right) \end{aligned} M00=X=RRY=RRI(x,y)M10=X=RRX=RRxI(x,y)M01=X=RRX=RRyI(x,y)QX==M00M10,QY=M00M01C=(m00m10,m00m00)θ=atan2(m01,m10)
c x = ∑ x = − R ∑ y = − R R x I ( x , y ) ⏞ m 10 ∑ x = − R R ∑ y = − R R I ( x , y ) ⏟ m 00 , c y = ∑ x = − R R ∑ y = − R R y I ( x , y ) ⏞ m 01 ∑ x = − R R ∑ y = − R R I ( x , y ) ⏟ m 00 θ = arctan ⁡ 2 ( c y , c x ) = arctan ⁡ 2 ( m 01 , m 10 ) \begin{aligned} & c_x=\frac{\overbrace{\sum_{x=-R} \sum_{y=-R}^R x I_{(x, y)}}^{m_{10}}}{\underbrace{\sum_{x=-R}^R \sum_{y=-R}^R I_{(x, y)}}_{m_{00}}}, c_y=\frac{\overbrace{\sum_{x=-R}^R \sum_{y=-R}^R y I_{(x, y)}}^{m_{01}}}{\underbrace{\sum_{x=-R}^R \sum_{y=-R}^R I_{(x, y)}}_{m_{00}}} \\ & \theta=\arctan 2\left(c_y, c_x\right)=\arctan 2\left(m_{01}, m_{10}\right) \\ & \end{aligned} cx=m00 x=RRy=RRI(x,y)x=Ry=RRxI(x,y) m10,cy=m00 x=RRy=RRI(x,y)x=RRy=RRyI(x,y) m01θ=arctan2(cy,cx)=arctan2(m01,m10)

2.6 计算特征点描述子 computeOrbDescriptor()

在特征点周围半径为 16 的圆域内选取 256 对点,比较,得到 256 位描述子。
02 特征点提取器 ORBextractor_第8张图片

computeOrientation() 中,我们求出了每个特征点的主方向,因此在计算描述子之前,要先将特征点周围像素旋转到主方向上来。
02 特征点提取器 ORBextractor_第9张图片

2.7 ORBextractor 类总结

2.7.1 主要流程

ORBextractor 类用于 tracking 线程中第一步预处理。

主要流程为
在这里插入图片描述

2.7.2 与其他类的关系

Frame类 中与 ORBextractor 有关的成员变量和函数

成员变量/函数 访问控制 意义
ORBextractor* mpORBextractorLeft public 左目特征点提取器
ORBextractor* mpORBextractorRight public 右目特征点提取器(单目/RGBD时为空指针)
ExtractORB() public 提取特征点,直接调用 mpORBextractorLeftmpORBextractorRight
Frame public Frame类的构造函数,调用 ExtractORB() 提取特征点

每次提取完 ORB 特征点之后,图像金字塔信息就会作废,下一帧图像到来时调用 ComputePyramid() 函数会覆盖掉上一帧的图像金字塔信息;但已经提取到的特征点信息会被保留在 Frame 对象中。所以 ORB-SLAM2 是稀疏重建,每帧图像只会保留最多 nfeatures 个特征点。

你可能感兴趣的:(ORB-SLAM2,ORB-SLAM2,SLAM)