图像检索之IF-IDF,RootSift,VLAD

TF-IDF

TF-IDF是一种用于信息检索的常用加权技术,在文本检索中,用以评估词语对于一个文件数据库中的其中一份文件的重要程度。词语的重要性随着它在文件中出现的频率成正比增加,但同时会随着它在文件数据库中出现的频率成反比下降。像‘的',‘我们',‘地'等这些常用词在所有文章中出现的频率会很高,并不能很好的表征一个文档的内容。

同样的在图像检索中也引入了IF-IDF权重,

词频(Term Frequency,TF) 一个visual word在一个图像中出现的频率的很高,则说明该visual word 能够很好的代表图像的内容。

逆文档词频(Inverse Document Frequency,IDF) 一些常见的word,会在每一图像出现的频率都很高,但是这些word并不能很好的表示图像的内容,所以要给这一部分word低一些的权重。IDF,描述一个word的普遍重要性,如古欧word在很多的图像中出现的频率都很高,则给予其较低的权重。

将分母+1是为了防止除数为0的情况出现。从上式中可以看出,包含当前word的图像个数越多,IDF的值越小,说明该词越不重要。反之,该词越重要。

计算得到了TF和IDF,则有

\[TF-IDF = TF * IDF\]

从TF和IDF的计算公式可以看出,IDF是针对整个图像数据库而言的,可以在训练完成后计算一次得到。而TF则是针对具体的某张图像来说的,需要多次计算。

将TF-IDF权值赋给BoW向量,再进行\(l_2\)的归一化,即可得到一个可用于图像检索的向量。

C++实现

void compute_idf(const vector<<vector<int>> &bow,vector<float> &idf){

    int img_count = bow.size();
    int clu_count = bow[0].size();

    idf = vector<float>(clu_count,1.0);

    for(int i = 0; i < img_count; i ++){
        for(int j = 0; j < clu_count; j ++){
            if(bow[i][j] != 0) idf[j] ++;
        }
    }

    for(int i = 0; i < idf.size(); i ++){
        idf[i] = log(img_count / idf[i]);
    }
}

上面代码计算的是图像库的IDF(IDF是针对整个图像库而言的)。
针对某一张图片,都需要计算一次TF。TF的计算公式:,可以看着是对图像的BoW向量进行\(l_1\)的归一化。

void compute_tf(const vector<int> &bow,vector<float> &tf){

    tf = vector<float>(bow.size(),0);

    int sum = 0; // All words in the image
    for(int i = 0; i < bow.size(); i ++){
        sum += bow[i];
    }

    for(int i = 0; i < bow.size(); i ++){
        tf[i] = (float)bow[i] / sum;
    }
}

RootSift

在Arandjelovic和Zisserman 2012的论文Three things everyone should know to improve object retrieval 提出了RootSift。

当比较直方图时,使用欧氏距离通常比卡方距离或Hellinger核时的性能差,但是在使用sift特征点为什么一直都使用欧氏距离呢?
不论是对sift特征点进行匹配,还是对sift特征集合进行聚类得到视觉词汇表,又或者对图像进行BoW编码,都使用的是欧氏距离。但是sift特征描述子本质上也是一种直方图,为什么对sift特征描述子进行比较的时候要使用欧氏距离呢,有没有一种更精确的比较方法呢?

sift描述子统计的是关键点邻域的梯度直方图,更详细的介绍可以参考图像检索(1): 再论SIFT-基于vlfeat实现

Zisserman 认为只所以一直使用欧氏距离来测量sift特征的相似度,是由于在sift提出的时候,使用的是欧氏距离的度量,可以找出一种比较欧氏距离更为精确的度量方法。Arandjelovic和Zisserman提出了RootSift对sift特征进行扩展。

在提取得到sift的描述向量\(x\)后,进行以下处理,即可得到RootSift
1.对特征向量\(x\)进行\(l_1\)的归一化(\(l_1-normalize\))得到\(x'\)
2.对\(x'\)的每一个元素求平方根
3.进行\(l_2-normalize\),可选

最后一步,是否进行\(l_2\)的归一化,有些不一致。 在paper 并没有指出需要进行\(l_2\)的归一化,但是在presentation, 却有\(l_2\)归一化这一步。也有认为,显式地执行L2规范化是不需要的。通过采用L1规范,然后是平方根,已经有L2标准化的特征向量,不需要进一步的标准化。

Python实现

# import the necessary packages
import numpy as np
import cv2

class RootSIFT:
    def __init__(self):
        # initialize the SIFT feature extractor
        self.extractor = cv2.DescriptorExtractor_create("SIFT")

    def compute(self, image, kps, eps=1e-7):
        # compute SIFT descriptors
        (kps, descs) = self.extractor.compute(image, kps)

        # if there are no keypoints or descriptors, return an empty tuple
        if len(kps) == 0:
            return ([], None)

        # apply the Hellinger kernel by first L1-normalizing and taking the
        # square-root
        descs /= (descs.sum(axis=1, keepdims=True) + eps)
        descs = np.sqrt(descs)
        #descs /= (np.linalg.norm(descs, axis=1, ord=2) + eps)

        # return a tuple of the keypoints and descriptors
        return (kps, descs)

来自 https://www.pyimagesearch.com/2015/04/13/implementing-rootsift-in-python-and-opencv/

C++ 实现

for(int i = 0; i < siftFeature.rows; i ++){
        // Conver to float type
        Mat f;
        siftFeature.row(i).convertTo(f,CV_32FC1);

        normalize(f,f,1,0,NORM_L1); // l1 normalize
        sqrt(f,f); // sqrt-root  root-sift
        rootSiftFeature.push_back(f);
    }

VLAD

局部聚合向量(Vector of Locally Aggregated Descriptors,VLAD)

前面介绍的BoW方法,在图像的检索和检索中有这广泛的应用。BoW通过聚类,对图像的局部特征进行重新编码,有很强的表示能力,并且使用SVM这样基于样本间隔的分类器,也能取得了很好的分类效果。但是在图像规模比较大的情况下,由于视觉词汇表Vocabulary大小的限制,BoW对图像的表示会越来越粗糙,编码后损失的图像信息较多,检索精度也随之而降低。

2010年,论文Aggregating local descriptors into a compact image representation中提出了一对新的图像表示方法,VLAD。从三个方面进行改进:

  • 使用VLAD表示图像的局部特征
  • PCA
  • 索引ADC的构建方法

BoW的表示方法中,是统计每个特征词汇在图像中出现的频率。VLAD则是求落在同一个聚类中心的特征和该聚类中心残差的累加和。公式表示如下:

\[v_{i,j} = \sum_{x\ such\ that\ NN(x)=c_i}x_j - c_{i,j}\]

\(x_j\)是图像的第\(j\)个特征点,\(c_i\)则是和该特征点最近的聚类中心,\(x_j - c_{i,j}\)表示特征点和其最近聚类中心的差。假如使用的是sift特征,视觉词汇表Vocabulary的大小为\(k\),则可以得到\(k\)个128维的向量\(v_{i,j}\)。
然后将这\(k*d\)个向量(\(d\)为图像特征的长度,例如sift为128维)\(v_{i,j}\)拉伸为一个\(k*d\)长度的一维向量,再对拉伸后的向量做一个\(l_2\)的归一化就得到图像的VLAD表示。

由于,VLAD是特征和其最邻近的聚类中心的残差和,该向量的很多分量很多分量都为0,也就是说该向量是稀疏的(sparse,很少的分量占有大部分的能量),所以可以对VLAD进行降维处理(例如,PCA)能进一步缩小向量的大小。

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
}

借助于OpenCV实现还是比较简单的。这里使用FlannBasedMatchermatch方法来查找特征最邻近的聚类中心(视觉词汇)。可以分为以下三步:

  • subtract计算特征和其最近邻的聚类中心的差值,add将同一个聚类中心的差值累加起来
  • 对上面得到的矩阵的responseHist进行\(l_2\)的归一化处理
  • 使用reshape方法,将矩阵拉伸为一维\(k*d(128)\)的一维向量VLAD。

Summary

Bow通常要搭配TF-IDF进行使用,但是由于Vocabulary的大小的限制,VLAD是一个不错的替代选择。RootSift是对原生sift的扩展。

(0)

相关推荐

  • 基于OpenCV实现小型的图像数据库检索功能

    本文对前面的几篇文章进行个总结,实现一个小型的图像检索应用. 一个小型的图像检索应用可以分为两部分: train,构建图像集的特征数据库. retrieval,检索,给定图像,从图像库中返回最类似的图像 构建图像数据库的过程如下: 生成图像集的视觉词汇表(Vocabulary) 提取图像集所有图像的sift特征 对得到的sifte特征集合进行聚类,聚类中心就是Vocabulary 对图像集中的图像重新编码表示,可使用BoW或者VLAD,这里选择VLAD. 将图像集中所有图像的VLAD表示组合到一

  • python实现图像检索的三种(直方图/OpenCV/哈希法)

    简介: 本文介绍了图像检索的三种实现方式,均用python完成,其中前两种基于直方图比较,哈希法基于像素分布. 检索方式是:提前导入图片库作为检索范围,给出待检索的图片,将其与图片库中的图片进行比较,得出所有相似度后进行排序,从而检索结果为相似度由高到低的图片.由于工程中还包含Qt界面类.触发函数等其他部分,在该文档中只给出关键函数的代码. 开发系统:MacOS 实现方式:Qt + Python 方法一:自定义的直方图比较算法 a) 基本思路 遍历图片像素点,提取R\G\B值并进行对应的计数,得

  • opencv3/C++ PHash算法图像检索详解

    PHash算法即感知哈希算法/Perceptual Hash algorithm,计算基于低频的均值哈希.对每张图像生成一个指纹字符串,通过对该字符串比较可以判断图像间的相似度. PHash算法原理 将图像转为灰度图,然后将图片大小调整为32*32像素并通过DCT变换,取左上角的8*8像素区域.然后计算这64个像素的灰度值的均值.将每个像素的灰度值与均值对比,大于均值记为1,小于均值记为0,得到64位哈希值. PHash算法实现 将图片转为灰度值 将图片尺寸缩小为32*32 resize(src

  • 图像检索之IF-IDF,RootSift,VLAD

    TF-IDF TF-IDF是一种用于信息检索的常用加权技术,在文本检索中,用以评估词语对于一个文件数据库中的其中一份文件的重要程度.词语的重要性随着它在文件中出现的频率成正比增加,但同时会随着它在文件数据库中出现的频率成反比下降.像'的','我们','地'等这些常用词在所有文章中出现的频率会很高,并不能很好的表征一个文档的内容. 同样的在图像检索中也引入了IF-IDF权重, 词频(Term Frequency,TF) 一个visual word在一个图像中出现的频率的很高,则说明该visual

  • 图像检索之基于vlfeat实现SIFT特征

    概述 基于内容的图像检索技术是采用某种算法来提取图像中的特征,并将特征存储起来,组成图像特征数据库.当需要检索图像时,采用相同的特征提取技术提取出待检索图像的特征,并根据某种相似性准则计算得到特征数据库中图像与待检索图像的相关度,最后通过由大到小排序,得到与待检索图像最相关的图像,实现图像检索.图像检索的结果优劣取决于图像特征提取的好坏,在面对海量数据检索环境中,我们还需要考虑到图像比对(图像相似性考量)的过程,采用高效的算法快速找到相似图像也至关重要. 在构建图像特征库的时候,通常不会使用原始

  • Python Pytorch学习之图像检索实践

    目录 背景 图像表现 搜索 随着电子商务和在线网站的出现,图像检索在我们的日常生活中的应用一直在增加. 亚马逊.阿里巴巴.Myntra等公司一直在大量利用图像检索技术.当然,只有当通常的信息检索技术失败时,图像检索才会开始工作. 背景 图像检索的基本本质是根据查询图像的特征从集合或数据库中查找图像. 大多数情况下,这种特征是图像之间简单的视觉相似性.在一个复杂的问题中,这种特征可能是两幅图像在风格上的相似性,甚至是互补性. 由于原始形式的图像不会在基于像素的数据中反映这些特征,因此我们需要将这些

  • .NET下文本相似度算法余弦定理和SimHash浅析及应用实例分析

    本文实例讲述了.NET下文本相似度算法余弦定理和SimHash浅析及应用.分享给大家供大家参考.具体分析如下: 余弦相似性 原理:首先我们先把两段文本分词,列出来所有单词,其次我们计算每个词语的词频,最后把词语转换为向量,这样我们就只需要计算两个向量的相似程度.   我们简单表述如下   文本1:我/爱/北京/天安门/ 经过分词求词频得出向量(伪向量)  [1,1,1,1]   文本2:我们/都爱/北京/天安门/ 经过分词求词频得出向量(伪向量)  [1,0,1,2]   我们可以把它们想象成空

  • github版本库使用详细图文教程(命令行及图形界面版)

    Git是一个分布式的版本控制系统,作为开源代码库以及版本控制系统,Github目前拥有140多万开发者用户.随着越来越多的应用程序转移到了云上,Github已经成为了管理软件开发以及发现已有代码的首选方法. > Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理.在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中.目前,包括 Rubinius和Merb在内的很多知名项目都使用了Git.Git同样可以被诸如Capistrano和

  • Java中的反射机制详解

    Java中的反射机制详解 反射,当时经常听他们说,自己也看过一些资料,也可能在设计模式中使用过,但是感觉对它没有一个较深入的了解,这次重新学习了一下,感觉还行吧! 一,先看一下反射的概念: 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高! 看概念很晕的,继续往下

  • Java基于余弦方法实现的计算相似度算法示例

    本文实例讲述了Java基于余弦方法实现的计算相似度算法.分享给大家供大家参考,具体如下: (1)余弦相似性 通过测量两个向量之间的角的余弦值来度量它们之间的相似性.0度角的余弦值是1,而其他任何角度的余弦值都不大于1;并且其最小值是-1.从而两个向量之间的角度的余弦值确定两个向量是否大致指向相同的方向.所以,它通常用于文件比较. 相关介绍可参考百度百科:余弦相似性 (2)算法实现的中未使用权重(IDF ---逆文档频率),使用词项的出现次数作为向量空间的值. import java.util.H

随机推荐