zoukankan      html  css  js  c++  java
  • 图像检索(2):均值聚类-构建BoF

    在图像检索时,通常首先提取图像的局部特征,这些局部特征通常有很高的维度(例如,sift是128维),有很多的冗余信息,直接利用局部特征进行检索,效率和准确度上都不是很好。这就需要重新对提取到的局部特征进行编码,以便于匹配检索。
    常用的局部特征编码方法有三种:

    • BoF
    • VLAD
    • FV

    本文主要介绍基于k-means聚类算法的BoF的实现。

    • BoF的原理
    • k均值聚类概述
    • 使用OpenCV实现的BoF

    BoF

    该方法源自于文本处理的词袋模型。Bag-of-words model (BoW model) 最早出现在NLP和IR领域. 该模型忽略掉文本的语法和语序, 用一组无序的单词(words)来表达一段文字或一个文档. 近年来, BoW模型被广泛应用于计算机视觉中. 与应用于文本的BoW类比, 图像的特征(feature)被当作单词(Word).

    例如下面的句子:
    John likes to watch movies. Mary likes too. John also likes to watch football games.
    就可以构建一个词典

    {"John": 1, "likes": 2, "to": 3, "watch": 4, "movies": 5, "also": 6, "football": 7, "games": 8, "Mary": 9, "too": 10}
    

    该字典中包含10个单词, 每个单词有唯一索引, 注意它们的顺序和出现在句子中的顺序没有关联. 根据这个字典, 我们能将上述两句话重新表达为下述两个向量:

    [1, 2, 1, 1, 1, 0, 0, 0, 1, 1]  [1, 1, 1, 1, 0, 1, 1, 1, 0, 0]
    

    这两个向量共包含10个元素, 其中第i个元素表示字典中第i个单词在句子中出现的次数。
    统计词频的时候有两种方法:

    • 词集模型(set of words model) 将每个词出现与否作为特征,忽略词出现的次数,这种模型得到的向量只有0和1两个值;
    • 词袋模型(bag of words model)要统计词出现的次数。

    特征词袋(BoF,Bag Of Feature)借鉴文本处理的词袋(BoW,Bag Of Bag)算法,将图像表示成视觉关键词的统计直方图。就像上面对文本的处理一样,提取文本中出现单词组成词汇表,这里关键是得到图像库的“词汇表”。为了得到图像库的“词汇表",通常对提取到的图像特征进行聚类,得到一定个数的簇。这些聚类得到的簇,就是图像的”词汇“,可以称为视觉词(Visual Word)。聚类形成的簇,可以使用聚类中心来描述,所以,视觉词指的是图像的局部区域特征(如纹理,特征点)经过聚类形成的聚类中心。

    有了视觉词的集合后,就可以将一幅图像表示为(K)维的向量((K)为聚类中心的个数,也就是视觉词的个数),向量的每个分量表示某个视觉词在图像中出现的次数。

    构建图像的BoF的步骤如下:以SIFT特征为例

    1. SIFT特征提取 提取训练集中所有图像的SIFT特征,设有(M)幅图像,共得到(N)个SIFT特征。
    2. 构建视觉词汇表 对提取到的(N)个SIFT特征进行聚类,得到(K)个聚类中心,组成图像的视觉词汇表
    3. 图像的视觉词向量表示,统计每幅图像中视觉词汇的出现的次数,得到图像的特征向量。在检索时,该特征向量就代表该幅图像。统计时,计算图像中提取到的SIFT特征点到各个视觉词(聚类中心)的距离,将其归类到聚类最近的视觉词中。

    所以聚类在构建BoF是很重要的一步,接下来简单的介绍下聚类的基本知识以及最常用的聚类算法k-means算法。

    聚类概述

    聚类(Clustering)是一种无监督学习算法,其目的是将数据集中的样本划分为若干个不相交的子集,每个子集称为一个簇(Cluster)。聚类的时候并不关心某一类是什么,只根据数据的相似性,将数据划分到不同的组中。每个组内的成员具有相似的性质。

    聚类算法说白了就是给你一大堆点的坐标(维度可以是很高),然后通过一个向量的相似性准则(通常是距离,比如欧拉距离),然后把相近的点放在一个集合里面,归为一类。

    更正式的说,假设有样本集 (D = {x_1,x_2,dots,x_m})(m)个无标记的样本,每个样本可以使用一个(n)维特征向量表示:(x_i = (x_{i1};x_{i2};dots;x_{in})),根据相似的准则,将集合(D)划分为(k)个不相交的簇({C_l|l = 1,2,dots,k})。每个簇可以用其聚类中心来描述(lambda_l = (x_{l1},x_{l2},dots,x_{ln}),l = 1,2,dots,k).

    相似性度量(距离计算)

    两个向量的相似性,通常可以使用距离度量,距离越大,相似性越小;距离越小,相似性越大。给定两个样本(x = (x_1,x_2,dots,x_n), y = (y_1,y_x,dots,y_n)),常用的距离计算有:

    • 欧氏距离 Euclidean distance,这个应该是最有名的了。(dist = |x_i-y_i|_2 sqrt{sum_{i=1}^n(x_i - y_i)^2}),欧氏距离也是一种(l_2)范数。
    • 曼哈顿距离 Manhattan distance,也被称为城市街区距离。(dist = |x_i-y_i|_1= sum_{i=1}^n|x_i -y_i|),曼哈顿距离也是(l_1)范数。
    • 切比雪夫距离 Chebyshev distance,(dist = max(|x_i - y_i|))

    以上三种距离可以统称为闵可夫斯基距离 Minkowski distance,(dist = (sum_{i=1}^n|x_i-y_i|^p)^{frac{1}{p}})

    • (p=1)为曼哈顿距离
    • (p=2)为欧氏距离
    • (p oinfty)为切比雪夫距离。

    当然,度量两个向量相似性的方法还有很多种,这里只列举了最常用的,在均值聚类算法中经常的使用的是欧氏距离和曼哈顿距离。

    k-means

    聚类算法可以分为三类:

    • 原型聚类,此类算法假设聚类结构能够通过一组原型描述,这里原型指的是样本空间中具有代表性的点。
    • 密度距离,该类算法假设聚类结构能够通过样本分布的紧密程度来确定。
    • 层次聚类,在不同的层次对数据集进行划分,从而形成树形的聚结构。

    (k)均值聚类是原型聚类的一种,它使用簇内的均值向量来描述每个簇,假设给定的样本集(D = {x_1,x_2,dots,x_m}),得到(k)个簇,(C = {C_1,C_2,dots,C_k})(k)means算法的目标是使,簇内样本到簇的质心(簇内的均值向量)距离最小

    [E = sum_{i=1}^ksum_{xin C_i}|x-u_i|_2^2,u_i = frac{1}{|C_i|}sum xin C_i ]

    (u_i)是簇(C_i)的均值向量。(E)就表示了簇内样本围绕着均值向量(簇的中心)的紧密程度,(E)越小则簇内样本相似度越高。
    要使得(E)的值最小,是一个NP难题,因此均值聚类使用贪心策略,通过迭代的方法来求解最优解。

    Lioyd's Algorithm

    均值聚类算法多数是基于Lioyd's Algorithm,其流程很简单。首先,随机的确定(k)个初始点作为各个簇的质心。然后将数据集中每个点分配到与其最近的质心代表的簇中。然后更新各个簇的质心为该簇所有向量的均值。具体表示如下:

    创建k个点作为起始质心(通常随机选择)
    当任意一个点所在的簇发生变化时
        对数据集中的每个数据点
            对每个质心
                计算质心与数据点之间的距离
            将数据点分配到与其最近的簇中
        对每个簇,计算簇中所有点的均值作为新的质心
    

    k-means算法有两个输入参数簇的个数(k)以及初始的簇的质心

    • 簇的个数(k)通常可以使用“肘点法”,通过最小化(E)来确定
    • 对于初始的质心的选择,可以随机确定或者使用k-means++来确定

    vlfeat以及OpenCV实现

    vlfeat

    vlfeat实现了三种的k-means算法:

    • Lioyd's Algorithm
    • Elkan's Algorithm 使用三角形不等式对Lioyd算法的一种优化,提高了其计算的速度,本质上两者是一样的。
    • ANN Algorithm 适用于大规模的数据集(百万级)簇的个数成百上千

    可以使用如下代码来初始化k-means算法:

    VlKMeans * fkmeans = vl_kmeans_new(VL_TYPE_FLOAT, VlDistanceL2);
    vl_kmeans_set_algorithm(fkmeans, VlKMeansElkan);
    vl_kmeans_init_centers_with_rand_data(fkmeans,data,data_dim,data_num,k);
    

    首先设置聚类时的数据类型为float,相似性度量使用l2距离也就是欧氏距离;接着设置使用的算法为是Elkan,并且使用随机的方法确定k个簇的中心。
    初始化完成后,使用如下代码进行聚类

    vl_kmeans_cluster(fkmeans, data, data_dim, data_num, k);  
    

    需要指定数据,数据的维度,数据的个数以及簇的中心,这里需要注意的是数据的维度。聚类数据的维度指的是,一个数据有几个分量组成。例如,

    • 一幅灰度图像,其聚类的对象是像素的像素值。灰度图,一个像素只有一个分量,则灰度图聚类数据的维度就是1维。
    • RGB图像,一个像素有RGB三个分量组成,则其聚类数据的维度就是3维。
    • sift描述子,一个sift描述子是128维的向量,则其聚类数据的维度就是128维。
    OpenCV

    相较于vlfeat,OpenCV中的kmeans则更易于调用。

    
    double cv::kmeans(InputArray data,
    	int 	K,
    	InputOutputArray 	bestLabels,
    	TermCriteria 	criteria,
    	int 	attempts,
    	int 	flags,
    	OutputArray 	centers = noArray() 
    )
    
    • data 数据集,每一行代表数据集中的一个样本
    • k 聚类形成簇的个数
    • bestLabels 数据集中每个样本在簇的index
    • criteria 迭代终止的条件。
    • attempts 算法执行的次数
    • flags 初始质心的指定方法,KMEANS_RANDOM_CENTERS 随机指定;KMEANS_PP_CENTERSk-means++;KMEANS_USE_INITIAL_LABELS 算法第一次执行时,使用用户提供的初始质心;第二次及以后的执行使用随机或者半随机的方式初始化质心

    在OpenCV中TermCriteria表示迭代算法结束的两种条件:

    • 达到了迭代的次数
    • 迭代产生的结果达到了指定的精度

    该类的初始化需要三个参数

    • type 有三种选择 COUNT, EPS or COUNT + EPS
    • maxCount 最大的迭代次数
    • epsilon 精度

    构建BoF

    在上一篇文章图像检索(1): 再论SIFT-基于vlfeat实现中实现了SIFT特征点的提取,这里再对提取到的特征点进行聚类,构建图像集的视觉词汇表。

    基于SIFT特征构建BoF的步骤:

    • 提取sift特征点
    • 聚类生成视觉词汇表 Visual Vocabulary
    • 统计视觉词在每张图像中出现的频率,形成BoF

    基于OpenCV的实现如下:

    void bof_encode(const string &image_folder,int k,vector<Mat> &bof) {
    
        vector<string> image_file_list;
        get_file_name_list(image_folder,image_file_list);
    
        // 提取图像的sift
        vector<Mat> descriptor_list;
        Ptr<xfeatures2d::SIFT> sift = xfeatures2d::SIFT::create();
        for(const string & file: image_file_list){
            cout << "Extracte sift feature #" << file << endl;
            vector<KeyPoint> kpts;
            Mat des;
            Mat img = imread(file);
            CV_Assert(!img.empty());
            sift->detectAndCompute(img,noArray(),kpts,des);
            descriptor_list.push_back(des);
        }
    
        // 将各个图像的sift特征组合到一起
        Mat descriptor_stack;
        vconcat(descriptor_list,descriptor_stack);
    
        // 聚类
        Mat cluster_centers;
        vector<int> labels;
        kmeans(descriptor_stack,k,labels,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 
            10, 1.0),3, KMEANS_RANDOM_CENTERS,cluster_centers);
    
        // labels已经得到了每个样本(特征点)所属的簇,需要进行统计得到每一张图像的BoF
        int index = 0;
        for(Mat img : descriptor_list){
            // For all keypoints of each image 
            auto cluster = new int[k];
            for(int i = 0; i < img.rows; i ++){
                cluster[labels[index]] ++;
                index ++;
            }
    
            Mat mat(1,k,CV_32S);
            auto ptr = mat.ptr<int>(0);
            mempcpy(ptr,cluster,sizeof(int) * k);
    
            bof.push_back(mat);
            delete cluster;
        }
    }
    

    提取特征点后,需要将得到的sift的特征描述子组合到一起,进行聚类,需要用到函数vconcat,该函数在y方向上将Mat组合在一起,需要各个Mat的列是一样,组合得到的Mat仍然有相同的列;同样的函数hconcat在水平方向上组合Mat,组合得到的Mat的行保持不变。

    在聚类后可以得到所有图像的各个sift特征所属的簇,上述代码的:

        // labels已经得到了每个样本(特征点)所属的簇,需要进行统计得到每一张图像的BoF
        int index = 0;
        for(Mat img : descriptor_list){
            // For all keypoints of each image 
            auto cluster = new int[k];
            for(int i = 0; i < img.rows; i ++){
                cluster[labels[index]] ++;
                index ++;
            }
    
            Mat mat(1,k,CV_32S);
            auto ptr = mat.ptr<int>(0);
            mempcpy(ptr,cluster,sizeof(int) * k);
    
            bof.push_back(mat);
            delete cluster;
        }
    

    就是统计每张图像中,各个Visual Word的个数。这样一幅图像就可以使用一个K维的向量表示。

  • 相关阅读:
    Atitit. visual studio vs2003 vs2005 vs2008  VS2010 vs2012 vs2015新特性 新功能.doc
    Atitit. C#.net clr 2.0  4.0新特性
    Atitit. C#.net clr 2.0  4.0新特性
    Atitit.通过null 参数 反射  动态反推方法调用
    Atitit.通过null 参数 反射  动态反推方法调用
    Atitit..net clr il指令集 以及指令分类  与指令详细说明
    Atitit..net clr il指令集 以及指令分类  与指令详细说明
    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL
    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL
    Atitit.跨语言反射api 兼容性提升与增强 java c#。Net  php  js
  • 原文地址:https://www.cnblogs.com/wangguchangqing/p/9222267.html
Copyright © 2011-2022 走看看