• 图像检索(4):IF-IDF,RootSift,VLAD


    • TF-IDF
    • RootSift
    • VLAD

    TF-IDF

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

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

    • 词频(Term Frequency,TF) 一个visual word在一个图像中出现的频率的很高,则说明该visual word 能够很好的代表图像的内容。 $$TF = frac{图像中某个word出现的次数}{图像word的总的个数}$$

    • 逆文档词频(Inverse Document Frequency,IDF) 一些常见的word,会在每一图像出现的频率都很高,但是这些word并不能很好的表示图像的内容,所以要给这一部分word低一些的权重。IDF,描述一个word的普遍重要性,如古欧word在很多的图像中出现的频率都很高,则给予其较低的权重。$$IDF = log(frac{图像的总个数}{含有该word的图像数 + 1})$$
      将分母+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的计算公式:(TF = frac{图像中某个word出现的次数}{图像word的总的个数}),可以看着是对图像的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的扩展。

  • 相关阅读:
    全栈程工程师
    月薪8000的程序员和月薪2万的程序员差别在哪里?
    原型中的访问
    关于 基本类型和复合类型 用== 判断的小问题
    使用原型解决构造函数问题
    前端工程师学习路线 --书籍
    程序员成长之路
    GIT学习(1) Pull和Fetch
    OO面向对象编程:第四单元总结及课程总结
    OO面向对象编程:第三单元总结
  • 原文地址:https://www.cnblogs.com/wangguchangqing/p/9471103.html
Copyright © 2020-2023  润新知