开源词袋模型DBow3原理&源码(一)整体结构
前人摘树,后人乘凉。
源码在github有CMakeLists,代码下下来可以直接编译。
泡泡机器人有个很详细的分析,结合浅谈回环检测中的词袋模型,配合高翔的回环检测应用,基本上就可以串起来了。
tf-idf的概念,表达方式不唯一,这里的定义是这样:
tf表示词频,这个单词在图像中出现的次数/图像单词总量
idf表示单词在整个训练语料库中的常见程度:idf=log(N/Ni),N是训练语料库中的图片总数,Ni是训练语料库中包含这个单词的图像数
由于Ni<=N,idf>=0,当Ni=N时,idf取最小值0(如果一个单词在每个语料库里都出现了,这个单词太过常见可以忽略)
视觉字典生成以后idf就固定了,检索数据库时的权重用tf*idf计算。
个人理解DBow3里单词(word Id)对应的权值就是idf,检索入口(Entry Id)处的权值是tf*idf。
demo的使用:./demo_gernal orb image1 image2 image3 image4
会在执行目录下生成用输入的4张图提特征的voc字典,并以这四张图为检索入口,生成db检索库,db文件里包含字典,所以有了db就不用voc了。
字典长这样:
vocabulary:
k: 9 //聚类中心数
L: 3 //层数
scoringType: 0
weightingType: 0
nodes:
- { nodeId:19, parentId:10, weight:2.8768207245178085e-01,
descriptor:dbw3 0 32 124 169 185 96 221 205 85 157 235 189 172 8 159 181 72 50 137 222 236 88 26 107 250 49 251 221 21 127 210 106 198 42 }
- { nodeId:20, parentId:10, weight:1.3862943611198906e+00,
descriptor:dbw3 0 32 120 164 24 104 249 61 80 95 115 27 172 24 31 147 64 248 145 152 76 56 26 111 82 23 219 223 17 125 226 200 202 42 }
- { nodeId:21, parentId:10, weight:0.,
descriptor:dbw3 0 32 124 165 248 102 209 109 83 25 99 173 173 8 115 241 72 50 16 222 68 56 26 114 248 2 255 205 37 121 226 234 198 34 }
- { nodeId:22, parentId:10, weight:0.,
descriptor:dbw3 0 32 36 161 252 81 224 233 97 139 235 53 108 40 151 199 204 26 3 158 110 24 18 248 234 65 205 152 53 117 194 200 198 162 }
- { nodeId:23, parentId:10, weight:1.3862943611198906e+00,
descriptor:dbw3 0 32 76 140 56 218 221 253 113 218 95 53 44 24 24 211 106 116 81 154 18 24 18 26 122 245 95 29 17 109 146 137 212 106 }
words:
- { wordId:0, nodeId:19 }
- { wordId:1, nodeId:20 }
- { wordId:2, nodeId:21 }
- { wordId:3, nodeId:22 }
- { wordId:4, nodeId:23 }
- { wordId:5, nodeId:24 }
是对聚类生成的树的描述,树上的每一个点都是node,只有叶子结点才是word,每张图进来提取特征,利用descriptor算特征距离,最终落到叶子上(单词),所有特征的单词构成该图片的词向量。

检索入口描述:
database:
nEntries: 4
usingDI: 0
diLevels: 0
invertedIndex:
- //(wordId:0)
- { imageId:1, weight:1.6807896319101980e-03 }
- { imageId:2, weight:3.2497152852064880e-03 }
- { imageId:3, weight:3.6665308718065778e-03 }
- //(wordId:1)
- { imageId:1, weight:4.0497295661974788e-03 }
- //(wordId:2)
[]
-
[]
-
- { imageId:2, weight:3.9149658655580431e-03 }
-
- { imageId:3, weight:4.4171079458813099e-03 }
-
- { imageId:1, weight:2.0248647830987394e-03 }
- { imageId:3, weight:4.4171079458813099e-03 }
检索入口:


根据voc字典里的描述,word id 2对应node id 21, 而node id 21对应的权值为0,也就是说word 2太普通了,在用来生成视觉词汇表的4张图里都出现了(参考中文文章里的“的”、“在”、“和”等常见词),不具有代表性, 于是根本就没有对应入口id,这是合理的。
开源出来的代码不是对相同word的入口进行加1投票,而是直接计算单词对应的所有EntryId分数,最后排序取前n个。分数可以有L1 L2 KL等几种计算方式
queryL1,C++不熟看了半天,用到map函数,注释:
void Database::queryL1(const BowVector &vec, QueryResults &ret, int max_results, int max_id) const
{
BowVector::const_iterator vit; std::map<EntryId, double> pairs;
std::map<EntryId, double>::iterator pit; for(vit = vec.begin(); vit != vec.end(); ++vit)
{
const WordId word_id = vit->first;
const WordValue& qvalue = vit->second; const IFRow& row = m_ifile[word_id]; // IFRows are sorted in ascending entry_id order
for(auto rit = row.begin(); rit != row.end(); ++rit)
{
const EntryId entry_id = rit->entry_id;
const WordValue& dvalue = rit->word_weight; if((int)entry_id < max_id || max_id == -)
{
double value = fabs(qvalue - dvalue) - fabs(qvalue) - fabs(dvalue);
//pairs是一个map,low_bound返回首个不小于entry_id的迭代器,因此如果map里有这个entry_id, pit指向它,否则指向pairs.end()
pit = pairs.lower_bound(entry_id); //因为在db库里entry_id是按顺序存的,所以可以这样查找
if(pit != pairs.end() && !(pairs.key_comp()(entry_id, pit->first)))
{
pit->second += value; //如果已经有entry_id,累加和
}
else
{ //如果没有,插入此id
pairs.insert(pit, std::map<EntryId, double>::value_type(entry_id, value));
}
}
} // for each inverted row
} // for each query word // move to vector
ret.reserve(pairs.size());
for(pit = pairs.begin(); pit != pairs.end(); ++pit)
{
ret.push_back(Result(pit->first, pit->second));
} // resulting "scores" are now in [-2 best .. 0 worst]
// sort vector in ascending order of score
std::sort(ret.begin(), ret.end());
// (ret is inverted now --the lower the better--)
// cut vector
if(max_results > && (int)ret.size() > max_results)
ret.resize(max_results); // complete and scale score to [0 worst .. 1 best]
// ||v - w||_{L1} = 2 + Sum(|v_i - w_i| - |v_i| - |w_i|)
// for all i | v_i != 0 and w_i != 0 // scaled_||v - w||_{L1} = 1 - 0.5 * ||v - w||_{L1}
QueryResults::iterator qit;
for(qit = ret.begin(); qit != ret.end(); qit++)
qit->Score = -qit->Score/2.0;
}
-------------------2018.02.23 打印生成的orb特征----------------
输入2张图,提orb特征,描述子保存在features[0]、features[1]里,一张图500个特征点,每个点256维描述子(分成8bit*32),显示描述子竟然写了半天,我这弱渣的代码能力...
cout<<"features size:"<<features.size()<<endl; //feature定义:vector<cv::Mat>
cout<<"feautres[0] size:"<<features[].size()<<endl;
for(int i=;i<features[].rows;i++)
{
for(int j=;j<features[].cols;j++)
{ //十六进制显示
cout<<hex<<setfill('')<<setw()<<int( features[].at<unsigned char>(i,j) )<<' ';
}
cout<<endl;
}

扩展:
dbow3支持4种描述类型:orb brisk akaze(opencv3) surf(编译选项USE_CONTRIB)
描述子orb: 32CV_8U(256bit) brisk 64CV_8U(512bit) akaze 61CV_8U(默认DESCRIPTOR_MLDB也是二进制描述子,根据a-kaze论文,描述子长度为486bit(61*8bit) )
选择akaze描述子,生成的字典中描述子长度变为61:

-----------2019.04.04-----------
回顾了下去年这个项目:
1. 没有用到正序索引:

开源词袋模型DBow3原理&源码(一)整体结构的更多相关文章
- 开源词袋模型DBow3原理&源码(二)ORB特征的保存和读取
util里提供了create_voc_step0用于批量生成features并保存,create_voc_step1读入features再生成聚类中心,比较适合大量语料库聚类中心的生成. 提取一张图的 ...
- 深入理解Faiss 原理&源码 (一) 编译
目录 深入理解Faiss 原理&源码 (一) 编译 mac下安装 安装mac xcode工具包 安装 openblas 安装swig 安装libomp 编译faiss 附录 深入理解Faiss ...
- SharedPreferences 原理 源码 进程间通信 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Laya Timer原理 & 源码解析
Laya Timer原理 & 源码解析 @author ixenos 2019-03-18 16:26:38 一.原理 1.将所有Handler注册到池中 1.普通Handler在handle ...
- 百度开源分布式id生成器uid-generator源码剖析
百度uid-generator源码 https://github.com/baidu/uid-generator snowflake算法 uid-generator是基于Twitter开源的snowf ...
- 一个Python开源项目-哈勃沙箱源码剖析(下)
前言 在上一篇中,我们讲解了哈勃沙箱的技术点,详细分析了静态检测和动态检测的流程.本篇接着对动态检测的关键技术点进行分析,包括strace,sysdig,volatility.volatility的介 ...
- 开源分布式数据库中间件MyCat源码分析系列
MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...
- Mybatis Interceptor 拦截器原理 源码分析
Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...
- 带着萌新看springboot源码11(springboot启动原理 源码上)
通过前面这么多讲解,springboot原理应该也大概有个轮廓了,一些基本的配置,从客户端url到controller(配置一些要用的组件,servlet三大组件,处理器映射器,拦截器,视图解析器这些 ...
随机推荐
- MySQL Sandbox安装使用
前言 经常看见新同学对于安装MySQL十分惧怕,这是因为不熟悉的缘故,确实源码编译比较麻烦,缺少各种依赖包也够折腾的.当然还有预编译包,但是对于新同学来说好像都挺麻烦的.当然有yum安装,但是我们需要 ...
- (4.3)mysql备份还原——mysql备份策略
(4.3)mysql备份还原——mysql备份策略 1.指定备份策略时需要考虑的点 [1.1]备份周期:2次备份间隔时长 [1.2]备份方式:在备份周期中,使用什么备份方式.备份模式 [1.3]实现方 ...
- 配置svn用户及密码
配置账户及密码 格式: user=passwd 配置新的账户 [root@mysql conf]# vim passwd 创建两个账户 [root@mysql conf]# egrep -v &q ...
- hadoop搭建伪分布式集群(centos7+hadoop-3.1.0/2.7.7)
目录: Hadoop三种安装模式 搭建伪分布式集群准备条件 第一部分 安装前部署 1.查看虚拟机版本2.查看IP地址3.修改主机名为hadoop4.修改 /etc/hosts5.关闭防火墙6.关闭SE ...
- vue中根据生日计算年龄
getage() { var birthdays = new Date(this.birthday.replace(/-/g, "/")); var d = new Date(); ...
- golang 中的 sizeof 以及 golang中的 union
golang 中的 sizeof: 1: int(unsafe.Sizeof(uint32(0))) 2: int(reflect.TypeOf(uint32(0)).Size()) golang中的 ...
- 创建genil component
1: 创建一个类继承 CL_WCF_GENIL_ABSTR_COMPONENT 2:创建 genil _ editor 创建 component, 填入该实现类. 3: genil component ...
- 构造器初始化(static)
package demo; /* * 在类 的内部,变量定义的先后顺序决定了初始化的顺序.即使变量定义散布于方法定义之间, * 它们仍旧会在任何方法(包括构造器)被调用之前得到初始化. */ publ ...
- 微信小程序:wx.navigateBack页面返回传参
场景(比如:商城选地址,选优惠券): 在[页面A]中调用wx.navigateTo方法跳转到[页面B] 然后从[页面B]返回[页面A], 并将[页面B]中的一些数据传回[页面A] wx.navigat ...
- kfrobotaidlog查找
======查看最新的大于6的数量========== select c.questionid from (select a.questionid, b.clientQuestion from sim ...