美文网首页
通俗易懂理解ORBSLAM2特征提取模块

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

作者: Optimization | 来源:发表于2020-11-01 17:48 被阅读0次

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

    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)
    

    相关文章

      网友评论

          本文标题:通俗易懂理解ORBSLAM2特征提取模块

          本文链接:https://www.haomeiwen.com/subject/arwplktx.html