首页 > 其他分享 >ORB-SLAM2 ---- ORBextractor::ComputeKeyPointsOctTree

ORB-SLAM2 ---- ORBextractor::ComputeKeyPointsOctTree

时间:2024-10-11 20:53:52浏览次数:12  
标签:遍历 const level int ---- 特征 SLAM2 图像 ORB

文章目录

一、函数作用

ORB-SLAM2 ---- ORBextractor::DistributeOctTree是此函数的基础,此函数是除主函数外最重要的一个函数,此函数的主要作用是对特征点进行处理(提取,分配,计算方向信息)

二、源码及注释

void ORBextractor::ComputeKeyPointsOctTree(
    vector<vector<KeyPoint> >& allKeypoints)// 所有的特征点,这里第一层vector存储的是某图层里面的所有特征点,
												// 第二层存储的是整个图像金字塔中的所有图层里面的所有特征点
{
    // 该容器是存放不同层数的所有特征点,一共nlevels = 8层,初始化一下
    allKeypoints.resize(nlevels);

    // 一个网格单元的边长,网格单元为正方形
    const float W = 30;

    // 遍历每一层图像
    for (int level = 0; level < nlevels; ++level)
    {
        // EDGE_THRESHOLD为扩充的边界,加减3是因为特征点分析时,要留出半径为3的圆,使得周围有16个像素可以进行灰度比较
        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;

        // k开辟一个需要分配的特征点容器
        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++)
        {
            // 计算每个小单元的左上y坐标和左下y坐标
            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;

                // 这个容器存储提取到的特征点
                vector<cv::KeyPoint> vKeysCell; // 为什么每次循环不用清空这个容器
                // 调用FAST来提取该单元格角点
                FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),// 待检测的图像,这里就是当前遍历到的图像块
                    vKeysCell,      // 存储角点位置的容器
                    iniThFAST,      // 检测阈值  ORBextractor.iniThFAST: 20
                    true);          // 使能非极大值抑制


                // 如果这个图像块中使用默认的FAST检测阈值没有能够检测到角点
                if(vKeysCell.empty())
                {
                    FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
                         vKeysCell,minThFAST,true); // ORBextractor.minThFAST: 7
                }
                
                // 如果有角点进行下面的操作
                if(!vKeysCell.empty())
                {
                    // 遍历其中的所有的FAST角点
                    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);
        // 调用DistributeOctTree,返回已经被分配好特征点的容器
        keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,
                                      minBorderY, maxBorderY,mnFeaturesPerLevel[level], level);

        // 当前图层的缩放因子
        const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level];
        // 获取剔除过程后保留下来的特征点数目
        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;
            // 记录计算方向的patch,缩放后对应的大小, 又被称作为特征点半径
            keypoints[i].size = scaledPatchSize;
        }
    }

    // 然后计算这些特征点的方向信息,注意这里还是分层计算的
    for (int level = 0; level < nlevels; ++level)
        computeOrientation(mvImagePyramid[level],   //对应的图层的图像
        allKeypoints[level],                        //这个图层中提取并保留下来的特征点容器
        umax);                                      //以及PATCH的横坐标边界
}

三、函数的讲解

因为此函数代码量较大,我们分几段来讲解

1. 遍历金字塔的每一层,将其分成30*30的网格单元,并给每一层添加图像边界

这段代码已经详细标注了,相信读者能看懂,难点在于为什么要这样做

  1. 第一点就是为什么要添加图像边界,为什么要加减3,在识别角点的时候用的是灰度对比法,特征点需要与旁边半径为3的圆的特定16个点比较灰度,这个加减3就是为了防止边缘的角点对比灰度时缺失像素点,添加图像边界EDGE_THRESHOLD,这个边界扩展方式是 BORDER_REFLECT_101(ORBextractor::ComputePyramid中有提到),即通过镜像反射图像边缘的像素来填充扩展区域。这种扩展方式不会引入额外的噪声或突兀的边界像素,确保了图像的平滑性,增强了特征提取的鲁棒性。
  2. 第二点就是很多初学者好奇最后四行代码是干吗,这不是重复计算了吗,其实这里是有一个四舍五入的,在算一次是为了保证完整覆盖图像
allKeypoints.resize(nlevels);

    // 一个网格单元的边长,网格单元为正方形
    const float W = 30;

    // 遍历每一层图像
    for (int level = 0; level < nlevels; ++level)
    {
        // EDGE_THRESHOLD为扩充的边界,加减3是因为特征点分析时,要留出半径为3的圆,使得周围有16个像素可以进行灰度比较
        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;

        // k开辟一个需要分配的特征点容器
        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);

2. 遍历每个单元格,提取特征点

这段代码前面的部分,在补充分配单元格的内容,简单来说就是分到最后一行不足30了,就委屈一下最后一行列少分配一些,后续调用
FAST ()角点提取器来提取角点(角点优先作为特征点),如果该网格内没有角点,就降低阈值进行提取特征值,特征值提取好后,遍历这些提取出来的特征值,将特征点的坐标转换到【坐标边界】下的坐标。其中FAST ()函数是OpenCV中内置的函数。

// 遍历每个小单元格
        for(int i=0; i<nRows; i++)
        {
            // 计算每个小单元的左上y坐标和左下y坐标
            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;

                // 这个容器存储提取到的特征点
                vector<cv::KeyPoint> vKeysCell; // 为什么每次循环不用清空这个容器
                // 调用FAST来提取该单元格角点
                FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),// 待检测的图像,这里就是当前遍历到的图像块
                    vKeysCell,      // 存储角点位置的容器
                    iniThFAST,      // 检测阈值  ORBextractor.iniThFAST: 20
                    true);          // 使能非极大值抑制


                // 如果这个图像块中使用默认的FAST检测阈值没有能够检测到角点
                if(vKeysCell.empty())
                {
                    FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
                         vKeysCell,minThFAST,true); // ORBextractor.minThFAST: 7
                }
                
                // 如果有角点进行下面的操作
                if(!vKeysCell.empty())
                {
                    // 遍历其中的所有的FAST角点
                    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);
                    }
                }

            }
        }

3. 调用DistributeOctTree()函数分配特征点

这段代码先调用DistributeOctTree()来分配提取出来的特征点,然后遍历这些特征点,将其坐标转换到当前图层图像坐标系下

// 声明一个对当前图层的特征点的容器的引用
vector<KeyPoint> & keypoints = allKeypoints[level];
        // 开辟他的大小为想要提取的特征点数量
        keypoints.reserve(nfeatures);
        // 调用DistributeOctTree,返回已经被分配好特征点的容器
        keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,
                                      minBorderY, maxBorderY,mnFeaturesPerLevel[level], level);

        // 当前图层的缩放因子
        const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level];
        // 获取剔除过程后保留下来的特征点数目
        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;
            // 记录计算方向的patch,缩放后对应的大小, 又被称作为特征点半径
            keypoints[i].size = scaledPatchSize;
        }

4. 计算所有保留下来的特征点的方向信息

computeOrientation()函数在ORB-SLAM ---- IC_Angle()这篇中讲过

// 然后计算这些特征点的方向信息,注意这里还是分层计算的
    for (int level = 0; level < nlevels; ++level)
        computeOrientation(mvImagePyramid[level],   //对应的图层的图像
        allKeypoints[level],                        //这个图层中提取并保留下来的特征点容器
        umax);                                      //以及PATCH的横坐标边界

标签:遍历,const,level,int,----,特征,SLAM2,图像,ORB
From: https://blog.csdn.net/uzi_ccc/article/details/142768805

相关文章

  • Challenges of regulatory laws.
    Thechallengesandpracticesofregulatoryproblems.Firstlyfromthepointofcountrylayer,thenetworksecuritylawwaspublishedintwothousandandseventeen.(2017)AnditisthefirstBasicLawfocusingontheareaofnetworksecurityandtosetcle......
  • Financial technology security.
    Thisbookintroduceaboutthefinancialtechnologysecurityproblemsandithastenchapterstointroducethesecurityproblemsandthevalueofsecurityandsomefearsaboutsecuritypointsandtheapplicationsecurityandsomedataandnetworksecurit......
  • PCL 计算点云AABB包围盒
    目录一、概述1.1原理1.2实现步骤1.3应用场景二、代码实现2.1关键函数2.1.1计算AABB2.1.2可视化AABB2.2完整代码三、实现效果PCL点云算法汇总及实战案例汇总的目录地址链接:PCL点云算法与项目实战案例汇总(长期更新)一、概述        点云的包围盒(Boundi......
  • 【动物识别系统】Python+卷积神经网络算法+人工智能项目+深度学习+计算机课设项目
    一、介绍动物识别系统。本项目以Python作为主要编程语言,并基于TensorFlow搭建ResNet50卷积神经网络算法模型,通过收集4种常见的动物图像数据集(猫、狗、鸡、马)然后进行模型训练,得到一个识别精度较高的模型文件,然后保存为本地格式的H5格式文件。再基于Django开发Web网页端操作......
  • Linux下以编译源码的方式安装Qt5与Qt6及其使用
    文章目录概要资源下载依赖安装编译Qt5Qt6遇到的问题qtchooser使用概要自Qt5.15开始,不再提供opensourceofflineinstallers,也就是原来的.run的安装文件,只能通过源码编译来安装了参考文章资源下载源码网址,链接为Qt的资源,根据自己选择下载例如#下载源码......
  • 【C++】模板进阶
    【C++】模板进阶一.非类型模板参数二.模板的特化函数模板特化函数模板的特化步骤:类模板特化1.全特化2.偏特化(1)部分特化(2)参数更进一步的限制三.模板分离编译1.什么是分离编译2.模板的分离编译3.解决方法四.模板总结一.非类型模板参数模板参数分为:类类......
  • 5.1 数据集概览
    前言:总算找到了自己的数据集如何处理的教程了,有意思。 5.1数据集概览Ultralytics支持的数据集Ultralytics为各种计算机视觉任务提供了丰富的数据集支持,包括检测、实例分割、姿态估计、分类和多目标跟踪。以下是主要的Ultralytics数据集列表,以及每个计算机视觉任务和......
  • 软考11——信息安全
    1.信息安全和信息系统安全文老师软考教育1SO网络参考模型应用层表示层会话层传输层网络层链路层物理层安全对等实体认证服务访问控制服务>数据保密服务>数据完整性服务>数据源点认证服务》禁止否认服务犯罪证据提供服务安全服务安全机制◆信息安全系统的体系架构×轴是“安全机制......
  • FPGA Verilog HDL代码如何debug?
    Q:Verilog代码如何debug?最近学习fpga,写了不少verilog,开始思考如何debug的问题!c语言是顺序执行,而verilog是并行执行,想请教如何debug自己的verilog代码,我以前一直都是对照着modelsim上的方针波形来看看哪里有逻辑错误!A:以下是一些常见的Verilog代码调试方法:1.仿真工具:正如......
  • 3.6 使用Ultralytics YOLO进行模型基准测试
    3.6使用UltralyticsYOLO进行模型基准测试UltralyticsYOLO生态系统及其集成引言当您的模型完成训练和验证后,下一步就是评估其在各种现实场景中的表现。UltralyticsYOLO11的基准测试模式通过提供一个强大的框架,用于评估模型在各种导出格式下的速度和准确性。观看视频:Ult......