当前位置: 首页 > news >正文

ORB-SLAM2学习笔记:ComputeKeyPointsOctTree分析过程记录

ComputeKeyPointsOctTree是ORB特征提取器中计算关键点的部分,特别是使用八叉树(OctTree)方法进行关键点分布。

首先,函数参数是vector<vector的引用allKeypoints,用来存储各层的关键点。代码开头调整了allKeypoints的大小为nlevels,即金字塔层数。接下来,循环处理每一层图像金字塔。

在循环内部,首先定义了边界minBorderX和minBorderY,可能是为了避开边缘区域,因为边缘附近的关键点可能不完整。然后计算了maxBorderX和maxBorderY,确定当前层的有效区域。接着初始化vToDistributeKeys,预留空间用于存储待分配的关键点。

然后根据宽度和高度划分网格,每个网格大小为W(30)。这里计算了nCols和nRows,即网格的行列数。wCell和hCell是每个网格的实际大小,可能因为不能整除而向上取整。

接下来是双重循环遍历每个网格。对于每个网格,计算初始坐标iniY和iniX,以及最大坐标maxY和maxX。这里有一些条件判断,确保网格不超过最大边界。然后在每个网格内使用FAST算法检测关键点,首先使用iniThFAST阈值,如果没检测到则改用minThFAST阈值。

检测到关键点后,调整它们的坐标,加上网格的偏移量,并存入vToDistributeKeys。这一步是因为FAST检测是在网格的子图像上进行的,所以需要将坐标转换回原图的坐标。

之后,调用DistributeOctTree函数,将关键点按八叉树分布,筛选出符合数量要求的关键点,并存入当前层的keypoints。这里可能涉及到关键点的均匀分布和响应值排序,确保每层的关键点数量符合mnFeaturesPerLevel的设置。

接着处理每个关键点的坐标,加上minBorderX和minBorderY的偏移,设置octave(金字塔层)和size(根据缩放因子调整的尺寸)。最后,循环结束后,计算每个关键点的方向,使用computeOrientation函数。

需要注意的是,代码中的EDGE_THRESHOLD、mvImagePyramid、PATCH_SIZE、mvScaleFactor等变量可能在类的其他部分定义。此外,DistributeOctTree函数的具体实现细节会影响关键点的分布方式,可能涉及四叉树分割和响应值筛选。

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;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);keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,minBorderY, maxBorderY,mnFeaturesPerLevel[level], level);const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level];// Add border to coordinates and scale informationconst 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 orientationsfor (int level = 0; level < nlevels; ++level)computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
}

以下是 ORBextractor::ComputeKeyPointsOctTree 函数的逐行解析及原理说明,结合 ORB 特征提取的算法设计与实现细节:


功能概述

该函数用于在图像金字塔各层级上提取并均匀分布 ORB 特征点,通过分块检测 FAST 角点并结合四叉树筛选策略,保证特征点分布的均匀性和尺度不变性。这是 ORB-SLAM 等视觉系统的核心步骤之一。


输入与输出

  • 输入:图像金字塔 mvImagePyramid(通过高斯模糊生成的多尺度图像集合)。
  • 输出:分层的关键点集合 allKeypoints,包含每个金字塔层级的关键点信息。

代码解析

1. 分层处理图像金字塔
allKeypoints.resize(nlevels);
for (int level = 0; level < nlevels; ++level) {// 各层参数初始化const int minBorderX = EDGE_THRESHOLD - 3;const int maxBorderX = mvImagePyramid[level].cols - EDGE_THRESHOLD + 3;// 类似处理Y方向
}

以下是代码的逐行解析,结合ORB特征提取及图像分块处理逻辑:

代码功能概述

该代码段用于在图像金字塔的指定层级上进行分块FAST特征点检测,并通过动态阈值调整和特征点再分配策略实现关键点的均匀分布,属于ORB特征提取流程中的核心步骤。


行级解析

for(int i=0; i<levelRows; i++) 
{// 计算当前行的起始Y坐标(考虑边界扩展3像素)const float iniY = minBorderY + i*cellH - 3;iniYRow[i] = iniY;  // 保存当前行的起始Y坐标// 处理最后一行的高度计算if(i == levelRows-1) {hY = maxBorderY + 3 - iniY;  // 计算实际高度if(hY <= 0) continue;        // 跳过无效行}float hX = cellW + 6;  // 列的默认宽度(包含左右各3像素扩展)// 遍历每一列for(int j=0; j<levelCols; j++) {float iniX;// 第一行初始化X坐标if(i == 0) {iniX = minBorderX + j*cellW - 3;iniXCol[j] = iniX;  // 保存列的起始X坐标} else {iniX = iniXCol[j];  // 复用已保存的X坐标}// 处理最后一列的宽度计算if(j == levelCols-1) {hX = maxBorderX + 3 - iniX;if(hX <= 0) continue;  // 跳过无效列}// 提取当前单元格图像块Mat cellImage = mvImagePyramid[level].rowRange(iniY, iniY+hY).colRange(iniX, iniX+hX);// 预分配内存(避免多次扩容)cellKeyPoints[i][j].reserve(nfeaturesCell * 5);// 使用初始阈值检测FAST特征点(启用非极大值抑制)FAST(cellImage, cellKeyPoints[i][j], iniThFAST, true);// 特征点不足时,改用最小阈值重新检测if(cellKeyPoints[i][j].size() <= 3) {cellKeyPoints[i][j].clear();FAST(cellImage, cellKeyPoints[i][j], minThFAST, true);}// 统计当前单元格特征点数量const int nKeys = cellKeyPoints[i][j].size();nTotal[i][j] = nKeys;  // 记录实际特征点数// 判断是否需要保留更多特征点if(nKeys > nfeaturesCell) {nToRetain[i][j] = nfeaturesCell;  // 保留上限bNoMore[i][j] = false;            // 标记可继续分配} else {nToRetain[i][j] = nKeys;          // 保留全部现有特征点nToDistribute += nfeaturesCell - nKeys;  // 累计待分配数量bNoMore[i][j] = true;             // 标记无法分配更多nNoMore++;                        // 统计无法分配的单元格数}}
}

关键设计原理

  1. 分块策略

    • 将图像划分为levelRows×levelCols的网格,每个单元格独立检测特征点,确保空间均匀性。
    • 边界扩展3像素(cellW+6cellH+6),避免边缘特征遗漏。
  2. 动态阈值调整

    • 初始阈值iniThFAST检测失败时,改用更低阈值minThFAST,适应低纹理区域。
  3. 特征点再分配

    • 通过nToDistribute累计需要补充的特征点数,后续步骤可能通过非极大值抑制或四叉树分割重新分配。
  4. 内存优化

    • reserve(nfeaturesCell*5)预分配内存,减少动态扩容开销。

应用场景

  • ORB特征提取:用于SLAM(如ORB-SLAM3)、目标跟踪等需要旋转不变性和实时性的场景。
  • 多尺度处理mvImagePyramid支持图像金字塔,实现多尺度特征检测。

性能优化点

  • 复用坐标:首行初始化iniXCol[j],后续行直接复用,减少重复计算。
  • 提前终止hY<=0hX<=0时跳过无效区域,减少冗余计算。
  • 并行潜力:分块结构天然适合多线程处理(需注意线程安全)。
  • 目的:遍历金字塔的每一层(nlevels),处理不同尺度的图像。
  • 边界处理EDGE_THRESHOLD 是特征点距图像边缘的最小距离,±3 的调整用于扩展有效检测区域,避免边缘特征不稳定。
2. 网格分块与FAST角点检测
const float W = 30;
const int nCols = width / W;  // 列方向分块数
const int nRows = height / W; // 行方向分块数for (int i = 0; i < nRows; i++) {for (int j = 0; j < nCols; j++) {// 定义当前网格的坐标范围const float iniY = minBorderY + i * hCell;const float iniX = minBorderX + j * wCell;// 执行FAST角点检测FAST(mvImagePyramid[level].rowRange(iniY, maxY).colRange(iniX, maxX),vKeysCell, iniThFAST, true);}
}
  • 网格划分:将当前金字塔层级的有效区域划分为 30x30 的网格,每个网格独立检测 FAST 角点,确保初步均匀性。
  • FAST检测优化
    • 使用两种阈值(iniThFASTminThFAST),优先用高阈值检测强角点,若失败则改用低阈值,平衡特征点数量与质量。
    • 检测到的角点坐标需根据网格位置偏移,转换为全局坐标((*vit).pt.x += j * wCell)。
3. 四叉树筛选关键点
keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,minBorderY, maxBorderY, mnFeaturesPerLevel[level], level);
  • 四叉树算法:将初步检测的角点(vToDistributeKeys)按空间分布均匀性筛选,最终保留 mnFeaturesPerLevel[level] 个关键点。
    • 原理:递归将区域划分为四个子节点,保留响应最强的角点,避免特征点聚集。
    • 终止条件:当子节点中仅剩一个角点或达到预设特征数量时停止分割。
4. 关键点属性设置
const int scaledPatchSize = PATCH_SIZE * mvScaleFactor[level];
keypoints[i].octave = level;          // 所属金字塔层级
keypoints[i].size = scaledPatchSize;  // 特征点邻域直径(尺度自适应)
  • 尺度不变性scaledPatchSize 根据金字塔层级缩放,高层级特征点对应更大的邻域,用于后续计算旋转和描述子。
  • 坐标修正:将筛选后的关键点坐标还原到原始图像坐标系(minBorderX/Y 偏移)。
5. 计算关键点方向
computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
  • 灰度质心法:通过计算图像块的一阶矩(m10m01),确定关键点的主方向,实现旋转不变性。
  • 预计算参数 umax:用于加速圆形区域的像素采样,确保方向计算的效率。

关键设计原理

  1. 多尺度处理

    • 通过图像金字塔(mvImagePyramid)实现尺度不变性,高层级检测大尺度特征,低层级检测细节。
    • 特征点邻域大小(PATCH_SIZE)随层级缩放,匹配图像分辨率变化。
  2. 均匀分布策略

    • 网格分块:初步分块检测避免特征点过度集中。
    • 四叉树筛选:二次优化分布,保证每层特征数量与质量。
  3. 效率优化

    • FAST检测加速:网格化检测减少无效区域遍历,双阈值策略平衡速度与鲁棒性。
    • 预计算参数:如 umax 和金字塔图像,减少运行时计算量。

应用场景

  • ORB-SLAM:用于实时特征提取与匹配,支撑相机位姿估计和地图构建。
  • 目标跟踪:均匀分布的特征点提升匹配稳定性,减少误匹配。

扩展说明

  • 浮点坐标问题:尽管代码中关键点坐标为整数,但在高层级金字塔中,坐标经缩放后可能为浮点数(如 mvScaleFactor 作用)。
  • 边界与异常处理:依赖 EDGE_THRESHOLD 避免越界,但未显式处理极端情况(如全图无角点),需前置步骤保证鲁棒性。

以下是ORBextractor::ComputeKeyPointsOctTree函数的逐行注释分析:

  1. 初始化多尺度关键点容器
allKeypoints.resize(nlevels);  // 根据金字塔层数调整容器维度
const float W = 30;           // 网格划分基准宽度
  1. 金字塔层级遍历
for (int level = 0; level < nlevels; ++level) {// 计算有效检测区域边界(EDGE_THRESHOLD通常取19像素)const int minBorderX = EDGE_THRESHOLD-3;  // 左边界留3像素缓冲const int minBorderY = minBorderX;        // 上边界同理const int maxBorderX = mvImagePyramid[level].cols-EDGE_THRESHOLD+3;  // 右有效边界const int maxBorderY = mvImagePyramid[level].rows-EDGE_THRESHOLD+3;  // 下有效边界
  1. 网格化特征检测
    vector<cv::KeyPoint> vToDistributeKeys;vToDistributeKeys.reserve(nfeatures*10);  // 预分配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); // 实际网格高度
  1. 网格遍历与FAST检测
    for(int i=0; i<nRows; i++) {const float iniY = minBorderY + i*hCell;  // 网格起始Y坐标float maxY = iniY + hCell + 6;            // 网格终止Y坐标(+6扩展检测范围)// 边界溢出处理if(iniY >= maxBorderY-3) continue;if(maxY > maxBorderY) maxY = maxBorderY;for(int j=0; j<nCols; j++) {const float iniX = minBorderX + j*wCell;  // 网格起始X坐标float maxX = iniX + wCell + 6;            // 网格终止X坐标// 边界溢出处理if(iniX >= maxBorderX-6) continue;if(maxX > maxBorderX) maxX = maxBorderX;
  1. 双阈值FAST检测策略
            vector<cv::KeyPoint> vKeysCell;// 先使用初始阈值iniThFAST检测FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),vKeysCell, iniThFAST, true);  // true表示使用非极大值抑制// 若未检测到,改用更低阈值minThFASTif(vKeysCell.empty()) {FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),vKeysCell, minThFAST, true);}
  1. 关键点坐标校正
            if(!vKeysCell.empty()) {for(auto vit=vKeysCell.begin(); vit!=vKeysCell.end(); vit++) {(*vit).pt.x += j*wCell;  // X坐标恢复全局坐标系(*vit).pt.y += i*hCell;  // Y坐标恢复全局坐标系vToDistributeKeys.push_back(*vit);  // 存入待分配容器}}
  1. 八叉树关键点分布
        vector<KeyPoint> & keypoints = allKeypoints[level];keypoints.reserve(nfeatures);// 执行四叉树分配算法(关键步骤)keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX,minBorderY, maxBorderY, mnFeaturesPerLevel[level], level);
  1. 关键点属性赋值
        const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level];for(int i=0; i<keypoints.size(); i++) {keypoints[i].pt.x += minBorderX;  // 补偿初始偏移量keypoints[i].pt.y += minBorderY;keypoints[i].octave = level;      // 记录金字塔层级keypoints[i].size = scaledPatchSize;  // 特征点邻域直径(尺度自适应)}
  1. 方向计算
    // 对所有层关键点计算主方向for (int level = 0; level < nlevels; ++level)computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
}

核心设计解析

  • 网格化检测:通过30x30网格划分实现特征点初步均匀分布,避免特征聚集
  • 双阈值策略:先使用较高阈值(iniThFAST)确保质量,失败后降阈值(minThFAST)保证数量
  • 边界缓冲:±3像素的边界处理既避免越界,又保证边缘特征的有效提取
  • 坐标校正:将网格局部坐标转换为金字塔层全局坐标,保持空间一致性
  • 八叉树分配:通过递归区域分割和响应值排序,实现特征点最优空间分布
  • 尺度自适应:scaledPatchSize根据金字塔缩放系数调整邻域大小,实现尺度不变性

注:EDGE_THRESHOLD的取值(通常为19)与ORB描述子计算相关,保证特征点周围有足够区域计算描述子。computeOrientation使用灰度质心法计算特征方向,umax为模式匹配预计算表。

相关文章:

  • Vision + Robot New Style
  • IP证书的作用与申请全解析:从安全验证到部署实践
  • day39 pythonCNN网络
  • DeepSeek实战:打造智能数据分析与可视化系统
  • QT 5.15.2 程序中文乱码
  • 如何处理 Python 入门难以进步的现象
  • 小样本学习
  • VirtualBox怎样安装Win10
  • 《P5507 机关》
  • windows本地虚拟机上运行docker-compose案例
  • 多台电脑共用一个ip地址可以吗?会怎么样
  • 解决线程安全问题
  • C++补充基础小知识:什么是接口类 和 抽象类?为什么要继承?
  • 易学探索助手-个人记录(十二)
  • 运用集合知识做斗地主案例
  • DOM和BOM的区别
  • 国内外AI编程工具对比(Trae对比Cursor)
  • CloudCompare——点云统计滤波
  • 关于JavaScript、TypeScript Module的配置和用法
  • 解决 AntV G6 使用 SVG 渲染脑图时节点文字过多导致拖动卡顿的问题
  • 能登上日本网站的代理服务器/seo职业
  • 怎么在欧美做网站推广/福州seo网站管理
  • 网站换空间上怎么办/移动网站推广如何优化
  • 网站的做网站公司/网络营销推广的基本手段
  • 做kegg通路富集的网站/公司网站制作要多少钱
  • 沈丘做网站去哪里/提供seo顾问服务适合的对象是