ORBLoopDetector 基于ORB特征提取的DLoopDetector

这个假期为了完成毕设,一直在研究回环检测的算法,之前一直在研究ORBSLAM2,希望从中寻找到一部分回环检测算法的研究思路。但事实上并不尽如人意,其回环检测虽然严谨,但实在太过于复杂,不仅运用常见的词袋模型,还运用了其他一些共视点(MapPoint)等等这些内容。于是我重新在网络上进行了搜索,试图寻找基于词袋模型的回环检测算法,于是乎,找到了DBoW2的作者Galvez-López D写的一篇论文 Bags of Binary Words for Fast Place Recognition in Image Sequences , 这篇文章介绍了关于利用词袋模型进行回环检测的很多思路。尤其是利用归一化得分进行计算,解决了我一直以来对于大批量图片如何进行相似度匹配的疑惑。

除此之外,我还阅读了 Bags of Binary Words for Fast Place Recognition in Image Sequences------阅读笔记 和 基于词袋模型DBoW2的回环检测实现DLoopDetector (c++) 的两篇博客文章,都让我对整体回环检测的代码有了很多的理解。于是,我下载并编译了一下DLoopDetector,但事实上随着深入的研究我发现DLoopDetector提供的demo并不是我原来想象当中的利用ORB算子进行特征提取,而是利用BRIEF算子进行的一次提取,而且这当中的BRIEF与ORB中的BRIEF描述子又存在很大很大的差别,一个是使用vector进行存储,而另一个则是使用cv::Mat数据类型进行存储,也就是说如果想单纯的只改特征提取部分是不可能也不现实的,于是,我从头到尾把DLoopDetector的源代码重新读了一遍,并编写了注释,以便日后查看。(如果各位不想花太多时间在理解代码上面,可以扫描文章后面的赞赏码对我进行微信打赏,并添加我的微信号,我将把源码注释分享给您)

这里我也忍不住想吐槽一下关于DLoopDetector的作者,他应该也发现了cv::Mat往vector转换的一些问题,但他并没有在原始的DBoW2或者别的代码中进行修改,而是在DBoW2中的demo文件中添加了一个新函数,解决了两种数据类型接口之间的不对等问题。

void changeStructure(const cv::Mat &plain, vector<cv::Mat> &out)
{
  out.resize(plain.rows);
  for(int i = 0; i < plain.rows; ++i)
  {
    out[i] = plain.row(i);
  }
}

对于这个函数的具体调用方法则是在调用cv当中的detectAndCompute函数进行描述子(Mat类型)的提取之后,将其进行类型转换的。

    stringstream ss;
    ss << "images/image" << i << ".png";
    cv::Mat image = cv::imread(ss.str(), 0);
    cv::Mat mask;
    vector<cv::KeyPoint> keypoints;
    cv::Mat descriptors;
    orb->detectAndCompute(image, mask, keypoints, descriptors);
    features.push_back(vector<cv::Mat >());
    changeStructure(descriptors, features.back());

(features是之前定义的一个vector<vector<cv::Mat > > features)

亦或许是DBoW3的作者发现了有些图像匹配需要用vector类型的描述子,而本身opencv提取的描述子又是Mat类型,于是在DBoW3添加了别的函数将transform中的vector传递的问题解决了,当然这是后话,对于DBoW2而言并不能这样干,至少在我看来DLoopDetector很大一部分都是依赖于DBoW2,如果在这里想调用DBoW3,而别的地方又不进行过大改动保持使用DBoW2,可能会使代码变得复杂,理解起来也很困难,有时也有可能会弄混,因此这里我的解决方案使尽可能的利用changeStructure函数对类型进行转换,以便实现接口的统一。

接下来,就是最核心的两部分代码了,首先是我修改的特征提取算法部分:

    //读取每一帧图像
    cv::Mat im = cv::imread(filenames[i].c_str(), 0); // grey scale
    //进行目标特征提取
    extractor(im, keys, descriptors);

    //此处为展示目标提取的角点
    cv::Mat outimg1;
    drawKeypoints(im, keys, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    if(m_show)
      DUtilsCV::GUI::showImage(outimg1, true, &win, 10);

    // add image to the collection and check if there is some loop
    DetectionResult result;
    //检测当前图片与之前的若干张图片是否构成回环
    detector.detectLoop(keys, descriptors, result);

关于extractor部分,我进行了大部分的修改,尤其是将数据类型全部改为ORB的相关类型,这里我有点纳闷,原作者也提供了ORB的数据类型,但为什么就是不肯将ORB特征提取算法的demo公开出来呢,害得我还得花这么多时间去理解。

下面是ORBExtractor类的定义:

/// This functor extracts ORB descriptors in the required format
class ORBExtractor: public FeatureExtractor<cv::Mat>
{
public:
  /** 
   * Extracts features from an image
   * @param im image
   * @param keys keypoints extracted
   * @param descriptors descriptors extracted
   */
  void operator()(const cv::Mat &im, 
    vector<cv::KeyPoint> &keys, vector<cv::Mat> &descriptors) const override;
  /**
   * Creates the orb extractor with the given pattern file
   * @param pattern_file
   */
  ORBExtractor();

private:
    Ptr< Feature2D > detectors;
};

下面是关于()的运算符重载,这一部分与原来的BRIEF算子的计算相比完全不一样 。

void ORBExtractor::operator() (const cv::Mat &im, 
  vector<cv::KeyPoint> &keys, vector<cv::Mat> &descriptors) const
{
  Mat descriptor;
  // 检测以及计算角点和描述子
  detectors->detectAndCompute(im, Mat(), keys, descriptor);
  // 将描述子格式从Mat型转换为vector型进行存储,方便后续的计算
  changeStructure(descriptor, descriptors);
}

在这一部分就基本实现了接口的匹配,为后面的 detectLoop(keys, descriptors, result) 提供了接口。

然后是回环检测部分,我觉得这一部分是回环检测前期准备工作的重中之重,即将vector类型的描述子传递到bowvec中,接下来的分数计算和十四讲中的分数计算基本相似,不存在太大的区别,其他关于回环检测的代码,我觉得Galvez-López D博士已经写的很好了,就后期基本上利用它的代码进行匹配的。

扫描二维码关注公众号,回复: 15664299 查看本文章
  //如果是直接索引那么要得到描述子,词典,特征值和索引层
  if(m_params.geom_check == GEOM_DI)
    m_database->getVocabulary()->transform(descriptors, bowvec, featvec,
      m_params.di_levels);
  else
    m_database->getVocabulary()->transform(descriptors, bowvec);

总的来说这几天修改代码,帮助我深入理解了一下关于ORB特征提取算法的描述子计算那一部分内容,之前一直以为是每一个对应角点计算一个描述子(一个0或者1)得到Mat矩阵中的一行,然而在利用Clion进行debug以及看书之后,立刻改变了我的想法,我才发现事实上是一个角点总共要提取256个描述子(或者128),总共有32byte,当然Mat类型的行数对应着相应的角点数目,这一点我们想错。而一个vector类型的描述子则代表着一个幅图所对应的描述子,vector中存放的一个Mat则代表一个描述子,有点感觉将原来opencv提取的描述子空间扩容而实际内容不变的感觉。

下面两幅图是我的实验结果:

 第一幅图是我修改后的图片,第二幅图则是原始demo的图片。相比较而言,在特征提取方面,ORB用时较多,这主要是因为,ORB除了要计算描述子之外,还需要计算角点,相比之下,BRIEF用时较短;而回环检测时间方面,ORB的平均用时较少,我推测主要可能ORB特征提取了之后,较BRIEF的描述子数量方面少了较多,筛选了一些不需要的描述子,但总体上回环检测上面两者时间差距并不太明显;最终回环检测数目上,可以发现ORB特征提取上较BRIEF而言少了将近一半,据此我推测,可能ORB特征提取过于严苛,可能与BRIEF而言只要ORB相似度出现差异就会不匹配,当然这也可以理解,因为在ratslam的代码中也有相同路线情况下,回环还是创建了新的视觉模板。

接下来,我觉得我的下一步工作,是在这个回环检测的基础上按照之前写的一个筛选关键帧的流程图去编写一下新的代码。当然,我觉得原始的opencv的特征提取也使得特征点过于集中,对于特征识别而言容易漏掉别的信息,因此,也在考虑加入ORBSLAM2的ORB特征提取算法。总体而言,这一部分的测试还是很成功的,为我的毕设完成了很大一部分的工作!

当然,如果有小伙伴想要查看关于ORBLoopDetector的代码,或者DLoopDetector代码的注释的话可以扫描一下我的赞赏码鼓励一下我,添加我的微信,我分享给你们。如果有小伙伴觉得这篇文章对你有帮助的话也可以扫描一下赞赏码表示一下对我的鼓励,谢谢!

猜你喜欢

转载自blog.csdn.net/loveSIYU/article/details/116379278