zoukankan      html  css  js  c++  java
  • 《视觉SLAM十四讲》笔记(ch12)

    ch12——回环检测

      主要目标:1.理解回环检测的比要性

            2.掌握基于词袋的外观式回环检测

               3.通过DBoW3的实验,学习词袋模型的实际用途

      SLAM主体(前端[提供特征点的提取和轨迹、地图的初值]和后端[负责对所有这些数据进行优化])主要目的:估计相机运动

      本讲将介绍主流视觉SLAM中检测回环的方式:词袋模型,并通过DBoW库上的实验程序,使读者得到更加直观的解释。

      回环检测的关键:如何有效的检测出相机经过同一个地方这件事。

      回环检测的作用:1)得到一个全局一致(Global Consistent)的估计,提高系统稳定性。

              2)由于回环检测提供了当前数据与所有历史数据的关联,在跟踪算法丢失后,可利用回环检测进行重定位。  

      仅有前端和局部后端——VO

      带有回环检测和全局后端的系统——SLAM

      实现回环检测的方法:1)基于里程计的几何关系(Odometry based):当我们发现相机运动到了之前的某个位置,检测它们有没有回环。

          这种方法的思路很直观。(1)但是由于累计误差的存在,我们往往没法正确地发现“运动到了之前的某个位置附近”这个事实,以至于回环检测也无从谈起。(2)并且回环检测的目标就是发现“相机回到之前位置的事实”,从而消除累计误差。而在此方法中,我们需要假设的就是“相机回到之前的位置”。所以,逻辑上存在问题。

                2)基于外观(Appearance based):和前端(前端可以向其提供特征点)、后端无关,仅根据两副图像的相似性来确定回环检测的关系。

            总结:基于外观的回环检测能在不同的场景下有效工作,是视觉SLAM中的主流方法,并被应用到不同的系统中。

      基于外观的回环检测:

        核心问题:如何计算图像间的相似性?

         由于像素灰度很容易受到环境光照和相机曝光的影响,而且即使光照不变,仅是相机视角发生变动,像素值也会发生大的变动,因此我们不能简单地考虑“将两副图像的灰度值相减”来评估图像是相似。

      考虑使用的方法:1)对两幅图像的特征点进行匹配,只要匹配数量大于一定值,就认为出现了回环。(存在的问题:特征匹配比较费时,当光照变化时特征描述可能不稳定等)。

              2)利用词袋模型。我们通常使用这种方法

    评估回环检测好坏的标准:准确率(P)、召回率(R)

      相关知识点:假阳性(False Positive,事实上不是回环,但算法给出的是回环)又称为感知偏差(Perceptual Aliasing)假阴性(False Negative,事实上是回环,但算法给出的不是回环)又称为感知变异(Perceptual Variability)

    评估标准:TP(true positive,真阳性。事实和算法吻合,是回环)、TN(true negative,真阴性。事实和算法吻合,不是回环)、FP、FN。我们希望TP、FP尽量高,TN、FN尽量低

    准确率(Precision):表示的是算法提取的所有回环中确实是真实回环的概率。Precision=TP/(TP+FP)

    召回率(Recall):表示的是在所有真实回环中被正确检测出来的概率。Recall=TP/(TP+FN)

    P和R是一对矛盾的统计量,想一想为什么这样说?

    在SLAM中,我们对准确率的要求更高,对召回率的要求较为宽容。

    词袋模型:

    词袋(Bag-of-Words(BoW)),目的是用“图像上有哪几种特征”来描述一副图像。强调的是words的有无,而不关心word出现的顺序。

    利用词袋模型判断图像间相似性的步骤:

     1)生成字典:根据K-means,我们可以把已经提取的大量特征点聚类成一个含有k个单词的字典。

        2)使用一种k叉树来表达字典。我们构建一个深度为d,每次分叉均为k的树,训练字典时,逐层使用K-means聚类。这种树结构可容纳k^d个单词。当在字典中查找单词时,只需要将它与树结构中每个中间节点的聚类中心比较,总共做d次比较,就可找到对应的单词。

    实践:演示如何训练生成一个ORB字典。代码如下:

     1 #include "DBoW3/DBoW3.h"
     2 #include <opencv2/core/core.hpp>
     3 #include <opencv2/highgui/highgui.hpp>
     4 #include <opencv2/features2d//features2d.hpp>
     5 #include <iostream>
     6 #include <vector>
     7 #include <string>
     8 
     9 using namespace std;
    10 using namespace cv;
    11 
    12 //本程序演示了如何根据data/目录下的十张图片训练字典
    13 int main(int argc,char** argv)
    14 {
    15     cout<<"reading images..."<<endl;
    16     vector<Mat> images;
    17     for(int i=0;i<10;++i)
    18     {
    19         string path="/home/cc/slambook/ch12/data/"+to_string(i+1)+".png";
    20         //cout<<path<<endl;
    21         images.push_back(imread(path));
    22     }
    23 
    24     //detect ORB features
    25     cout<<"detecting ORB features..."<<endl;
    26     Ptr<Feature2D> detector=ORB::create();
    27     vector<Mat> descriptors;
    28     for(Mat& image:images)
    29     {
    30         vector<KeyPoint> keypoints;
    31         Mat descriptor;
    32         detector->detectAndCompute(image,Mat(),keypoints,descriptor);
    33         descriptors.push_back(descriptor);
    34     }
    35 
    36     //create vocabulary
    37     cout<<"creating vocabulary..."<<endl;
    38 //    DBoW3::Vocabulary vocab;//默认k=10,d=5
    39     //我们也可以指定树的分叉数量及深度
    40     const int k=9;//配置分叉数为9
    41     const int L=3;//配置深度为3
    42 //    const WeightingType weight = TF_IDF;
    43 //    const ScoringType score = L1_NORM; //使用L1范数计算word之间的分数差
    44 //    const ScoreingType score=LI_NORM;//使用LI范数计算word之间的分数
    45 //    DBoW3::Vocabulary vocab(k,L,weight,score);
    46     DBoW3::Vocabulary vocab(k,L);
    47     vocab.create(descriptors);
    48     cout<<"vocabulary info: "<<vocab<<endl;
    49     vocab.save("vocabulary.yml.gz");
    50     cout<<"done"<<endl;
    51     return 0;
    52 }

      实际BoW使用时字典往往是从更大的数据集生成的。而且最好是来自与目标环境类似的地方。字典的规模越大意味着字典中单词量越丰富,就越容易找到与当前图像对应的单词,所以我们通常使用较大规模的字典,但是也不能大到超过计算能力和内存。

      如果我们想要获得更好的效果,可以自己训练一个更大的字典,或者使用别人训练好的,但是一档要注意字典使用的特征类型是否是一致的。

       2)利用字典计算两幅图像间的相似性

      TF-IDF(Term Frequency-Inverse Document Frequency),频率-逆文档频率。对于TF部分的思想是:某单词在一副图像中越常出现,它的区分度就越高。IDF部分的思想是:某单词在一副图像中出现的频率越低,它的区分度就越高。

      IDF-统计某个叶子节点中特征数量相对于所有特征数量的比例:,其中n表示所有特征的数量,表示某个叶子节点中特征数量

      TF-指某个特征在单副图像中出现的频率:,其中n表示图像A中一共出现的单词数目,表示图像A中单词出现的次数。

      =>的权重 

      对于图像A,我们可以定义组成它的Bag-of-Words:A={(),(),… ,()}=。我们就用这个向量描述一副图像A,是一个稀疏的向量,它的非零部分指示了图像A中含有哪些单词,而这些部分的值为TF-IDF的值。

      利用词袋模型计算两幅图像间的相似度的方式1:

      实践2:我们利用实践1中生成的字典,计算图像中的相似度。代码如下:

     

     1 #include "DBoW3/DBoW3.h"
     2 #include <opencv2/core/core.hpp>
     3 #include <opencv2/highgui/highgui.hpp>
     4 #include <opencv2/features2d/features2d.hpp>
     5 #include <iostream>
     6 #include <vector>
     7 #include <string>
     8 
     9 using namespace std;
    10 using namespace cv;
    11 
    12 //本程序演示了根据前面训练的字典计算相似性评分
    13 int main(int argc,char** argv)
    14 {
    15     //read the images and database
    16     cout<<"reading database..."<<endl;
    17     DBoW3::Vocabulary vocab("/home/cc/slambook/ch12/build/vocabulary.yml.gz");
    18     if(vocab.empty())
    19     {
    20         cerr<<"Vocabulary dose not exist."<<endl;
    21         return 1;
    22     }
    23     cout<<"reading images..."<<endl;
    24     vector<Mat> images;
    25     for(int i=0;i<10;i++)
    26     {
    27         string path="/home/cc/slambook/ch12/data/"+to_string(i+1)+".png";
    28         images.push_back(imread(path));
    29     }
    30 
    31     //detect ORB features
    32     cout<<"detecting ORB features..."<<endl;
    33     Ptr<Feature2D> detector=ORB::create();
    34     vector<Mat> descriptors;
    35     for(Mat& image:images)
    36     {
    37         vector<KeyPoint> keypoints;
    38         Mat descriptor;
    39         detector->detectAndCompute(image,Mat(),keypoints,descriptor);
    40         descriptors.push_back(descriptor);
    41     }
    42 
    43     //images:
    44     cout<<"comparing images with images "<<endl;
    45     for(int i=0;i<images.size();i++)
    46     {
    47         DBoW3::BowVector v1;
    48         vocab.transform(descriptors[i],v1);
    49         for(int j=i;j<images.size();j++)
    50         {
    51             DBoW3::BowVector v2;
    52             vocab.transform(descriptors[j],v2);//i->j
    53             double score=vocab.score(v1,v2);
    54             cout<<"image "<<i<<" vs image "<<j<<":"<<score<<endl;
    55         }
    56         cout<<endl;
    57     }
    58 
    59     //or with database:
    60     cout<<"comparing images with database"<<endl;
    61     DBoW3::Database db(vocab,false,0);
    62     for(int i=0;i<descriptors.size();i++)
    63     {
    64         db.add(descriptors[i]);
    65     }
    66     cout<<"database info: "<<db<<endl;
    67     for(int i=0;i<descriptors.size();i++)
    68     {
    69         DBoW3::QueryResults ret;
    70         db.query(descriptors[i],ret,4);//max result=4
    71         cout<<"searching for image "<<i<<" returns "<<ret<<endl<<endl;
    72     }
    73     cout<<"done."<<endl;
    74 }

     

    实验结果:

      各图像之间的得分为:

        

      在数据库查询时,DBoW对上面的分数进行排序,给出最相似的结果:    

        

        

        由此可知,明显相似的是图像1和图像10(分别对应0和9),但其相似评分也只有0.307861,而其他图像间的相似评分大约为0.2左右,在数值上这个差别不是十分明显。

      考虑原因:在机器学习领域,如果代码没有出错,而结果不满意,我们首先怀疑“网络结构是否够大,层数是否够深,数据样本是否够多”等。

           在教材中,训练了一个样本更多的字典,得到的结果由于上面这个结果。由此说明增大字典的训练样本是有益的,同理,也可以增大字典的规模(即增大L、K)。

       利用词袋进行回环检测的缺点:1)算法完全依赖于外观而没有利用其他任何的几何信息i,会导致外观相似的图像易被当作回环

                    2)由于词袋不在乎单词顺序,只在意单词有无的表达方式,更容易引发感知偏差。

      所以要在检测到回环之后进行验证,方式有:

        1)时间上的一致性检测:如果能在一段时间中一直检测到回环,才认为是正确的回环。

        2)空间上的一致性检测:对回环检测到的两个帧进行匹配,估计相机的运动。然后再把运动放到之前的Pose Graph中,检查与之前的估计是否有很大的出入。

     

        

      

  • 相关阅读:
    Java Spring Boot VS .NetCore (十) Java Interceptor vs .NetCore Interceptor
    Java Spring Boot VS .NetCore (九) Spring Security vs .NetCore Security
    IdentityServer4 And AspNetCore.Identity Get AccessToken 问题
    Java Spring Boot VS .NetCore (八) Java 注解 vs .NetCore Attribute
    Java Spring Boot VS .NetCore (七) 配置文件
    Java Spring Boot VS .NetCore (六) UI thymeleaf vs cshtml
    Java Spring Boot VS .NetCore (五)MyBatis vs EFCore
    Java Spring Boot VS .NetCore (四)数据库操作 Spring Data JPA vs EFCore
    Java Spring Boot VS .NetCore (三)Ioc容器处理
    Java Spring Boot VS .NetCore (二)实现一个过滤器Filter
  • 原文地址:https://www.cnblogs.com/cc111/p/9630425.html
Copyright © 2011-2022 走看看