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

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为模式匹配预计算表。

http://www.xdnf.cn/news/9592.html

相关文章:

  • 【C语言】详解 指针
  • 使用 PySpark 从 Kafka 读取数据流并处理为表
  • 【25软考网工】第九章 网络管理(1)网络管理基础、SNMP
  • 端到端测试最佳实践:从入门到精通的完整指南
  • vue+ts+TinyEditor 是基于 Quill 2.0 开发的富文本编辑器,提供丰富的扩展功能,适用于现代 Web 开发的完整安装使用教程
  • 集成电路制造设备防震基座选型指南:为稳定护航-江苏泊苏系统集成有限公司
  • 手机如何压缩文件为 RAR 格式:详细教程与工具推荐
  • 井喷式增长下的证件缺口:特种设备人才供需矛盾如何破局?
  • 数值积分实验
  • 深入理解计算机科学中的“递归”:原理、应用与优化
  • vue3+Pinia+element-plus 后台管理系统项目实战
  • 安全,稳定可靠的政企即时通讯数字化平台
  • 金山云Q1营收19.7亿元 AI持续释放业务增长新动能
  • 【第2章 绘制】2.13 坐标变换
  • 数据拟合实验
  • IO 中的阻塞、非阻塞、同步、异步及五种IO模型
  • 服务器定时任务查看和编辑
  • SpringBoot Controller接收参数方式
  • Senna代码解读
  • SQLite软件架构与实现源代码浅析
  • 跨平台开发框架electron
  • 【Linux学习笔记】深入理解动静态库本质及其制作
  • 嵌入式学习笔记 - 用typedef定义函数指针
  • 网络安全十大漏洞
  • 22.代理模式:思考与解读
  • MongoDB选择理由
  • Java设计模式之解释器模式详解
  • flutter使用html_editor_enhanced: ^2.6.0后,编辑框无法获取焦点,无法操作
  • 计算机网络 - 关于IP相关计算题
  • BugKu Web渗透之矛盾