本文首发于微信公众号「3D视觉工坊」——VSLAM|回环检测之词袋字典如何生成?
近期在学习SLAM中的回环检测模块,着重于对于字典的训练方式进行了研究,简单整理了下学习笔记如下。
一 DBoW库 首先,DBoW库的作者github网址: https://github.com/dorian3d?tab=overview&from=2018-12-01&to=2018-12-31 DBoW3是DBoW2的增强版,这是一个开源C++库,用于给图像特征排序,并将图像转化成视觉词袋表示。 DBoW3与DBoW2的主要差别: 1、DBoW3可使用二值和浮点特征描述子,无需为任何描述符重写类; 2、DBoW3可以在Linux和Windows下编译;3、仍然和DBoW2中的yml文件兼容。 4、DBoW3依赖项只有OpenCV,DBoW2依赖项DLIB被移除;5、重写了代码进行优化,DBoW3的接口也被简化了;6、可使用二进制视觉词典文件,加载或者保存速度更快,而且,二进制文件还能被压缩; 同时,DoW3还生成一个图像数据库,带有顺序索引和逆序索引,可以使图像特征的检索和对比非常快。
二 ORB-SLAM2中的字典DBoW 无论是DBoW2,还是DBoW3,我们发现它们封装的函数可以创建的字典文件格式都为yml格式,不能直接应用于不能直接应用于ORB-SLAM2,而ORB-SLAM2中的词典为ORBvoc.txt格式。
2.1 ORB-SLAM2中的ORBvoc.txt文件 SLAM中的字典文件是作者使用非常庞大的图片库生成的,对室内和户外都有很好的效果,有时候自己生成的字典,由于我们采集的图片质量以及数据集没有他们那么庞大,效果不一定会比作者提供的好,其文件格式如下:10 6 0 0 #分别表示上面的树的分支、树的深度、相似度、权重0 0 252 188 188 242 169 109 85 143 187 191 164 25 222 255 72 27 129 215 237 16 58 111 219 51 219 211 85 127 192 112 134 34 0…#0表示节点的父节点;0表示是否是叶节点,是的话为1,否则为0;252-34表示orb特征;最后一位是权重。
2.2 DBoW3生成的yml文件格式 首先我们来看一下yml格式的字典内容:vocabulary: k: 10 #表示树的分支 L: 5 #表示树的深度 scoringType: 0 #相似度 weightingType: 0 #权重 nodes:#节点,以下三个分别表示:节点id,父节点id,权重 - { nodeId:1, parentId:0, weight:0., descriptor:“0 32 62 65 18 172 93 223 86 104 133 132 233 11 79 219 43 144 216 249 195 98 76 35 26 140 179 213 1 63 115 63 110 130 " }- { nodeId:2, parentId:0, weight:0., descriptor:“0 32 254 180 252 240 173 125 80 203 219 191 181 57 78 253 78 159 143 215 237 16 62 103 235 211 219 219 85 127 195 108 78 71 " }… words: - { wordId:0, nodeId:31 } - { wordId:1, nodeId:32 } - { wordId:2, nodeId:33 }…
2.3 如何生成ORB-SLAM2中的ORBvoc.txt文件格式 鉴于已经有作者对DBoW库进行了些许修改[1],在原有DBoW2库中加入一个模板函数 saveToTextFile , 使得可以直接生成ORB-SLAM2中开源代码提供的字典文件格式 。同时,如果对于 voc.txt文件的读取,需要在头文件TemplatedVocabulary.h中添加如下函数进行编译(这也是ORB-SLAM2开源代码作者实现好的函数),此处粘贴如下:
template< class TDescriptor, class F> bool TemplatedVocabulary< TDescriptor, F>::loadFromTextFile( const std:: string &filename) { ifstream f; f.open(filename.c_str()); if(f.eof()) return false; m_words.clear(); m_nodes.clear(); string s; getline(f,s); stringstream ss; ss << s; ss >> m_k; ss >> m_L; int n1, n2; ss >> n1; ss >> n2; if( m_k< 0 || m_k> 20 || m_L< 1 || m_L> 10 || n1< 0 || n1> 5 || n2< 0 || n2> 3) { std::cerr << “Vocabulary loading failure: This is not a correct text file!” << endl; return false; } m_scoring = ( ScoringType)n1; m_weighting = ( WeightingType)n2; createScoringObject(); // nodes int expected_nodes = ( int)((pow(( double) m_k, ( double) m_L + 1) - 1)/( m_k - 1)); m_nodes.reserve(expected_nodes); m_words.reserve(pow(( double) m_k, ( double) m_L + 1)); m_nodes.resize( 1); m_nodes[ 0].id = 0; while(!f.eof()) { string snode; getline(f,snode); stringstream ssnode; ssnode << snode; int nid = m_nodes.size(); m_nodes.resize( m_nodes.size()+ 1); m_nodes[nid].id = nid; int pid ; ssnode >> pid; m_nodes[nid].parent = pid; m_nodes[pid].children.push_back(nid); int nIsLeaf; ssnode >> nIsLeaf; stringstream ssd; for( int iD= 0;iD< F::L;iD++) { string sElement; ssnode >> sElement; ssd << sElement << " “; } F::fromString( m_nodes[nid].descriptor, ssd.str()); ssnode >> m_nodes[nid].weight; if(nIsLeaf> 0) { int wid = m_words.size(); m_words.resize(wid+ 1); m_nodes[nid].word_id = wid; m_words[wid] = & m_nodes[nid]; } else { m_nodes[nid].children.reserve( m_k); } } return true; }三 TF-IDF 在上述的字典中有个变量: scoringType ,我们都知道scoring在英文中一般指评分的意思。那么在ORB字典里的评分是如何计算的呢?
TF-IDF(Term Frequency-Inverse Document Frequency)是一种用于信息检索与文本挖掘的常用加权技术。
TF-IDF是一种统计方法,用以评估——字词对于一个文件集或一个语料库中的某一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
3.1 TF——词频(Term Frequency) TF部分是指某个特征在单幅图像中出现的频率。假设图像M中单词w i 出现了n i 次,而一共出现的单词次数为n,则TF i 计算公式如下:
3.2 IDF——逆文档频率(Inverse Document Frequency) 我们统计某个叶子节点w i 中的特征数量相对于所有特征数量的比例作为IDF部分。假设所有特征数量为n,w i 数量为n i ,那么该单词的ID F i 为[2]:
备注: 在书[2]中这里给出的为log,但笔者也有点疑惑到底是以e为底还是以10为底取对数,对这个比较清楚的小伙伴也欢迎留言指教。注意:生成的词袋树只有IDF,没有TF。TF是对之后生成的词袋向量才会计算。
于是,单词w i 的权重 weightingType 等于TF乘IDF之积:h i =TF i *IDF i
四 如何使用DBoW2训练字典 首先可以下载个TUM开源数据集rgbd_dataset_freiburg1_plant,我们使用前十张图片用来训练字典。
第一步:编写程序读入图片; vector < string > vstrImageFilenames; vector < double > vTimestamps; string dataPath= ”…/data/rgbd_dataset_freiburg1_plant” ; string strFile_01= string (dataPath + ”/rgb.txt" );LoadImages(strFile_01,vstrImageFilenames,vTimestamps);
vector < Mat > images; int nImages=vstrImageFilenames.size(); std ::cout << “image size == “ << nImages << endl; cv :: Mat im; for ( int ni = 0 ; ni
关注
打赏