ORB特征点提取代码详解 四叉树均匀化

2023-05-16

ORB特征点提取代码详解 ExtractORB

最近在尝试将ORB提取特征点机制放置于芯片上,便于后期调用,减小CPU的计算负担,故最近对ORB特征点提取代码进行了研读,记录一下,有不对的还请指教;

首先在Frame()初始化中,含有ExtractORB(0,imGray); 这次我们研究的全在这个特征点提取函数里面;

void Frame::ExtractORB(int flag, const cv::Mat &im)
{
    if(flag==0)
        (*mpORBextractorLeft)(im,cv::Mat(),mvKeys,mDescriptors);
    else
        (*mpORBextractorRight)(im,cv::Mat(),mvKeysRight,mDescriptorsRight);
}

这里用了()的重载,直接跳转 *mpORBextractorLeft ; 跳转至void ORBextractor::operator();
特征点的提取均在此函数实现;

    if(_image.empty())
        return;

    Mat image = _image.getMat();
    assert(image.type() == CV_8UC1 );
    
    // Pre-compute the scale pyramid
    // 构建图像金字塔
    ComputePyramid(image);

读取图片,为空返回; 不然建立金字塔,过程不再阐述;

重点 ComputeKeyPointsOctTree

    // 计算每层图像的兴趣点    //计算关键点并生成四叉树
    vector < vector<KeyPoint> > allKeypoints; // vector<vector<KeyPoint>>
    ComputeKeyPointsOctTree(allKeypoints);
    //ComputeKeyPointsOld(allKeypoints);

首先开辟一个vector < vector > allKeypoints 空间,存放提取的特征点,后面进入 ComputeKeyPointsOctTree, 提取特征点,并采用四叉树的方法对提取的特征点进行均匀化;

void ORBextractor::ComputeKeyPointsOctTree(vector<vector<KeyPoint> >& allKeypoints)
{
    allKeypoints.resize(nlevels);

    const float W = 30;

    // 对每一层图像做处理
    for (int level = 0; level < nlevels; ++level)
    {
        const int minBorderX = EDGE_THRESHOLD-3;
        const int minBorderY = minBorderX;
        const int maxBorderX = mvImagePyramid[level].cols-EDGE_THRESHOLD+3;
        const int maxBorderY = mvImagePyramid[level].rows-EDGE_THRESHOLD+3;

        vector<cv::KeyPoint> vToDistributeKeys;
        vToDistributeKeys.reserve(nfeatures*10);

        const float width = (maxBorderX-minBorderX);
        const float height = (maxBorderY-minBorderY);

        const int nCols = width/W;
        const int nRows = height/W;
        const int wCell = ceil(width/nCols);
        const int hCell = ceil(height/nRows);

        for(int i=0; i<nRows; i++)
        {
            const float iniY =minBorderY+i*hCell;
            float maxY = iniY+hCell+6;

            if(iniY>=maxBorderY-3)
                continue;
            if(maxY>maxBorderY)
                maxY = maxBorderY;

            for(int j=0; j<nCols; j++)
            {
                const float iniX =minBorderX+j*wCell;
                float maxX = iniX+wCell+6;
                if(iniX>=maxBorderX-6)
                    continue;
                if(maxX>maxBorderX)
                    maxX = maxBorderX;

                // FAST提取兴趣点, 自适应阈值
                vector<cv::KeyPoint> vKeysCell;
                FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
                     vKeysCell,iniThFAST,true);

                if(vKeysCell.empty())
                {
                    FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
                         vKeysCell,minThFAST,true);
                }

                if(!vKeysCell.empty())
                {
                    for(vector<cv::KeyPoint>::iterator vit=vKeysCell.begin(); vit!=vKeysCell.end();vit++)
                    {
                        (*vit).pt.x+=j*wCell;
                        (*vit).pt.y+=i*hCell;
                        vToDistributeKeys.push_back(*vit);
                    }
                }

            }
        }

        vector<KeyPoint> & keypoints = allKeypoints[level];
        keypoints.reserve(nfeatures);

        // 根据mnFeaturesPerLevel,即该层的兴趣点数,对特征点进行剔除
        keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,
                                      minBorderY, maxBorderY,mnFeaturesPerLevel[level], level);

        const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level];

        // Add border to coordinates and scale information
        const int nkps = keypoints.size();
        for(int i=0; i<nkps ; i++)
        {
            keypoints[i].pt.x+=minBorderX;
            keypoints[i].pt.y+=minBorderY;
            keypoints[i].octave=level;
            keypoints[i].size = scaledPatchSize;
        }
    }

    // compute orientations
    for (int level = 0; level < nlevels; ++level)
        computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
}

首先根据金字塔层数开辟空间; w=30;在提取特征点过程中,将图像分割成很多分,w=30,为分割的每个小格子的大小;后面进入第一个for循环,对每一层进行同样操作;

vToDistributeKeys 为当前层提取的特征点个数,为最终提取的10倍;

后面按照对应的策略,分成跟多小块进行提出处理;
进入两个for循环,开始对每个小块进行处理;

                // FAST提取兴趣点, 自适应阈值
                vector<cv::KeyPoint> vKeysCell;
                FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
                     vKeysCell,iniThFAST,true);

提取FAST特征点,此时提取的数量很多,后续需要均匀化处理;

        // 根据mnFeaturesPerLevel,即该层的兴趣点数,对特征点进行剔除
        keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,
                                      minBorderY, maxBorderY,mnFeaturesPerLevel[level], level);

DistributeOctTree 采用四叉树的方法对提取的特征点进行提取,从而提高特征点的精度以及控制数量;
四叉树的具体原理,我推荐看这个:https://zhuanlan.zhihu.com/p/61738607
在 DistributeOctTree 中 :
list lNodes; 建立了list 存储需要处理的节点;

如果image为640*480,则一开始 节点数nIni = 1 ;即 lNodes.size() = 1;
后面进入第一个 大 while 循环, 建立缓存区; 后面进入里面的第一个 while 循环;

                // If more than one point, subdivide
                ExtractorNode n1,n2,n3,n4;
                lit->DivideNode(n1,n2,n3,n4); // 再细分成四个子区域

当拆分的节点数不满足要求时,采用 DivideNode 进行1分4的拆分,同时处理每个节点时的数据存在 vSizeAndPointerToNode 中;每当1分4后处理后,此节点删除;

                if(n1.vKeys.size()>0)
                {
                    lNodes.push_front(n1);                    
                    if(n1.vKeys.size()>1)
                    {
                        nToExpand++;
                        vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));
                        lNodes.front().lit = lNodes.begin();
                    }
                }

对于刚分的节点,
如果此节点中没有特征点,则删除此节点,后续不在关注;
如果此节点中有特征点,且则将节点至于 lNodes 最前端;
如果此节点中特征点个数>1,则nToExpand+1,后续可能对其继续进行分割;
在此while 中将 lNodes 的 节点全都处理一次,后面观察是否满足要求:

        if((int)lNodes.size()>=N || (int)lNodes.size()==prevSize)
        {
            bFinish = true;
        }
        // 当再划分之后所有的Node数大于要求数目时
        else if(((int)lNodes.size()+nToExpand*3)>N)
        {}

如果不满足数目要求,则返回上面的while循环,将上一次分割的节点继续分割;直到满足 else if(((int)lNodes.size()+nToExpand*3)>N) , 则运行第二个while 循环;完成最后一轮的节点分割;

    // Retain the best point in each node
    // 保留每个区域响应值最大的一个兴趣点
    vector<cv::KeyPoint> vResultKeys;
    vResultKeys.reserve(nfeatures);
    for(list<ExtractorNode>::iterator lit=lNodes.begin(); lit!=lNodes.end(); lit++)
    {
        vector<cv::KeyPoint> &vNodeKeys = lit->vKeys;
        cv::KeyPoint* pKP = &vNodeKeys[0];
        float maxResponse = pKP->response;

        for(size_t k=1;k<vNodeKeys.size();k++)
        {
            if(vNodeKeys[k].response>maxResponse)
            {
                pKP = &vNodeKeys[k];
                maxResponse = vNodeKeys[k].response;
            }
        }

        vResultKeys.push_back(*pKP);
    }

按照上述分配的节点,提取每个节点中得分较大的特征点;完成返回;

返回上层后计算对应的描述子和 计算方向等事宜,比较简单,不在阐述;重点在如何采用四叉树来对提取的特征点进行均匀化;
大概写了下,大家将就看吧 哈哈

ORB-SLAM中的ORB特征(提取) 均匀化 : https://zhuanlan.zhihu.com/p/61738607
ORB特征均匀提取策略对性能的提升有多大? : https://www.sohu.com/a/341157217_100007727

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ORB特征点提取代码详解 四叉树均匀化 的相关文章

随机推荐