图像检索(5):基于OpenCV实现小型的图像数据库检索

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

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

  • train,构建图像集的特征数据库。
  • retrieval,检索,给定图像,从图像库中返回最类似的图像

构建图像数据库的过程如下:

  • 生成图像集的视觉词汇表(Vocabulary)
    • 提取图像集所有图像的sift特征
    • 对得到的sifte特征集合进行聚类,聚类中心就是Vocabulary
  • 对图像集中的图像重新编码表示,可使用BoW或者VLAD,这里选择VLAD.
  • 将图像集中所有图像的VLAD表示组合到一起得到一个VLAD表,这就是查询图像的数据库。

得到图像集的查询数据后,对任一图像查找其在数据库中的最相似图像的流程如下:

  • 提取图像的sift特征
  • 加载Vocabulary,使用VLAD表示图像
  • 在图像数据库中查找与该VLAD最相似的向量

构建图像集的特征数据库的流程通常是offline的,查询的过程则需要是实时的,基本流程参见下图:
439761-20180905100126925-385864582.png

由两部分构成: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

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

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

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的内容可以参看文章:

关于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;
}
  • loadsave,为了使用方便,需要能够将生成的视觉词汇表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 添加图像到数据库
  • saveload 将数据库保存为文件(.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.ymldb-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;
};

使用也很简单了,从文件中加载VaocabularyDatabase,设置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

439761-20180905100205924-1793508824.png

上图来总结下整个流程

  • 创建Vocabulary
  • 创建Database
  • Search Similary list

转载于:https://www.cnblogs.com/wangguchangqing/p/9590406.html

大规模图像检索的代码,matlab与c++混合编程。总结了目前图像检索领域目前主要存在的方法。通过阅读该代码,可以对于经典的“词袋”模型(bow模型)有个具体的了解,但是该代码没有提供前序的特征提取,是直接从对提取好的特征向量聚类开始的,包括了k-means,分层k-means(HKM)聚类,倒排文件的建立和索引等,该代码还提供了局部敏感哈希(LSH)方法。最后,这份代码是下面这篇论文的作者提供的, Indexing in Large Scale Image Collections: Scaling Properties and Benchmark-This C++/Matlab package implements several algorithms used for large scale image search. The algorithms are implemented in C++, with an eye on large scale databases. It can handle millions of images and hundreds of millions of local features. It has MEX interfaces for Matlab, but can also be used (with possible future modifications) from Python and directly from C++. It can also be used for approximate nearest neighbor search, especially using the Kd-Trees or LSH implementations. The algorithms can be divided into two broad categories, depending on the approach taken for image search: 1. Bag of Words: ---------------- The images are represented by histograms of visual words. It includes algorithms for computing dictionaries: * K-Means. * Approximate K-Means (AKM). * Hierarchical K-Means (HKM). It also includes algorithms for fast search: * Inverted File Index. * Inverted File Index with Extra Information (for example for implementing Hamming Embedding).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值