本文对前面的几篇文章进行个总结,实现一个小型的图像检索应用。

一个小型的图像检索应用可以分为两部分:

  • 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,实例化如下:

  1. auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10);

create的声明如下:

  1. static Ptr<SIFT> cv::xfeatures2d::SIFT::create ( int nfeatures = 0,
  2. int nOctaveLayers = 3,
  3. double contrastThreshold = 0.04,
  4. double edgeThreshold = 10,
  5. double sigma = 1.6
  6. )
  • nfeatures 设置提取到的特征点的个数,每个sift的特征点都根据其对比度(local contrast)计算出来一个分数。设置了该值后,会根据分数排序,只保留前nfeatures个返回
  • nOctaveLayers 每个octave中的层数,该值可以根据图像的分辨率大小计算出来。D.Lowe论文中该值为3
  • contrastThreshold 过滤掉低对比度的不稳定特征点,该值越大,提取到的特征点越少
  • edgeThreshold 过滤边缘处的特征点,该值越大,提取到的特征点就越多
  • sigma 高斯滤波器的参数,该滤波器应用于第0个Octave

个人的一些见解。

设置参数时,主要是设置contrastThresholdedgeThresholdcontrastThreshold是过滤掉平滑区域的一些不稳定的特征点,edgeThreshold是过虑类似边缘的不稳定关键点。设置参数时,应尽量保证提取的特征点个数适中,不易过多,也不要过少。另外,contrastThresholdedgeThreshold的平衡,应根据要提取的目标是比较平滑的区域还是纹理较多的区域,来平衡这两个参数的设置。

对于有些图像,可能设置的提取特征点的参数叫严格,提取特征点的个数过少,这时候可改变宽松一些的参数。

  1. auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10);
  2. fdetector->detectAndCompute(img,noArray(),kpts,feature);
  3. if(kpts.size() < 10){
  4. fdetector = xfeatures2d::SIFT::create();
  5. fdetector->detectAndCompute(img,noArray(),kpts,feature);
  6. }

阈值10,可根据具体的情况进行调节。

更多关于sift的内容可以参看文章:

关于RootSift和VLAD可以参考前面的文章图像检索(4):IF-IDF,RootSift,VLAD

第二步,构建Vocabulary

Vocabulary的构建过程,实际就是对提取到的图像特征点的聚类。首先提取图像库图像sift特征,并将其扩展为RootSift,然后对提取到的RootSift进行聚类得到Vocabulary。

这里创建class Vocabulary,主要以下方法:

  • create 从提取到的特征点构建聚类得到视觉词汇表Vocabulary
  1. void Vocabulary::create(const std::vector<cv::Mat> &features,int k)
  2. {
  3. Mat f;
  4. vconcat(features,f);
  5. vector<int> labels;
  6. kmeans(f,k,labels,TermCriteria(TermCriteria::COUNT + TermCriteria::EPS,100,0.01),3,cv::KMEANS_PP_CENTERS,m_voc);
  7. m_k = k;
  8. }
  • loadsave,为了使用方便,需要能够将生成的视觉词汇表Vocabulary保存问文件(.yml)
  • tranform_vlad,将输入的图像进行转换为vlad表示
  1. void Vocabulary::transform_vlad(const cv::Mat &f,cv::Mat &vlad)
  2. {
  3. // Find the nearest center
  4. Ptr<FlannBasedMatcher> matcher = FlannBasedMatcher::create();
  5. vector<DMatch> matches;
  6. matcher->match(f,m_voc,matches);
  7. // Compute vlad
  8. Mat responseHist(m_voc.rows,f.cols,CV_32FC1,Scalar::all(0));
  9. for( size_t i = 0; i < matches.size(); i++ ){
  10. auto queryIdx = matches[i].queryIdx;
  11. int trainIdx = matches[i].trainIdx; // cluster index
  12. Mat residual;
  13. subtract(f.row(queryIdx),m_voc.row(trainIdx),residual,noArray());
  14. add(responseHist.row(trainIdx),residual,responseHist.row(trainIdx),noArray(),responseHist.type());
  15. }
  16. // l2-norm
  17. auto l2 = norm(responseHist,NORM_L2);
  18. responseHist /= l2;
  19. //normalize(responseHist,responseHist,1,0,NORM_L2);
  20. //Mat vec(1,m_voc.rows * f.cols,CV_32FC1,Scalar::all(0));
  21. vlad = responseHist.reshape(0,1); // Reshape the matrix to 1 x (k*d) vector
  22. }

class Vocabulary有以下方法:

  • 从图像列表中构建视觉词汇表Vocabulary
  • 将生成的Vocabulary保存到本地,并提供了load方法
  • 将图像表示为VLAD

第三步,创建图像数据库

图像数据库也就是将图像VLAD表示的集合,在该数据库检索时,返回与query图像相似的VLAD所对应的图像。

本文使用OpenCV提供的Mat构建一个简单的数据库,Mat保存所有图像的vlad向量组成的矩阵,在检索时,实际就是对该Mat的检索。

声明类class Database,其具有以下功能:

  • add 添加图像到数据库
  • saveload 将数据库保存为文件(.yml)
  • retrieval 检索,对保存的vald向量的Mat创建索引,返回最相似的结果。

第四步,Trainer

在上面实现了特征点的提取,构建视觉词汇表,构建图像表示为VLAD的数据库,这里将其组合到一起,创建Trainer类,方便训练使用。

  1. class Trainer{
  2. public:
  3. Trainer();
  4. ~Trainer();
  5. Trainer(int k,int pcaDim,const std::string &imageFolder,
  6. const std::string &path,const std::string &identifiery,std::shared_ptr<RootSiftDetector> detector);
  7. void createVocabulary();
  8. void createDb();
  9. void save();
  10. private:
  11. int m_k; // The size of vocabulary
  12. int m_pcaDimension; // The retrain dimensions after pca
  13. Vocabulary* m_voc;
  14. Database* m_db;
  15. private:
  16. /*
  17. Image folder
  18. */
  19. std::string m_imageFolder;
  20. /*
  21. training result identifier,the name suffix of vocabulary and database
  22. voc-identifier.yml,db-identifier.yml
  23. */
  24. std::string m_identifier;
  25. /*
  26. The location of training result
  27. */
  28. std::string m_resultPath;
  29. };

使用Trainer 需要配置

  • 图像集所在的目录
  • 视觉词汇表的大小(聚类中心的个数)
  • PCA后VLAD保留的维度,可先不管设置为0,不进行PCA
  • 训练后数据的保存路径。 训练后的数据保存为yml形式,命名规则是voc-m_identifier.ymldb-m_identifier.yml。 为了方便测试不同参数的数据,这里设置一个后缀参数m_identifier,来区分不同的参数的训练数据。

其使用代码如下:

  1. int main(int argc, char *argv[])
  2. {
  3. const string image_200 = "/home/test/images-1";
  4. const string image_6k = "/home/test/images/sync_down_1";
  5. auto detector = make_shared<RootSiftDetector>(5,5,10);
  6. Trainer trainer(64,0,image_200,"/home/test/projects/imageRetrievalService/build","test-200-vl-64",detector);
  7. trainer.createVocabulary();
  8. trainer.createDb();
  9. trainer.save();
  10. return 0;
  11. }

偷懒,没有配置为参数,使用时需要设置好图像的路径,以及训练后数据的保存数据。

第五步,Searcher

Database中,已经实现了retrieval的方法。 这里之所以再封装一层,是为了更好的契合业务上的一些需求。比如,图像的一些预处理,分块,多线程处理,查询结果的过滤等等。关于Searcher和具体的应用耦合比较深,这里只是简单的实现了个retrieval方法和查询参数的配置。

  1. class Searcher{
  2. public:
  3. Searcher();
  4. ~Searcher();
  5. void init(int keyPointThreshold);
  6. void setDatabase(std::shared_ptr<Database> db);
  7. void retrieval(cv::Mat &query,const std::string &group,std::string &md5,double &score);
  8. void retrieval(std::vector<char> bins,const std::string &group,std::string &md5,double &score);
  9. private:
  10. int m_keyPointThreshold;
  11. std::shared_ptr<Database> m_db;
  12. };

使用也很简单了,从文件中加载VaocabularyDatabase,设置Searcher的参数。

  1. Vocabulary voc;
  2. stringstream ss;
  3. ss << path << "/voc-" << identifier << ".yml";
  4. cout << "Load vocabulary from " << ss.str() << endl;
  5. voc.load(ss.str());
  6. cout << "Load vocabulary successful." << endl;
  7. auto detector = make_shared<RootSiftDetector>(5,0.2,10);
  8. auto db = make_shared<Database>(detector);
  9. cout << "Load database from " << path << "/db-" << identifier << ".yml" << endl;
  10. db->load1(path,identifier);
  11. db->setVocabulary(voc);
  12. cout << "Load database successful." << endl;
  13. Searcher s;
  14. s.init(10);
  15. s.setDatabase(db);

Summary

上图来总结下整个流程

  • 创建Vocabulary

  • 创建Database

  • Search Similary list

图像检索(5):基于OpenCV实现小型的图像数据库检索的更多相关文章

  1. OpenCV 学习笔记 06 图像检索以及基于图像描述符的搜索

    OpenCV 可以检测图像的主要特征,然后提取这些特征,使其成为图像描述符,这些图像特征可作为图像搜索的数据库:此外可以利用关键点将图像拼接 stitch 起来,组成一个更大的图像.如将各照片组成一个 ...

  2. [转载]卡尔曼滤波器及其基于opencv的实现

    卡尔曼滤波器及其基于opencv的实现 源地址:http://hi.baidu.com/superkiki1989/item/029f65013a128cd91ff0461b 这个是维基百科中的链接, ...

  3. 基于Opencv和Mfc的图像处理增强库GOCVHelper(索引)

    GOCVHelper(GreenOpen Computer Version Helper )是我在这几年编写图像处理程序的过程中积累下来的函数库.主要是对Opencv的适当扩展和在实现Mfc程序时候的 ...

  4. 基于OpenCv的人脸检测、识别系统学习制作笔记之一

    基于OpenCv从视频文件到摄像头的人脸检测 在OpenCv中读取视频文件和读取摄像头的的视频流然后在放在一个窗口中显示结果其实是类似的一个实现过程. 先创建一个指向CvCapture结构的指针 Cv ...

  5. 基于opencv网络摄像头在ubuntu下的视频获取

     基于opencv网络摄像头在ubuntu下的视频获取 1  工具 原料 平台 :UBUNTU12.04 安装库  Opencv-2.3 2  安装编译运行步骤 安装编译opencv-2.3  参 ...

  6. 基于opencv的小波变换

    基于opencv的小波变换 提供函数DWT()和IDWT(),前者完成任意层次的小波变换,后者完成任意层次的小波逆变换.输入图像要求必须是单通道浮点图像,对图像大小也有要求(1层变换:w,h必须是2的 ...

  7. 基于opencv在摄像头ubuntu根据视频获取

     基于opencv在摄像头ubuntu根据视频获取 1  工具 原料 平台 :UBUNTU12.04 安装库  Opencv-2.3 2  安装编译执行步骤 安装编译opencv-2.3  參考h ...

  8. OpenCV2学习笔记(十四):基于OpenCV卡通图片处理

    得知OpenCV有一段时间.除了研究的各种算法的内容.除了从备用,据导游书籍和资料,尝试结合链接的图像处理算法和日常生活,第一桌面上(随着摄像头)完成了一系列的视频流处理功能.开发平台Qt5.3.2+ ...

  9. Android上掌纹识别第一步:基于OpenCV的6种肤色分割 源码和效果图

    Android上掌纹识别第一步:基于OpenCV的6种肤色分割 源码和效果图 分类: OpenCV图像处理2013-02-21 21:35 6459人阅读 评论(8) 收藏 举报   原文链接  ht ...

随机推荐

  1. i春秋----Misc

    好久没有写 博客今天更新多了一些 解题思路:题目做多了,自然能够想到是凯撒解密: 查看得到答案; flag{4c850c5b3b2756e67a91bad8e046dda} 2: 解题思路:是我想太多 ...

  2. 在ASP.NET Core中给上传图片功能添加水印

    在传统的.NET框架中,我们给图片添加水印有的是通过HttpModules或者是HttpHandler,然后可以通过以下代码添加水印: var image = new WebImage(imageBy ...

  3. 手把手学会MySql主从配置

    001 开启二进制日志. vi /data/mysql/port-3306/my.cnf log-bin=mysql-bin 002 在master上为从主机新建一个专门用于同步的账户,并授权REPL ...

  4. Java安全(权限)框架 - Shiro 功能讲解 架构分析

    Java安全(权限)框架 - Shiro 功能讲解 架构分析 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 简述Shiro Shiro出自公司Apache(阿帕奇),是java的一 ...

  5. Java虚拟机二:使用jvisualvm工具远程监控tomcat内存

    jdk中自带了很多工具可以用于性能分析,位于jdk的bin目录下,jvisualvm工具可以以图形化的方式更加直观的监控本地以及远程的java进程的内存占用,线程状态等信息. 一.配置tomcat 在 ...

  6. java监听器简述

    监听器的概念 所谓监听器就是对内置对象的状态或者属性变化进行监听并且做出反应的特殊servlet,并且也需要在web.xml文件中进行相关配置. 内置对象的状态变化:初始化和销毁,也就是说当内置对象初 ...

  7. java接口与抽象类

    本片随笔讲讲java中接口与抽象类. 一,接口 1.什么是接口? 那在日常生活中接口是什么呢?就是两个对象之间进行连接的部分就是接口,就比如热水器与水管的接口一样,他可以确保不同的东西之间的顺利连接, ...

  8. 六大设计原则(二)LSP里氏替换原则

    里氏替换原则LSP(Liskov Subsituation Principle) 里氏替换原则定义 所有父类出现的地方可以使用子类替换并不会出现错误或异常,但是反之子类出现的地方不一定能用父类替换. ...

  9. DataPipeline丨金融行业如何统一管理单个任务下所有API的同步情况

    目前,依靠"手工人力"的电子表格数据治理模式逐渐被"自动智能"的专业工具取代.数据管理员.业务分析师开始采用"平台工具"来梳理主数据.元数据 ...

  10. [转]MS Excel VBO option missing in Blue Prism

    本文转自:https://stackoverflow.com/questions/48706743/ms-excel-vbo-option-missing-in-blue-prism 问: I am ...