• ORB-SLAM2 ---- ORBmatcher::SearchByBoW函数


    1.本函数的作用        

            匹配两帧中的特征点,与ORBmatcher::SearchForInitialization相比,ORBmatcher::SearchForTriangulation利用了词袋BoW加速了匹配速度。

    ORB-SLAM2 ---- ORBmatcher::SearchForInitialization函数_Courage2022的博客-CSDN博客https://blog.csdn.net/qq_41694024/article/details/126319167?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22126319167%22%2C%22source%22%3A%22qq_41694024%22%7D

    2.词袋原理 Bow

    直观理解词袋

    词袋的形成:

    提取ORB特征点、对描述子进行聚类;有新的一帧传进来时、我们将其在词袋内进行遍历得到很多单词,可以用频率来表示。

    3 如何制作BoW 

    3.1 离线训练

            首先图像提取ORB 特征点,将描述子通过 k-means 进行聚类,根据设定的树的分支数和深度,从叶子节点开始聚类一直到根节点,最后得到一个非常大的 vocabulary tree,

            1、遍历所有的训练图像,对每幅图像提取ORB特征点。

            2、设定vocabulary tree的分支数K和深度L。将特征点的每个描述子用 K-means聚类,变成 K个集合, 作为vocabulary tree 的第1层级,然后对每个集合重复该聚类操作,就得到了vocabulary tree的第2层级,继续迭代最后得到满足条件的vocabulary tree,它的规模通常比较大,比如ORB-SLAM2使用的离线字典就有108万+ 个节点。

            3、离根节点最远的一层节点称为叶子或者单词 Word。根据每个Word 在训练集中的相关程度给定一个权重weight,训练集里出现的次数越多,说明辨别力越差,给与的权重越低。(这个我感觉像哈夫曼树....)

    3.2 在线图像生成BoW向量 

    1、对新来的一帧图像进行ORB特征提取,得到一定数量(一般几百个)的特征点,描述子维度和 vocabulary tree中的一致

    2、对于每个特征点的描述子,从离线创建好的vocabulary tree中开始找自己的位置,从根节点开始, 用该描述子和每个节点的描述子计算汉明距离,选择汉明距离最小的作为自己所在的节点,一直遍历到叶子节点。 整个过程是这样的。紫色的线表示一个特征点从根节点到叶子节点的过程。

    3.3 代码解析: Frame::ComputeBoW函数

    ORB-SLAM2 ---- Frame::ComputeBoW函数_Courage2022的博客-CSDN博客icon-default.png?t=M666https://blog.csdn.net/qq_41694024/article/details/126328833

    4 ORBmatcher::SearchByBoW函数解析

    4.1 该函数的作用及参数

    1. /*
    2. * @brief 通过词袋,对关键帧的特征点进行跟踪
    3. * 步骤
    4. * Step 1:分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点)
    5. * Step 2:遍历KF中属于该node的特征点
    6. * Step 3:遍历F中属于该node的特征点,寻找最佳匹配点
    7. * Step 4:根据阈值 和 角度投票剔除误匹配
    8. * Step 5:根据方向剔除误匹配的点
    9. * @param pKF 关键帧
    10. * @param F 当前普通帧
    11. * @param vpMapPointMatches F中地图点对应的匹配,NULL表示未匹配
    12. * @return 成功匹配的数量
    13. */
    14. int ORBmatcher::SearchByBoW(KeyFrame* pKF,Frame &F, vector &vpMapPointMatches)
    15. {
    16. // 获取该关键帧的地图点
    17. const vector vpMapPointsKF = pKF->GetMapPointMatches();
    18. // 和普通帧F特征点的索引一致
    19. vpMapPointMatches = vector(F.N,static_cast(NULL));
    20. // 取出关键帧的词袋特征向量
    21. const DBoW2::FeatureVector &vFeatVecKF = pKF->mFeatVec;
    22. int nmatches=0;
    23. // 特征点角度旋转差统计用的直方图
    24. vector<int> rotHist[HISTO_LENGTH];
    25. for(int i=0;i
    26. rotHist[i].reserve(500);
    27. // 将0~360的数转换到0~HISTO_LENGTH的系数
    28. // !原作者代码是 const float factor = 1.0f/HISTO_LENGTH; 是错误的,更改为下面代码
    29. const float factor = HISTO_LENGTH/360.0f;
    30. // We perform the matching over ORB that belong to the same vocabulary node (at a certain level)
    31. // 将属于同一节点(特定层)的ORB特征进行匹配
    32. DBoW2::FeatureVector::const_iterator KFit = vFeatVecKF.begin();
    33. DBoW2::FeatureVector::const_iterator Fit = F.mFeatVec.begin();
    34. DBoW2::FeatureVector::const_iterator KFend = vFeatVecKF.end();
    35. DBoW2::FeatureVector::const_iterator Fend = F.mFeatVec.end();
    36. while(KFit != KFend && Fit != Fend)
    37. {
    38. // Step 1:分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点)
    39. // first 元素就是node id,遍历
    40. if(KFit->first == Fit->first)
    41. {
    42. // second 是该node内存储的feature index
    43. const vector<unsigned int> vIndicesKF = KFit->second;
    44. const vector<unsigned int> vIndicesF = Fit->second;
    45. // Step 2:遍历KF中属于该node的特征点
    46. for(size_t iKF=0; iKFsize(); iKF++)
    47. {
    48. // 关键帧该节点中特征点的索引
    49. const unsigned int realIdxKF = vIndicesKF[iKF];
    50. // 取出KF中该特征对应的地图点
    51. MapPoint* pMP = vpMapPointsKF[realIdxKF];
    52. if(!pMP)
    53. continue;
    54. if(pMP->isBad())
    55. continue;
    56. const cv::Mat &dKF= pKF->mDescriptors.row(realIdxKF); // 取出KF中该特征对应的描述子
    57. int bestDist1=256; // 最好的距离(最小距离)
    58. int bestIdxF =-1 ;
    59. int bestDist2=256; // 次好距离(倒数第二小距离)
    60. // Step 3:遍历F中属于该node的特征点,寻找最佳匹配点
    61. for(size_t iF=0; iFsize(); iF++)
    62. {
    63. // 和上面for循环重名了,这里的realIdxF是指普通帧该节点中特征点的索引
    64. const unsigned int realIdxF = vIndicesF[iF];
    65. // 如果地图点存在,说明这个点已经被匹配过了,不再匹配,加快速度
    66. if(vpMapPointMatches[realIdxF])
    67. continue;
    68. const cv::Mat &dF = F.mDescriptors.row(realIdxF); // 取出F中该特征对应的描述子
    69. // 计算描述子的距离
    70. const int dist = DescriptorDistance(dKF,dF);
    71. // 遍历,记录最佳距离、最佳距离对应的索引、次佳距离等
    72. // 如果 dist < bestDist1 < bestDist2,更新bestDist1 bestDist2
    73. if(dist
    74. {
    75. bestDist2=bestDist1;
    76. bestDist1=dist;
    77. bestIdxF=realIdxF;
    78. }
    79. // 如果bestDist1 < dist < bestDist2,更新bestDist2
    80. else if(dist
    81. {
    82. bestDist2=dist;
    83. }
    84. }
    85. // Step 4:根据阈值 和 角度投票剔除误匹配
    86. // Step 4.1:第一关筛选:匹配距离必须小于设定阈值
    87. if(bestDist1<=TH_LOW)
    88. {
    89. // Step 4.2:第二关筛选:最佳匹配比次佳匹配明显要好,那么最佳匹配才真正靠谱
    90. if(static_cast<float>(bestDist1)static_cast<float>(bestDist2))
    91. {
    92. // Step 4.3:记录成功匹配特征点的对应的地图点(来自关键帧)
    93. vpMapPointMatches[bestIdxF]=pMP;
    94. // 这里的realIdxKF是当前遍历到的关键帧的特征点id
    95. const cv::KeyPoint &kp = pKF->mvKeysUn[realIdxKF];
    96. // Step 4.4:计算匹配点旋转角度差所在的直方图
    97. if(mbCheckOrientation)
    98. {
    99. // angle:每个特征点在提取描述子时的旋转主方向角度,如果图像旋转了,这个角度将发生改变
    100. // 所有的特征点的角度变化应该是一致的,通过直方图统计得到最准确的角度变化值
    101. float rot = kp.angle-F.mvKeys[bestIdxF].angle;// 该特征点的角度变化值
    102. if(rot<0.0)
    103. rot+=360.0f;
    104. int bin = round(rot*factor);// 将rot分配到bin组, 四舍五入, 其实就是离散到对应的直方图组中
    105. if(bin==HISTO_LENGTH)
    106. bin=0;
    107. assert(bin>=0 && bin
    108. rotHist[bin].push_back(bestIdxF); // 直方图统计
    109. }
    110. nmatches++;
    111. }
    112. }
    113. }
    114. KFit++;
    115. Fit++;
    116. }
    117. else if(KFit->first < Fit->first)
    118. {
    119. // 对齐
    120. KFit = vFeatVecKF.lower_bound(Fit->first);
    121. }
    122. else
    123. {
    124. // 对齐
    125. Fit = F.mFeatVec.lower_bound(KFit->first);
    126. }
    127. }
    128. // Step 5 根据方向剔除误匹配的点
    129. if(mbCheckOrientation)
    130. {
    131. // index
    132. int ind1=-1;
    133. int ind2=-1;
    134. int ind3=-1;
    135. // 筛选出在旋转角度差落在在直方图区间内数量最多的前三个bin的索引
    136. ComputeThreeMaxima(rotHist,HISTO_LENGTH,ind1,ind2,ind3);
    137. for(int i=0; i
    138. {
    139. // 如果特征点的旋转角度变化量属于这三个组,则保留
    140. if(i==ind1 || i==ind2 || i==ind3)
    141. continue;
    142. // 剔除掉不在前三的匹配对,因为他们不符合“主流旋转方向”
    143. for(size_t j=0, jend=rotHist[i].size(); j
    144. {
    145. vpMapPointMatches[rotHist[i][j]]=static_cast(NULL);
    146. nmatches--;
    147. }
    148. }
    149. }
    150. return nmatches;
    151. }

    4.2  分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点

    1. // 获取该关键帧的地图点
    2. const vector vpMapPointsKF = pKF->GetMapPointMatches();
    3. // 和普通帧F特征点的索引一致
    4. vpMapPointMatches = vector(F.N,static_cast(NULL));
    5. // 取出关键帧的词袋特征向量
    6. const DBoW2::FeatureVector &vFeatVecKF = pKF->mFeatVec;
    7. int nmatches=0;
    8. // 特征点角度旋转差统计用的直方图
    9. vector<int> rotHist[HISTO_LENGTH];
    10. for(int i=0;i
    11. rotHist[i].reserve(500);
    12. // 将0~360的数转换到0~HISTO_LENGTH的系数
    13. // !原作者代码是 const float factor = 1.0f/HISTO_LENGTH; 是错误的,更改为下面代码
    14. const float factor = HISTO_LENGTH/360.0f;
    15. // We perform the matching over ORB that belong to the same vocabulary node (at a certain level)
    16. // 将属于同一节点(特定层)的ORB特征进行匹配
    17. DBoW2::FeatureVector::const_iterator KFit = vFeatVecKF.begin();
    18. DBoW2::FeatureVector::const_iterator Fit = F.mFeatVec.begin();
    19. DBoW2::FeatureVector::const_iterator KFend = vFeatVecKF.end();
    20. DBoW2::FeatureVector::const_iterator Fend = F.mFeatVec.end();
    21. while(KFit != KFend && Fit != Fend)
    22. {
    23. // Step 1:分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点)
    24. // first 元素就是node id,遍历
    25. if(KFit->first == Fit->first)
    26. {
    27. // second 是该node内存储的feature index
    28. const vector<unsigned int> vIndicesKF = KFit->second;
    29. const vector<unsigned int> vIndicesF = Fit->second;
    class BowVector: public std::map
    class FeatureVector: public std::mapunsigned int> >

            pKF是关键帧、F是当前普通帧。用vpMapPointsKF容器提取关键帧中的地图点。vpMapPointMatches 容器记录匹配关系,建立指向关键帧词袋特征向量的引用vFeatVecKF、匹配数目的指示器nmatches、建立旋转直方图rotHist、建立关键帧和普通帧的FeatureVector的迭代器KFit、Fit、KFend、Fend

            由Frame::ComputeBoW函数的解析我们知:如果两个描述子要成功匹配,其必属于同一个node。

    4.3  遍历KF中属于该node的特征点

    4.4 遍历F中属于该node的特征点,寻找最佳匹配点

    1. while(KFit != KFend && Fit != Fend)
    2. {
    3. // Step 1:分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点)
    4. // first 元素就是node id,遍历
    5. if(KFit->first == Fit->first)
    6. {
    7. // second 是该node内存储的feature index
    8. const vector<unsigned int> vIndicesKF = KFit->second;
    9. const vector<unsigned int> vIndicesF = Fit->second;
    10. // Step 2:遍历KF中属于该node的特征点
    11. for(size_t iKF=0; iKFsize(); iKF++)
    12. {
    13. // 关键帧该节点中特征点的索引
    14. const unsigned int realIdxKF = vIndicesKF[iKF];
    15. // 取出KF中该特征对应的地图点
    16. MapPoint* pMP = vpMapPointsKF[realIdxKF];
    17. if(!pMP)
    18. continue;
    19. if(pMP->isBad())
    20. continue;
    21. const cv::Mat &dKF= pKF->mDescriptors.row(realIdxKF); // 取出KF中该特征对应的描述子
    22. int bestDist1=256; // 最好的距离(最小距离)
    23. int bestIdxF =-1 ;
    24. int bestDist2=256; // 次好距离(倒数第二小距离)
    25. // Step 3:遍历F中属于该node的特征点,寻找最佳匹配点
    26. for(size_t iF=0; iFsize(); iF++)
    27. {
    28. // 和上面for循环重名了,这里的realIdxF是指普通帧该节点中特征点的索引
    29. const unsigned int realIdxF = vIndicesF[iF];
    30. // 如果地图点存在,说明这个点已经被匹配过了,不再匹配,加快速度
    31. if(vpMapPointMatches[realIdxF])
    32. continue;
    33. const cv::Mat &dF = F.mDescriptors.row(realIdxF); // 取出F中该特征对应的描述子
    34. // 计算描述子的距离
    35. const int dist = DescriptorDistance(dKF,dF);
    36. // 遍历,记录最佳距离、最佳距离对应的索引、次佳距离等
    37. // 如果 dist < bestDist1 < bestDist2,更新bestDist1 bestDist2
    38. if(dist
    39. {
    40. bestDist2=bestDist1;
    41. bestDist1=dist;
    42. bestIdxF=realIdxF;
    43. }
    44. // 如果bestDist1 < dist < bestDist2,更新bestDist2
    45. else if(dist
    46. {
    47. bestDist2=dist;
    48. }
    49. }

            KFit、Fit分别是指向关键帧、普通帧的FeatureVector容器的迭代器。如果他们的nodeID相同则进入匹配,若KFit的node小于Fit的node,则调整迭代器KFit指向大于等于Fit迭代器所指向的node大小的地方,方便下次匹配。

    1. else if(KFit->first < Fit->first)
    2. {
    3. // 对齐
    4. KFit = vFeatVecKF.lower_bound(Fit->first);
    5. }
    6. else
    7. {
    8. // 对齐
    9. Fit = F.mFeatVec.lower_bound(KFit->first);
    10. }

            若node相同,则依次匹配利用选择排序,和前面讲的就完全一样了。

  • 相关阅读:
    批量归一化(部分理解)
    MySQL索引优化
    java sql字符串解析
    (十三) minAreaRect函数
    uni-fab彩色图标按钮
    ctfshow-web12(glob绕过)
    云原生|kubernetes|k8s下部署SQLServer以及Navicat连接SQLServer报错:远程主机强迫关闭了一个现有的连接 错误的解决
    cartgrapher ukf 代码清晰属实不错
    K8S故障处理指南:网络问题排查思路
    已加载插件:fastestmirror, langpacks /var/run/yum.pid 已被锁定,PID 为 2686 的另一个程序正在运行。
  • 原文地址:https://blog.csdn.net/qq_41694024/article/details/126322962