通俗易懂理解ORBSLAM2特征提取模块

一、通俗易懂理解图像金字塔特征点数目、灰度质心圆索引

1.参考资料:

[1] ORBSLAM2 source code

2.主要函数:
//特征点提取器的构造函数

ORBextractor::ORBextractor(int _nfeatures,      //指定要提取的特征点数目
                           float _scaleFactor,  //指定图像金字塔的缩放系数
                           int _nlevels,        //指定图像金字塔的层数
                           int _iniThFAST,      //指定初始的FAST特征点提取参数,可以提取出最明显的角点
                           int _minThFAST):     //如果因为图像纹理不丰富提取出的特征点不多,为了达到想要的特征点数目,
                                                //就使用这个参数提取出不是那么明显的角点
    nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),
    iniThFAST(_iniThFAST), minThFAST(_minThFAST)//设置这些参数
3.遇到的问题:
1)图像金字塔特征点数目

等比数列的思路计算总面积,然后计算单位面积的分配的特征点数量,得到金字塔每一层需要提取的特征点数量。注意其中scale^2用scale替换。



2)灰度质心圆索引

3)FAST特征点判定
4)BRIEF描述子

一个特征点的,描述子是如何用pattern得到的或者描述子是如何得到的?
BRIEF算法的核心思想是在关键点P的周围以一定模式选取N个点对,把这N个点对的比较结果组合起来作为描述子。

5)orientated FAST(ORB orientated rotated brief)

orientated FAST是改变描述子还是增加特征点的方向呢?
不管叫什么名字,这个算法要解决的问题是:

回顾一下BRIEF描述子的计算过程:在当前关键点P周围以一定模式选取N个点对,组合这N个点对的T操作的结果就为最终的描述子。当我们选取点对的时候,是以当前关键点为原点,以水平方向为X轴,以垂直方向为Y轴建立坐标系。当图片发生旋转时,坐标系不变,同样的取点模式取出来的点却不一样,计算得到的描述子也不一样,这是不符合我们要求的。因此我们需要重新建立坐标系,使新的坐标系可以跟随图片的旋转而旋转。这样我们以相同的取点模式取出来的点将具有一致性。ORB特征原理(浅显易懂)

[?]6)ORB特征点方向计算实现旋转不变性

这个问题问的起始是怎么具体解决旋转不变性的问题
为什么是圆呢?这个图应该怎么看呢?


一个是旋转前,一个是旋转后,我们后续是在旋转后的基础上做的。
当我们选取点对的时候,是以当前关键点为原点,以水平方向为X轴,以垂直方向为Y轴建立坐标系。当图片发生旋转时,坐标系就要旋转一下。

在图1中,P为关键点。圆内为取点区域,每个小格子代表一个像素。现在我们把这块圆心区域看做一块木板,木板上每个点的质量等于其对应的像素值。根据积分学的知识我们可以求出这个密度不均匀木板的质心Q。
我们知道圆心是固定的而且随着物体的旋转而旋转。当我们以PQ作为坐标轴时(图2),在不同的旋转角度下,我们以同一取点模式取出来的点是一致的。这就解决了旋转一致性的问题。

以计算出来的特征点方向为x轴,再建立y轴。在这个基础上计算特征点的描述子,这样就特征点就具备了旋转不变性。
计算灰度质心用的是圆呢?为什么方形的不行呢


我目前认为是可以的。如图。

=========================================================================
题目:图像金字塔特征点数目的计算方式。描述子加入计算特征点的方向的目的。
参考:
思路:
1.思路:

图像金字塔特征点数目的计算方式:等比数列。
描述子加入计算特征点的方向的目的:为了使得特征点的描述子具有旋转不变性。

2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
// 最底层分配的特征点个数
  float nDesiredFeaturesPerScale =
      nfeatures * (1 - factor) /
      (1 - (float)pow((double)factor, (double)nlevels));
其他:

二、通俗易懂理解特征提取仿函数、图像扩充金字塔

1.参考资料:

[1] ORBSLAM2 source code

2.主要函数:
void Frame::ExtractORB(int flag, const cv::Mat &im)
void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints,
                      OutputArray _descriptors)
void ORBextractor::ComputePyramid(cv::Mat image)
3.遇到的问题:
1)仿函数
void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints,
                      OutputArray _descriptors)

为什么要重载小括号运算符operator()?
可以用于仿函数(一个可以实现函数功能的对象)
仿函数(functor)又称为函数对象(functor object)是一个能行使函数功能的类。仿函数的语法几乎和我们使用的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符
1.仿函数可以拥有自己的数据成员和成员变量,这意味着仿函数拥有状态。这在一般的函数中是不可能的。
2.仿函数通常比一般函数有更好的速度。

2)图像金字塔

cv::Rect矩形类参数设置

typedef struct CvRect 
{ 
  int x; /* 方形的左上角的x-坐标 */ 
  int y; /* 方形的左上角的y-坐标*/ 
  int width; /* 宽 */ 
  int height; /* 高 */ 
}

图像金字塔

通俗解释:最底层图像,分辨率最高,可以看到最远的点。

copyMakeBorder
把源图像拷贝到目的图像的中央,四面填充指定的像素。图片如果已经拷贝到中间,只填充边界



PS:BORDER_ISOLATED由于我们是整张图像而不是ROI,所以可加可不加。

=========================================================================
题目:什么叫仿函数,怎么写,怎么用,有什么好处?
参考:
思路:
1.思路:

仿函数的语法几乎和我们使用的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符
1.仿函数可以拥有自己的数据成员和成员变量,这意味着仿函数拥有状态。这在一般的函数中是不可能的。
2.仿函数通常比一般函数有更好的速度。

2.图解(请用纸):
3.公式推导(请用纸):
要点程序:

// 这是一个变量

ORBextractor* mpORBextractorLeft, *mpORBextractorRight;

// 这是一个变量,这是一个看着像函数的用法

    (*mpORBextractorLeft)(im,         //待提取特征点的图像
                          cv::Mat(),  //掩摸图像, 实际没有用到
                          mvKeys,  //输出变量,用于保存提取后的特征点
                          mDescriptors);  //输出变量,用于保存特征点的描述子

// 然后为什么能这么干呢?在类里面实现了这个operator()函数

  void operator()(cv::InputArray image, cv::InputArray mask,
                  std::vector<cv::KeyPoint> &keypoints,
                  cv::OutputArray descriptors)
其他:

三、通俗易懂理解特征点四叉树均匀化分配策略

1.参考资料:

[1] ORBSLAM2 source code
[2] ORB-SLAM2代码笔记(十):ORBextractor

2.主要函数:
void ORBextractor::ComputeKeyPointsOctTree(
    vector<vector<KeyPoint> >& allKeypoints)
void ExtractorNode::DivideNode(ExtractorNode &n1,   
                               ExtractorNode &n2, 
                               ExtractorNode &n3, 
                               ExtractorNode &n4)

非常重要的函数:

vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,
                                       const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
void ExtractorNode::DivideNode(ExtractorNode &n1,   
                               ExtractorNode &n2, 
                               ExtractorNode &n3, 
                               ExtractorNode &n4)
3.主要过程如下:
1)划分网格(网格大小是30像素),对每个网格进行FAST特征提取,没有提取的到的降低检测阈值
2)对当前层图像生成一个提取器节点,把特征点分配到这个提取器节点中
3)1分为4一次,看看节点数量是否大于我要提取的节点个数,如果小于的话,还需要接着分裂,其中节点为空,删除节点,节点中特征点数量为1,不再分裂。大于的话,结束分裂,从每个节点中选择一个响应值最好的,然后获取最终的特征点集合。



4.PS:
1)特征点提取的传统方法(网格筛点法):
void ORBextractor::ComputeKeyPointsOld(
    std::vector<std::vector<KeyPoint> > &allKeypoints)
2)主要过程如下:

第一步计算将图像分成多少个cell,对每个cell分别进行提取特征点,cell的计算方法是根据需要提取的特征点数目,假设每个cell中需要提取5个特征点,以此进行计算需要的cell数目。
接着对计算好的cell进行特征点提取。首先使用阈值较大的参数作为FAST特征点检测阈值,如果提取到的特征点数目足够多,那么直接计算下一个元包即可,否则使用较小的参数重新提取特征点。
然后就涉及到特征点数目的分配问题。由于图像中不可避免出现纹理丰富和纹理较浅的区域,在纹理丰富的区域,角点的数目可能提取很多,而在纹理不丰富的区域,角点提取很少。对于将特征点数目不足的cell中剩余不足的数目分配到其他cell中,提高其他cell中期望提取的特征点数量阈值,直到最后提取足够数量的特征点。当然如果提取的数量确实不足预期,也就停止了。

5.问题
=========================================================================
题目:特征点均匀化策略一分为4都是一分为4到底吗,比如说1张图像,已经一分为4。在这个基础上,下一轮是否对这4个中的每一个都进行一分为四了?还是说到数目了就停止了。
参考:
思路:
1.思路:

不断地进行一分为4。=>快要达到目标数量的时候,那就sort一下=>从数量多的进行开始进行一分为4,同时每次判断是否满足目标数量。这样很符合一个人的思维。

2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
其他:

四、通俗易懂理解ORB特征点方向计算实现旋转不变性

1.参考资料:

[1] ORBSLAM2 source code

2.主要函数:
static void computeOrientation(const Mat& image, vector<KeyPoint>& keypoints, const vector<int>& umax)
static float IC_Angle(const Mat& image, Point2f pt,  const vector<int> & u_max)

这个图有疑问啊!!!暂时不jiujie





五、通俗易懂理解ORB描述子steer brief计算方法

1.参考资料:

[1] ORBSLAM2 source code

2.主要函数:
static void computeOrbDescriptor(const KeyPoint& kpt,
                                 const Mat& img, const Point* pattern,
                                 uchar* desc)
3.如何将描述子和特征点方向结合起来

我们期望准确地描述特征点,所以当我们把图像顺时针转了一个角度的话,我们期望获取某一点旋转后的坐标,以便正常地描述该特征点。因为旋转后坐标对应的灰度值和之前的灰度值是一样的。
描述特征点的坐标集合中的坐标值是固定的。
高斯模糊把杂点去掉



高斯模糊能把杂点去掉

4.问题
1)描述子最终的形式是怎样的?

cv::Mat格式,描述整幅图像的特征点,每一行是一个描述子.
对于一个描述子来说,一共比较32次,每次用16个点,比较8次,产生1个字节,所以每个描述子用32*8=256bit.

=========================================================================
题目:如何利用特征点方向对描述子进行旋转呢?请写出公式。
参考:
思路:
1.思路:
2.图解(请用纸):
3.公式推导(请用纸):
要点程序:
// x'= xcos(θ) - ysin(θ),  y'= xsin(θ) + ycos(θ)
#define GET_VALUE(idx) \
        center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + \        // y'* step
               cvRound(pattern[idx].x*a - pattern[idx].y*b)]                // x'
其他:
=========================================================================
题目:高斯模糊的目的是什么?
参考:
思路:
1.思路:

高斯blur,去除噪点。

2.图解(请用纸):
3.公式推导(请用纸):
要点程序:

// 注意:提取特征点的时候,使用的是清晰的原图像;这里计算描述子的时候,为了避免图像噪声的影响,使用了高斯模糊

    GaussianBlur(workingMat,  //源图像
                 workingMat,  //输出图像
                 Size(7, 7),  //高斯滤波器kernel大小,必须为正的奇数
                 2,           //高斯滤波在x方向的标准差
                 2,           //高斯滤波在y方向的标准差
                 BORDER_REFLECT_101);  //边缘拓展点插值类型
其他:

六、通俗易懂理解去畸变、算图像边界、划分网格

1.参考资料:

[1] ORBSLAM2 source code

2.主要函数:
Frame::Frame(const cv::Mat &imGray, const double &timeStamp, ORBextractor* extractor,ORBVocabulary* voc, cv::Mat &K, cv::Mat &distCoef, const float &bf, const float &thDepth)
    :mpORBvocabulary(voc),mpORBextractorLeft(extractor),mpORBextractorRight(static_cast<ORBextractor*>(NULL)),
     mTimeStamp(timeStamp), mK(K.clone()), mDistCoef(distCoef.clone()), mbf(bf), mThDepth(thDepth)
void Frame::UndistortKeyPoints()
void Frame::ComputeImageBounds(const cv::Mat &imLeft)   

注意一下去畸变函数需要输入双通道的mat:

//为了能够直接调用opencv的函数来去畸变,需要先将矩阵调整为2通道(对应坐标x,y) 
mat=mat.reshape(2);
cv::undistortPoints(mat,mat,mK,mDistCoef,cv::Mat(),mK);
//调整回只有一个通道,回归我们正常的处理方式
mat=mat.reshape(1);

分配特征点到网格,网格在匹配的时候用到

void Frame::AssignFeaturesToGrid()
bool Frame::PosInGrid(const cv::KeyPoint &kp, int &posX, int &posY)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容