图像检索(5):基于OpenCV实现小型的图像数据库检索
本文对前面的几篇文章进行个总结,实现一个小型的图像检索应用。
一个小型的图像检索应用可以分为两部分:
- train,构建图像集的特征数据库。
- retrieval,检索,给定图像,从图像库中返回最类似的图像
构建图像数据库的过程如下:
- 生成图像集的视觉词汇表(Vocabulary)
- 提取图像集所有图像的sift特征
- 对得到的sifte特征集合进行聚类,聚类中心就是Vocabulary
- 对图像集中的图像重新编码表示,可使用BoW或者VLAD,这里选择VLAD.
- 将图像集中所有图像的VLAD表示组合到一起得到一个VLAD表,这就是查询图像的数据库。
得到图像集的查询数据后,对任一图像查找其在数据库中的最相似图像的流程如下:
- 提取图像的sift特征
- 加载Vocabulary,使用VLAD表示图像
- 在图像数据库中查找与该VLAD最相似的向量
构建图像集的特征数据库的流程通常是offline的,查询的过程则需要是实时的,基本流程参见下图:
由两部分构成:offline的训练过程以及online的检索查找
各个功能模块的实现
下面就使用VLAD表示图像,实现一个小型的图像数据库的检索程序。下面实现需要的功能模块
- 特征点提取
- 构建Vocabulary
- 构建数据库
第一步,特征点的提取
不管是BoW还是VLAD,都是基于图像的局部特征的,本文选择的局部特征是SIFT,使用其扩展RootSift。提取到稳定的特征点尤为的重要,本文使用OpenCV体哦那个的SiftDetecotr
,实例化如下:
auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10);
create
的声明如下:
static Ptr<SIFT> cv::xfeatures2d::SIFT::create ( int nfeatures = 0,
int nOctaveLayers = 3,
double contrastThreshold = 0.04,
double edgeThreshold = 10,
double sigma = 1.6
)
- nfeatures 设置提取到的特征点的个数,每个sift的特征点都根据其对比度(local contrast)计算出来一个分数。设置了该值后,会根据分数排序,只保留前nfeatures个返回
- nOctaveLayers 每个octave中的层数,该值可以根据图像的分辨率大小计算出来。D.Lowe论文中该值为3
- contrastThreshold 过滤掉低对比度的不稳定特征点,该值越大,提取到的特征点越少
- edgeThreshold 过滤边缘处的特征点,该值越大,提取到的特征点就越多
- sigma 高斯滤波器的参数,该滤波器应用于第0个Octave
个人的一些见解。
设置参数时,主要是设置contrastThreshold
和edgeThreshold
。contrastThreshold
是过滤掉平滑区域的一些不稳定的特征点,edgeThreshold
是过虑类似边缘的不稳定关键点。设置参数时,应尽量保证提取的特征点个数适中,不易过多,也不要过少。另外,contrastThreshold
和edgeThreshold
的平衡,应根据要提取的目标是比较平滑的区域还是纹理较多的区域,来平衡这两个参数的设置。
对于有些图像,可能设置的提取特征点的参数叫严格,提取特征点的个数过少,这时候可改变宽松一些的参数。
auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10);
fdetector->detectAndCompute(img,noArray(),kpts,feature);
if(kpts.size() < 10){
fdetector = xfeatures2d::SIFT::create();
fdetector->detectAndCompute(img,noArray(),kpts,feature);
}
阈值10,可根据具体的情况进行调节。
更多关于sift的内容可以参看文章:
- 图像检索(1): 再论SIFT-基于vlfeat实现 使用轻量级的视觉库vlfeat提取sift特征,其提取的特征觉得更稳定一些,但是使用上就不如OpenCV方便了。
- SIFT特征详解
关于RootSift和VLAD可以参考前面的文章图像检索(4):IF-IDF,RootSift,VLAD。
第二步,构建Vocabulary
Vocabulary的构建过程,实际就是对提取到的图像特征点的聚类。首先提取图像库图像sift特征,并将其扩展为RootSift,然后对提取到的RootSift进行聚类得到Vocabulary。
这里创建class Vocabulary
,主要以下方法:
create
从提取到的特征点构建聚类得到视觉词汇表Vocabulary
void Vocabulary::create(const std::vector<cv::Mat> &features,int k)
{
Mat f;
vconcat(features,f);
vector<int> labels;
kmeans(f,k,labels,TermCriteria(TermCriteria::COUNT + TermCriteria::EPS,100,0.01),3,cv::KMEANS_PP_CENTERS,m_voc);
m_k = k;
}
load
和save
,为了使用方便,需要能够将生成的视觉词汇表Vocabulary
保存问文件(.yml)tranform_vlad
,将输入的图像进行转换为vlad表示
void Vocabulary::transform_vlad(const cv::Mat &f,cv::Mat &vlad)
{
// Find the nearest center
Ptr<FlannBasedMatcher> matcher = FlannBasedMatcher::create();
vector<DMatch> matches;
matcher->match(f,m_voc,matches);
// Compute vlad
Mat responseHist(m_voc.rows,f.cols,CV_32FC1,Scalar::all(0));
for( size_t i = 0; i < matches.size(); i++ ){
auto queryIdx = matches[i].queryIdx;
int trainIdx = matches[i].trainIdx; // cluster index
Mat residual;
subtract(f.row(queryIdx),m_voc.row(trainIdx),residual,noArray());
add(responseHist.row(trainIdx),residual,responseHist.row(trainIdx),noArray(),responseHist.type());
}
// l2-norm
auto l2 = norm(responseHist,NORM_L2);
responseHist /= l2;
//normalize(responseHist,responseHist,1,0,NORM_L2);
//Mat vec(1,m_voc.rows * f.cols,CV_32FC1,Scalar::all(0));
vlad = responseHist.reshape(0,1); // Reshape the matrix to 1 x (k*d) vector
}
class Vocabulary
有以下方法:
- 从图像列表中构建视觉词汇表
Vocabulary
- 将生成的
Vocabulary
保存到本地,并提供了load
方法 - 将图像表示为VLAD
第三步,创建图像数据库
图像数据库也就是将图像VLAD表示的集合,在该数据库检索时,返回与query图像相似的VLAD所对应的图像。
本文使用OpenCV提供的Mat
构建一个简单的数据库,Mat
保存所有图像的vlad向量组成的矩阵,在检索时,实际就是对该Mat
的检索。
声明类class Database
,其具有以下功能:
add
添加图像到数据库save
和load
将数据库保存为文件(.yml)retrieval
检索,对保存的vald向量的Mat
创建索引,返回最相似的结果。
第四步,Trainer
在上面实现了特征点的提取,构建视觉词汇表,构建图像表示为VLAD的数据库,这里将其组合到一起,创建Trainer
类,方便训练使用。
class Trainer{
public:
Trainer();
~Trainer();
Trainer(int k,int pcaDim,const std::string &imageFolder,
const std::string &path,const std::string &identifiery,std::shared_ptr<RootSiftDetector> detector);
void createVocabulary();
void createDb();
void save();
private:
int m_k; // The size of vocabulary
int m_pcaDimension; // The retrain dimensions after pca
Vocabulary* m_voc;
Database* m_db;
private:
/*
Image folder
*/
std::string m_imageFolder;
/*
training result identifier,the name suffix of vocabulary and database
voc-identifier.yml,db-identifier.yml
*/
std::string m_identifier;
/*
The location of training result
*/
std::string m_resultPath;
};
使用Trainer
需要配置
- 图像集所在的目录
- 视觉词汇表的大小(聚类中心的个数)
- PCA后VLAD保留的维度,可先不管设置为0,不进行PCA
- 训练后数据的保存路径。 训练后的数据保存为
yml
形式,命名规则是voc-m_identifier.yml
和db-m_identifier.yml
。 为了方便测试不同参数的数据,这里设置一个后缀参数m_identifier
,来区分不同的参数的训练数据。
其使用代码如下:
int main(int argc, char *argv[])
{
const string image_200 = "/home/test/images-1";
const string image_6k = "/home/test/images/sync_down_1";
auto detector = make_shared<RootSiftDetector>(5,5,10);
Trainer trainer(64,0,image_200,"/home/test/projects/imageRetrievalService/build","test-200-vl-64",detector);
trainer.createVocabulary();
trainer.createDb();
trainer.save();
return 0;
}
偷懒,没有配置为参数,使用时需要设置好图像的路径,以及训练后数据的保存数据。
第五步,Searcher
在Database
中,已经实现了retrieval
的方法。 这里之所以再封装一层,是为了更好的契合业务上的一些需求。比如,图像的一些预处理,分块,多线程处理,查询结果的过滤等等。关于Searcher
和具体的应用耦合比较深,这里只是简单的实现了个retrieval
方法和查询参数的配置。
class Searcher{
public:
Searcher();
~Searcher();
void init(int keyPointThreshold);
void setDatabase(std::shared_ptr<Database> db);
void retrieval(cv::Mat &query,const std::string &group,std::string &md5,double &score);
void retrieval(std::vector<char> bins,const std::string &group,std::string &md5,double &score);
private:
int m_keyPointThreshold;
std::shared_ptr<Database> m_db;
};
使用也很简单了,从文件中加载Vaocabulary
和Database
,设置Searcher
的参数。
Vocabulary voc;
stringstream ss;
ss << path << "/voc-" << identifier << ".yml";
cout << "Load vocabulary from " << ss.str() << endl;
voc.load(ss.str());
cout << "Load vocabulary successful." << endl;
auto detector = make_shared<RootSiftDetector>(5,0.2,10);
auto db = make_shared<Database>(detector);
cout << "Load database from " << path << "/db-" << identifier << ".yml" << endl;
db->load1(path,identifier);
db->setVocabulary(voc);
cout << "Load database successful." << endl;
Searcher s;
s.init(10);
s.setDatabase(db);
Summary
上图来总结下整个流程
创建
Vocabulary
创建
Database
Search Similary list
图像检索(5):基于OpenCV实现小型的图像数据库检索的更多相关文章
- OpenCV 学习笔记 06 图像检索以及基于图像描述符的搜索
OpenCV 可以检测图像的主要特征,然后提取这些特征,使其成为图像描述符,这些图像特征可作为图像搜索的数据库:此外可以利用关键点将图像拼接 stitch 起来,组成一个更大的图像.如将各照片组成一个 ...
- [转载]卡尔曼滤波器及其基于opencv的实现
卡尔曼滤波器及其基于opencv的实现 源地址:http://hi.baidu.com/superkiki1989/item/029f65013a128cd91ff0461b 这个是维基百科中的链接, ...
- 基于Opencv和Mfc的图像处理增强库GOCVHelper(索引)
GOCVHelper(GreenOpen Computer Version Helper )是我在这几年编写图像处理程序的过程中积累下来的函数库.主要是对Opencv的适当扩展和在实现Mfc程序时候的 ...
- 基于OpenCv的人脸检测、识别系统学习制作笔记之一
基于OpenCv从视频文件到摄像头的人脸检测 在OpenCv中读取视频文件和读取摄像头的的视频流然后在放在一个窗口中显示结果其实是类似的一个实现过程. 先创建一个指向CvCapture结构的指针 Cv ...
- 基于opencv网络摄像头在ubuntu下的视频获取
基于opencv网络摄像头在ubuntu下的视频获取 1 工具 原料 平台 :UBUNTU12.04 安装库 Opencv-2.3 2 安装编译运行步骤 安装编译opencv-2.3 参 ...
- 基于opencv的小波变换
基于opencv的小波变换 提供函数DWT()和IDWT(),前者完成任意层次的小波变换,后者完成任意层次的小波逆变换.输入图像要求必须是单通道浮点图像,对图像大小也有要求(1层变换:w,h必须是2的 ...
- 基于opencv在摄像头ubuntu根据视频获取
基于opencv在摄像头ubuntu根据视频获取 1 工具 原料 平台 :UBUNTU12.04 安装库 Opencv-2.3 2 安装编译执行步骤 安装编译opencv-2.3 參考h ...
- OpenCV2学习笔记(十四):基于OpenCV卡通图片处理
得知OpenCV有一段时间.除了研究的各种算法的内容.除了从备用,据导游书籍和资料,尝试结合链接的图像处理算法和日常生活,第一桌面上(随着摄像头)完成了一系列的视频流处理功能.开发平台Qt5.3.2+ ...
- Android上掌纹识别第一步:基于OpenCV的6种肤色分割 源码和效果图
Android上掌纹识别第一步:基于OpenCV的6种肤色分割 源码和效果图 分类: OpenCV图像处理2013-02-21 21:35 6459人阅读 评论(8) 收藏 举报 原文链接 ht ...
随机推荐
- HEOI2018 游记
day0早上没有跑操,收拾了点东西,带了点吃的,老妈打电话说要给送点厚衣服,好像确实有点冷.上午有考试,说自愿考,然后开到一半就没了,根本没人打啊,打了一道小园丁和老司机,一个一个部分分打,最后T了一 ...
- BZOJ_1827_[Usaco2010 Mar]gather 奶牛大集会_树形DP
BZOJ_1827_[Usaco2010 Mar]gather 奶牛大集会_树形DP 题意:Bessie正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会.当然,她会选择最方便的地点来 ...
- [转]现代Linux系统上的栈溢出攻击
1. 基本内容 这个教程试着向读者展示最基本的栈溢出攻击和现代Linux发行版中针对这种攻击的防御机制.为此我选择了最新版本的Ubuntu系统(12.10),因为它默认集成了几个安全防御机制,而且它也 ...
- 毕业样本=[华威大学毕业证书]Warwick原件一模一样证书
华威大学毕业证[微/Q:2544033233◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归& ...
- css3 深入理解flex布局
一.简要介绍 css3最喜欢的新属性之一便是flex布局属性,用六个字概括便是简单.方便.快速. flex( flexible box:弹性布局盒模型),是2009年w3c提出的一种可以简洁.快速弹性 ...
- 自行实现 dotnet core rpc
前言 小李:“胖子,上头叫你对接我的数据好了没有?” 胖子:“那是你的事,你都不提供数据源,我咋接?” 小李:“你想要什么样的数据源?” 胖子:“我想要一个调用简单点的!” 小李:“我这个数据源是在l ...
- ES 19 - Elasticsearch的检索语法(_search API的使用)
目录 1 Search API的基本用法 1.1 查询所有数据 1.2 响应信息说明 1.3 timeout超时机制 1.4 查询多索引和多类型中的数据 2 URI Search的用法 2.1 GET ...
- elasticsearch6.6.2在Centos6.9的安装
JDK8 做个记录,以防以后忘记能够查看. 1.elastic是java编写的,先搭建运行环境,6.6.2版本必须要jdk8以上版本才可运行,先官网下载jdk,上传服务器 https://www.or ...
- [翻译 EF Core in Action 1.7] MyFirstEfCoreApp访问的数据库
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...
- LOJ #6041. 「雅礼集训 2017 Day7」事情的相似度
我可以大喊一声这就是个套路题吗? 首先看到LCP问题,那么套路的想到SAM(SA的做法也有) LCP的长度是它们在parent树上的LCA(众所周知),所以我们考虑同时统计多个点之间的LCA对 树上问 ...