ORB-SLAM2代码笔记(十):ORBextractor

特征点提取器的构造函数

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

  • 初始化下面参数:
    int nFeatures = 1000; //特征点数
    float fScaleFactor = 1.2; //高斯金字塔的放大系数
    int nLevels = 8; //高斯金字塔的层数
    int fIniThFAST = 40; //FAST特征的第一个参数
  • 然后逐层计算图像金字塔中图像相当于初始图像的缩放系数
  • 再开始逐层计算要分配的特征点个数,顶层图像除外(看循环后面)会导致剩余一些特征点个数没有被分配,所以这里就将这个余出来的特征点分配到最高的图层中
    //开始逐层计算要分配的特征点个数,顶层图像除外(看循环后面)
    for( int level = 0; level < nlevels-1; level++ )
    {
        //分配 cvRound : 返回个参数最接近的整数值
        mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);
        //累计
        sumFeatures += mnFeaturesPerLevel[level];
        //乘系数
        nDesiredFeaturesPerScale *= factor;
    }
    //由于前面的特征点个数取整操作,可能会导致剩余一些特征点个数没有被分配,所以这里就将这个余出来的特征点分配到最高的图层中
    mnFeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0);
    
  • 下来要考虑的是计算特征点的方向。本项目中采用的是在一个圆形区域中计算,为了能够快速计算该区域,采用的是首先生成蒙版的方法。所以首先初始化的过程中就将该圆形区域的相对坐标存储在umax数组中。至此,初始化过程结束。
    //利用圆的方程计算每行像素的u坐标边界(max)
    for (v = 0; v <= vmax; ++v)
        umax[v] = cvRound(sqrt(hp2 - v * v));       //结果都是大于0的结果,表示x坐标在这一行的边界
    
    // Make sure we are symmetric
    //这里其实是使用了对称的方式计算上八分之一的圆周上的umax,目的也是为了保持严格的对称(如果按照常规的想法做,由于cvRound就会很容易出现不对称的情况,
    //同时这些随机采样的特征点集也不能够满足旋转之后的采样不变性了)
    for (v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v)
    {
        while (umax[v0] == umax[v0 + 1])
            ++v0;
        umax[v] = v0;
        ++v0;
    }
    

构造图像金字塔

void ORBextractor::ComputePyramid(cv::Mat image)
直接通过图像的缩放就可以达到目的。主要使用的函数就是如下所示的resize函数
resize(mvImagePyramid[level - 1], mvImagePyramid[level], sz, 0, 0, INTER_LINEAR);

特征点提取与分配

为了使接下来计算PnP时能够更加准确,需要保持的原则就是尽量使得特征点能够均匀分布在整个图像中,所以需要将图像分成更小的部分分别提取特征点,并用某种机制来分配。在本项目中,作者提供了两种分配方案分别是八叉树的方法和传统的方法。考虑到在室内场景中并不需要提供太多的特征点,而在1000个特征点以下时,传统的方法表现要由于建造八叉树的方法。所以在实际应用中,都是采用传统的方法,而不去花费大量收时间用于建造八叉树。

传统方法:void ORBextractor::ComputeKeyPointsOld(
std::vector<std::vector<KeyPoint> > &allKeypoints) //输出,所有图层上的所有特征点
传统方法的第一步就是计算需要将图像分割成多少个元包(cell),对于每个元包分别提取特征点。元包的计算方法为,根据需要提取的特征点数目,假设每个元包中需要提取5个特征点,以此来进行计算需要的cell数目。

接着对上面计算好的元包分别进行特征点的提取。

//调用opencv的库函数来检测FAST角点
                FAST(cellImage,             //cell中的图像
        cellKeyPoints[i][j],    //用于保存提取出来的特征点的vector容器
        iniThFAST,              //初步的FAST检测阈值
        true);                  //使能非极大值抑制

这里注意,由于FAST特征在计算角点时向内缩进了3个像素才开始计算,所以在使用FAST之前还需要对图像加一条宽度为3像素的边。然后就要用到之前初始化的两个阈值参数,首先使用阈值较大的参数作为FAST特征点检测的阈值,如果提取到的特征点数目足够多,那么直接计算下一个元包即可,否则就要使用较小的参数重新提取。在本项目中,特征点数目的阈值设定为3.

//如果当前cell中提取出来的特征点的个数小于3
                if(cellKeyPoints[i][j].size()<=3)
                {
                    //那么首先清空刚才提取出来的特征点
                    cellKeyPoints[i][j].clear();
                    //然后使用更小的参数阈值,进行重新提取
                    FAST(cellImage,             //cell中的图像
                         cellKeyPoints[i][j],   //输出变量,用于保存提取出来的特征点的vector
                         minThFAST,             //较小的那个FAST阈值
                         true);                 //使能非极大值抑制
                }

然后就涉及到了特征点的数目分配问题。由于图像中不可避免的存在纹理丰富和纹理较浅的区域,在纹理较丰富的区域,角点的数目可能提取很多,而在纹理不丰富的区域,角点的数目可能很少。而在分配各区域选取的特征点数目时,就要考虑前面提到的极可能均匀的问题。所以采用的方法是循环将特征点数目不足的元包中的剩余数目分配到其他所有元包中,知道最后取得足够数量的特征点。当然,如果最初提取的特征点数目就不足预期,那么直接全部选取即可。所以这种方法并不能保证最终得到的特征点数目一定能达到1000。
对于那些特征点数目特别多的元包,采用的是对各个角点的质量进行排序,选择最好的前n个特征点作为最终结果。

计算方向

为了使得提取的特征点具有旋转不变性,需要计算每个特征点的方向。方法是计算以特征点为中心以像素为权值的圆形区域上的重心,以中心和重心的连线作为该特征点的方向。具体计算过程在IC_Angle

static void computeOrientation(
    const Mat& image,               //特征点所在的图像(其实就是图像金字塔中每一层的图像)
    vector<KeyPoint>& keypoints,    //存储有特征点的vector
    const vector<int>& umax)        //以及每个特征点所在图像区块的每行的边界u_max组成的vector
{
    //遍历完所有的特征点
    for (vector<KeyPoint>::iterator keypoint = keypoints.begin(),
         keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint)
    {
        //计算这个特征点的方向,其实就是简单的函数调用
        keypoint->angle = IC_Angle(image,           //特征点所在的图层的图像
                                   keypoint->pt,    //特征点在这张图像中的坐标subl
                                   umax);           //以及图像区块的边界
    }//遍历完成所有的特征点
}

计算描述子

brief描述子由32*8位组成,其中每一位是来自于两个像素点灰度的直接比较,所以每比较出8bit结果,需要16个随机点,这也就是为什么pattern需要+=16的原因。

for (int i = 0; i < 32; ++i, pattern += 16)
    {
        
        int t0,     //参与比较的一个特征点的灰度值
            t1,     //参与比较的另一个特征点的灰度值       //TODO 检查一下这里灰度值为int型???
            val;    //描述子这个字节的比较结果
        
        t0 = GET_VALUE(0); t1 = GET_VALUE(1);
        val = t0 < t1;                          //描述子本字节的bit0
        t0 = GET_VALUE(2); t1 = GET_VALUE(3);
        val |= (t0 < t1) << 1;                  //描述子本字节的bit1
        t0 = GET_VALUE(4); t1 = GET_VALUE(5);
        val |= (t0 < t1) << 2;                  //描述子本字节的bit2
        t0 = GET_VALUE(6); t1 = GET_VALUE(7);
        val |= (t0 < t1) << 3;                  //描述子本字节的bit3
        t0 = GET_VALUE(8); t1 = GET_VALUE(9);
        val |= (t0 < t1) << 4;                  //描述子本字节的bit4
        t0 = GET_VALUE(10); t1 = GET_VALUE(11);
        val |= (t0 < t1) << 5;                  //描述子本字节的bit5
        t0 = GET_VALUE(12); t1 = GET_VALUE(13);
        val |= (t0 < t1) << 6;                  //描述子本字节的bit6
        t0 = GET_VALUE(14); t1 = GET_VALUE(15);
        val |= (t0 < t1) << 7;                  //描述子本字节的bit7

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

推荐阅读更多精彩内容