视觉SLAM笔记(58) 字典

视觉SLAM笔记(58) 字典


1. 字典的结构

按照前面的介绍,字典由很多单词组成,而每一个单词代表了一个概念
一个单词与一个单独的特征点不同
它不是从单个图像上提取出来的,而是某一类特征的组合
所以,字典生成问题类似于一个 聚类(Clustering)问题

聚类问题是无监督机器学习(Unsupervised ML)中一个特别常见的问题
用于让机器自行寻找数据中的规律的问题
BoW 的字典生成问题亦属于其中之一

首先,假设对大量的图像提取了特征点,比如说有 N 个
现在,想找一个有 k 个单词的字典,每个单词可以看作局部相邻特征点的集合
这可以用经典的 K-means(K 均值)算法解决
K-means 是一个非常简单有效的方法,因此在无监督学习中广为使用

简单来说,当有 N 个数据,想要归成 k 个类
那么用 K-means 来做,主要有以下几个步骤:
在这里插入图片描述

K-means 的做法是朴素且简单有效的,不过也存在一些问题
例如需要指定聚类数量、随机选取中心点使得每次聚类结果都不相同以及一些效率上的问题
随后研究者们亦开发出层次聚类法、 K-means++ 等算法以弥补它的不足
总之,根据 K-means,可以把已经提取的大量特征点聚类成一个含有 k 个单词的字典了

现在的问题,变为如何根据图像中某个特征点,查找字典中相应的单词?
仍然有朴素的思想:只要和每个单词进行比对,取最相似的那个就可以了嘛
这当然是简单有效的做法
然而,考虑到字典的通用性,通常会使用一个较大规模的字典
以保证当前使用环境中的图像特征都曾在字典里出现过,或至少有相近的表达
如果觉得对 10 个单词一一比较不是什么麻烦事,但对于一万个呢?十万个呢?

也许读者学过数据结构,这种 O(n) 的查找算法显然不是我们想要的
如果字典排过序,那么二分查找显然可以提升查找效率,达到对数级别的复杂度
而实践当中,可能会用更复杂的数据结构,例如 Fabmap 中的 Chou-Liu tree 等等

这里介绍另一种较为简单实用的树结构,使用一种 k 叉树来表达字典
在这里插入图片描述

它的思路很简单,类似于层次聚类,是 kmeans 的直接扩展
假定我们有 N 个特征点,希望构建一个深度为 d,每次分叉为 k 的树,那么做法如下:
在这里插入图片描述

实际上,最终仍在叶子层构建了单词,而树结构中的中间节点仅供快速查找时使用
这样一个 k 分支,深度为 d 的树,可以容纳 kd 个单词
另一方面,在查找某个给定特征对应的单词时,只需将它与每个中间结点的聚类中心比较(一共 d 次)
即可找到最后的单词,保证了对数级别的查找效率


2. 创建字典

前面的 VO 部分大量使用了 ORB 特征描述
所以这里就来演示一下如何生成,以及如何使用 ORB 字典
在这里插入图片描述

选取 TUM 数据集中的 10 张图像(位于 /data 中),它们来自一次实际的相机运动轨迹
可以看出,第 1 张图像与最后一张图像明显采自同一个地方
现在要看程序能否检测到这件事情
根据词袋模型,先来生成这十张图像对应的字典

需要声明的是,实际 BoW 使用时,字典往往是从更大的数据集中生成的
而且最好是来自目标应该环境类似的地方

通常使用较大规模的字典——越大代表字典单词量越丰富,容易找到与当前图像对应的单词
但也不能大到超过我们计算能力和内存

由于不想在 github 上面存放一个很大的字典文件
所以暂时从十张图像训练一个小的字典
如果想进一步追求更好的效果,应该下载更多的数据,训练更大的字典,程序才会实用
或者使用别人训练好的字典,但请注意字典使用的特征类型是否一致

下面开始训练字典
首先,请安装本程序使用的 BoW 库,使用了 DBoW3:https://github.com/rmsalinas/DBow3
也可从代码的 3rdparty 文件夹中找到它
这是一个 cmake 工程,需要对它进行编译安装

$ mkdir build
$ cd build
$ cmake ..
$ make 
$ sudo make install 

接下来考虑训练字典feature_training.cpp

#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 images... " << endl;
    vector<Mat> images;
    for (int i = 0; i < 10; i++)
    {
        string path = "../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 << "creating vocabulary ... " << endl;
    DBoW3::Vocabulary vocab;
    vocab.create(descriptors);
    cout << "vocabulary info: " << vocab << endl;
    vocab.save("vocabulary.yml.gz");
    cout << "done" << endl;

    return 0;
}

DBoW3 的使用方式非常容易
对十张目标图像提取 ORB 特征并存放至 vector 容器中,然后调用 DBoW3 的字典生成接口即可
DBoW3::Vocabulary 对象的构造函数中,能够指定树的分叉数量以及深度
不过这里使用了默认构造函数,也就是 k =10,d = 5
这是一个小规模的字典,最大能容纳 10000 个单词
对于图像特征,亦使用默认参数,即每张图像 500 个特征点
最后把字典存储为一个压缩文件

运行此程序:

$ ./feature_training

将看到输出的字典信息:
在这里插入图片描述
看到输出的字典的信息:分支数量 k 为 10,深度 L 为 5,单词数量为 4970,没有充满最大容量
但是,还有剩下的 Weighting 和 Scoring
从字面上看, Weighting 是权重, Scoring 似乎指的是评分


参考:

《视觉SLAM十四讲》


相关推荐:

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


谢谢!

发布了265 篇原创文章 · 获赞 358 · 访问量 337万+

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览