视觉SLAM笔记(59) 相似度计算


1. 理论

有了字典之后,给定任意特征 f f i,只要在字典树中逐层查找,最后都能找到与之对应的单词 w w j
当字典足够大时,可以认为 f f i w w j 来自同一类物体
尽管没有理论上的保证,仅是在聚类意义下这样说

那么,假设一张图像中提取了 N 个特征,找到这 N 个特征对应的单词之后
相当于拥有了该图像在单词列表中的分布,或者直方图

直观上说(或理想情况下),相当于是说“这张图里有一个人和一辆汽车”这样的意思了
根据 Bag-of-Words 的说法,不妨认为这是一个 Bag
注意到这种做法中,对所有单词都是“一视同仁”的——有就是有,没有就是没有
这样做好不好呢?考虑到,不同的单词在区分性上的重要性并不相同
例如“的”、“是”这样的字可能在许许多多的句子中出现,无法根据它们判别句子的类型

但如果有“文档”、“足球”这样的单词,对判别句子的作用就更大一些,可以说它们提供了更多信息
所以概括的话,希望对单词的区分性或重要性加以评估,给它们不同的权值以起到更好的效果

在文本检索中,常用的一种做法称为 TF-IDF(Term Frequency– Inverse Document Frequency),或译频率-逆文档频率
TF 部分的思想是,某单词在一个图像中经常出现,它的区分度就高
另一方面, IDF 的思想是,某单词在字典中出现的频率越低,则分类图像时区分度越高

在词袋模型中,在建立字典时可以考虑 IDF 部分
统计某个叶子节点 wi 中的特征数量相对于所有特征数量的比例,作为 IDF 部分
假设所有特征数量为 n, w w i 数量为 n n i,那么该单词的 IDF 为:
在这里插入图片描述
另一方面, TF 部分则是指某个特征在单个图像中出现的频率
假设图像 A 中,单词 w w i 出现了 n n i 次,而一共出现的单词次数为 n,那么 TF 为:

在这里插入图片描述
于是 w w i 的权重等于 TF 乘 IDF 之积:

在这里插入图片描述

考虑权重以后,对于某个图像 A,它的特征点可对应到许多个单词,组成它的 Bag-of-Words:
在这里插入图片描述
由于相似的特征可能落到同一个类中,因此实际的 vA 中会存在大量的零
无论如何,通过词袋,用单个向量 vA 描述了一个图像 A
这个向量 vA 是一个稀疏的向量
它的非零部分指示出图像 A 中含有哪些单词,而这些部分的值为 TF-IDF 的值

接下来的问题是:
给定 vA 和 vB,如何计算它们的差异
这个问题和范数定义的方式一样,存在若干种解决方式,比如 L1 范数形式:
在这里插入图片描述


2. 相似度的计算

视觉SLAM笔记(58) 字典 中已对十张图像生成了字典 vocabulary.yml.gz
这次使用此字典,生成 Bagof-Words 并比较它们的差异,看看与实际有什么不同

#include "DBoW3/DBoW3.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <iostream>
#include <vector>
#include <string>

using namespace cv;
using namespace std;

int main( int argc, char** argv )
{
    // 读取图像和数据库
    cout<<"reading database"<<endl;
    DBoW3::Vocabulary vocab("../vocabulary.yml.gz");
    // DBoW3::Vocabulary vocab("../vocab_larger.yml.gz");
    if ( vocab.empty() )
    {
        cerr<<"Vocabulary does not exist."<<endl;
        return 1;
    }
    cout<<"reading images... "<<endl;
    vector<Mat> images; 
    for ( int i=0; i<10; i++ )
    {
        string path = "../../058/data/"+to_string(i+1)+".png";
        images.push_back( imread(path) );
    }
    
    // 在本例中,将图像与自己生成的词汇表进行比较,这可能导致过度拟合
    // 检测ORB功能
    cout<<"detecting ORB features ... "<<endl;
    Ptr< Feature2D > detector = ORB::create();
    vector<Mat> descriptors;
    for ( Mat& image:images )
    {
        vector<KeyPoint> keypoints; 
        Mat descriptor;
        detector->detectAndCompute( image, Mat(), keypoints, descriptor );
        descriptors.push_back( descriptor );
    }
    
    // 可以直接比较图像,也可以将一个图像与数据库进行比较
    // 图像 :
    cout<<"comparing images with images "<<endl;
    for ( int i=0; i<images.size(); i++ )
    {
        DBoW3::BowVector v1;
        vocab.transform( descriptors[i], v1 );
        for ( int j=i; j<images.size(); j++ )
        {
            DBoW3::BowVector v2;
            vocab.transform( descriptors[j], v2 );
            double score = vocab.score(v1, v2);
            cout<<"image "<<i<<" vs image "<<j<<" : "<<score<<endl;
        }
        cout<<endl;
    }
    
    // 数据库 :
    cout<<"comparing images with database "<<endl;
    DBoW3::Database db( vocab, false, 0);
    for ( int i=0; i<descriptors.size(); i++ )
        db.add(descriptors[i]);
    cout<<"database info: "<<db<<endl;
    for ( int i=0; i<descriptors.size(); i++ )
    {
        DBoW3::QueryResults ret;
        db.query( descriptors[i], ret, 4);      // max result = 4
        cout<<"searching for image "<<i<<" returns "<<ret<<endl<<endl;
    }
    cout<<"done."<<endl;
}

演示了两种比对方式:
图像之间的直接比较以及图像与数据库之间的比较——尽管它们是大同小异的
此外,输出了每个图像对应的 Bag-of-Words 描述向量,可以从输出数据中看到它们
可以看到, BoW 描述向量中含有每个单词的 id 和权重,它们构成了整个稀疏的向量
当比较两个向量时, DBoW3 计算了一个分数,计算的方式由之前构造字典时定义
在这里插入图片描述
在数据库查询时, DBoW 对上面的分数进行排序,给出最相似的结果:
在这里插入图片描述
可以查看所有的输出,看看不同图像与相似图像评分有多少差异
我们看到明显相似的图 1 和图 10(下标为 0 和 9),相似度评分约 0.0583173
而其他图像约在0.03 左右

相似图像 1 和 10 的评分明显高于其他图像对,然而就数值上看并没有想象的那么明显
按说,如果自己和自己比较相似度为 100%
那么从人类角度认为图 1 和图 10 至少也有百分之七八十的相似度
而其他图可能为百分之二三十
然而实验结果却是无关图像约 2%,相似图像约 5%,似乎没有想象的那么明显


3. 增加字典规模

在机器学习领域,如果代码没出错而结果不满意时
首先怀疑“网络结构是否够大,层数是否足够深,数据样本是否够多”之类的问题
这依然是出于“好模型敌不过烂数据”的大原则(一方面也是因为缺乏更深层次的理论分析)

尽管现在是在研究 SLAM,但出现这种情况,首先会怀疑:是不是字典选的太小了?
毕竟从十张图中生成了字典,然后又根据这个字典里计算图像相似性

vocab_larger.yml.gz 是生成的一个稍微大一点儿的字典
事实上是对同一个数据序列的所有图像生成的,大约有 2,900 张图像
字典的规模仍然取 k =10,d = 5,即最多一万个单词
可以使用同目录下的 gen_vocab_large.cpp 文件自行训练字典
请注意若要训练大型字典,可能需要一台内存较大的机器,并且耐心等上一段时间

对上节的程序稍加修改,使用更大的字典去检测图像相似性:

// DBoW3::Vocabulary vocab("../vocabulary.yml.gz");
DBoW3::Vocabulary vocab("../vocab_larger.yml.gz");

在这里插入图片描述
可以看到,当字典规模增加时,无关图像的相似性明显变小了
而相似的图像,例如图像 1 和 10,虽然分值也略微下降
但相对于其他图像的评分,却变得更为显著了
这说明增加字典训练样本是有益的


4. 相似性评分的处理

对任意两个图像,都能给出一个相似性评分
但是只利用这个分值的绝对大小,并不一定有很好的帮助

譬如说,有些环境的外观本来就很相似,像办公室往往有很多同款式的桌椅
另一些环境则各个地方都有很大的不同

考虑到这种情况,会取一个先验相似度 s (vt, vt−∆t)
它表示某时刻关键帧图像与上一时刻的关键帧的相似性
然后,其他的分值都参照这个值进行归一化:

在这里插入图片描述
站在这个角度上,就说:
如果当前帧与之前某关键帧的相似度,超过当前帧与上一个关键帧相似度的 3 倍,就认为可能存在回环
这个步骤避免了引入绝对的相似性阈值,使得算法能够适应更多的环境


5. 关键帧的处理

在检测回环时,必须考虑到关键帧的选取
如果关键帧选得太近
那么导致两个关键帧之间的相似性过高,相比之下不容易检测出历史数据中的回环
比如检测结果经常是第 n 帧和第 n − 2 帧、 n − 3 帧最为相似
这种结果似乎太平凡了,意义不大
所以从实践上说,用于回环检测的帧最好是 稀疏 一些,彼此之间不太相同,又能涵盖整个环境

另一方面,如果成功检测到了回环,比如说出现在第 1 帧和第 n 帧
那么很可能第 n + 1 帧, n + 2 帧都会和第 1 帧构成回环
但是,确认第 1 帧和第 n 帧之间存在回环,对轨迹优化是有帮助的
但再接下去的第 n + 1 帧, n + 2 帧都会和第 1 帧构成回环,产生的帮助就没那么大了
因为已经用之前的信息消除了累计误差,更多的回环并不会带来更多的信息
所以,会 把“相近”的回环聚成一类,使算法不要反复地检测同一类的回环


6. 检测之后的验证

词袋的回环检测算法完全依赖于外观而没有利用任何的几何信息
这导致外观相似的图像容易被当成回环
并且,由于词袋不在乎单词顺序,只在意单词有无的表达方式,更容易引发感知偏差

所以,在回环检测之后,通常还会有一个验证步骤,当然验证的方法有很多
其一是设立回环的缓存机制,认为单次检测到的回环并不足以构成良好的约束
而在一段时间中一直检测到的回环,才认为是正确的回环
这可以看成时间上的一致性检测
另一方法是空间上的一致性检测,即是对回环检测到的两个帧进行特征匹配,估计相机的运动
然后,再把运动放到之前的 Pose Graph 中,检查与之前的估计是否有很大的出入
总之,验证部分通常是必须的,但如何实现却是见仁见智的问题


7. 与机器学习的关系

从前边的论述中可以看出,回环检测与机器学习有着千丝万缕的关联
回环检测本身非常像是一个分类问题

与传统模式识别的区别在于,回环中的类别数量很大,而每类的样本很少
极端情况下,当机器人发生运动后,图像发生变化,就产生了新的类别
甚至可以把类别当成连续变量而非离散变量

而回环检测,相当于两个图像落入同一类,则是很少出现的
从另一个角度,回环检测也相当于对“图像间相似性”概念的一个学习
既然人类能够掌握图像是否相似的判断,让机器学习到这样的概念也是非常有可能的

从词袋模型来说,它本身是一个非监督的机器学习过程
构建词典相当于对特征描述子进行聚类,而树只是对所聚的类的一个快速查找的数据结构而已
既然是聚类,结合机器学习里的知识,至少可以问:

  1. 是否能对机器学习的图像特征进行聚类,而不是 SURF、 ORB 这样的人工设计特征进行聚类?
  2. 是否有更好的方式进行聚类,而不是用树结构加上 K-means 这些较朴素的方式?

结合目前机器学习的发展,二进制描述子的学习和无监督的聚类
都是很有望在深度学习框架中得以解决的问题
可以陆续看到利用机器学习进行回环检测的工作

未来深度学习方法很有希望打败这些人工设计特征的,“传统”的机器学习方法
毕竟词袋方法在物体识别问题上已经明显不如神经网络了
而回环检测又是非常相似的一个问题


参考:

《视觉SLAM十四讲》


相关推荐:

视觉SLAM笔记(58) 字典
视觉SLAM笔记(57) 回环检测
视觉SLAM笔记(56) 位姿图优化
视觉SLAM笔记(55) 位姿图
视觉SLAM笔记(54) Ceres 操作后端优化


谢谢!

发布了217 篇原创文章 · 获赞 290 · 访问量 288万+

猜你喜欢

转载自blog.csdn.net/qq_32618327/article/details/103106494