在图像检索时,通常首先提取图像的局部特征,这些局部特征通常有很高的维度(例如,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.

就可以构建一个词典

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

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

  1. [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\to\infty\)为切比雪夫距离。

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

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}^k\sum_{x\in C_i}\|x-u_i\|_2^2,u_i = \frac{1}{|C_i|}\sum x\in C_i
\]

\(u_i\)是簇\(C_i\)的均值向量。\(E\)就表示了簇内样本围绕着均值向量(簇的中心)的紧密程度,\(E\)越小则簇内样本相似度越高。

要使得\(E\)的值最小,是一个NP难题,因此均值聚类使用贪心策略,通过迭代的方法来求解最优解。

Lioyd's Algorithm

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

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

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

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

vlfeat以及OpenCV实现

vlfeat

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

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

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

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

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

初始化完成后,使用如下代码进行聚类

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

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

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

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


  1. double cv::kmeans(InputArray data,
  2. int K,
  3. InputOutputArray bestLabels,
  4. TermCriteria criteria,
  5. int attempts,
  6. int flags,
  7. OutputArray centers = noArray()
  8. )
  • 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的实现如下:

  1. void bof_encode(const string &image_folder,int k,vector<Mat> &bof) {
  2. vector<string> image_file_list;
  3. get_file_name_list(image_folder,image_file_list);
  4. // 提取图像的sift
  5. vector<Mat> descriptor_list;
  6. Ptr<xfeatures2d::SIFT> sift = xfeatures2d::SIFT::create();
  7. for(const string & file: image_file_list){
  8. cout << "Extracte sift feature #" << file << endl;
  9. vector<KeyPoint> kpts;
  10. Mat des;
  11. Mat img = imread(file);
  12. CV_Assert(!img.empty());
  13. sift->detectAndCompute(img,noArray(),kpts,des);
  14. descriptor_list.push_back(des);
  15. }
  16. // 将各个图像的sift特征组合到一起
  17. Mat descriptor_stack;
  18. vconcat(descriptor_list,descriptor_stack);
  19. // 聚类
  20. Mat cluster_centers;
  21. vector<int> labels;
  22. kmeans(descriptor_stack,k,labels,TermCriteria(TermCriteria::EPS + TermCriteria::COUNT,
  23. 10, 1.0),3, KMEANS_RANDOM_CENTERS,cluster_centers);
  24. // labels已经得到了每个样本(特征点)所属的簇,需要进行统计得到每一张图像的BoF
  25. int index = 0;
  26. for(Mat img : descriptor_list){
  27. // For all keypoints of each image
  28. auto cluster = new int[k];
  29. for(int i = 0; i < img.rows; i ++){
  30. cluster[labels[index]] ++;
  31. index ++;
  32. }
  33. Mat mat(1,k,CV_32S);
  34. auto ptr = mat.ptr<int>(0);
  35. mempcpy(ptr,cluster,sizeof(int) * k);
  36. bof.push_back(mat);
  37. delete cluster;
  38. }
  39. }

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

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

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

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

图像检索(2):均值聚类-构建BoF的更多相关文章

  1. 基于核方法的模糊C均值聚类

    摘要: 本文主要针对于FCM算法在很大程度上局限于处理球星星团数据的不足,引入了核方法对算法进行优化.  与许多聚类算法一样,FCM选择欧氏距离作为样本点与相应聚类中心之间的非相似性指标,致使算法趋向 ...

  2. 100天搞定机器学习|day44 k均值聚类数学推导与python实现

    [如何正确使用「K均值聚类」? 1.k均值聚类模型 给定样本,每个样本都是m为特征向量,模型目标是将n个样本分到k个不停的类或簇中,每个样本到其所属类的中心的距离最小,每个样本只能属于一个类.用C表示 ...

  3. 【转】算法杂货铺——k均值聚类(K-means)

    k均值聚类(K-means) 4.1.摘要 在前面的文章中,介绍了三种常见的分类算法.分类作为一种监督学习方法,要求必须事先明确知道各个类别的信息,并且断言所有待分类项都有一个类别与之对应.但是很多时 ...

  4. 5-Spark高级数据分析-第五章 基于K均值聚类的网络流量异常检测

    据我们所知,有‘已知的已知’,有些事,我们知道我们知道:我们也知道,有 ‘已知的未知’,也就是说,有些事,我们现在知道我们不知道.但是,同样存在‘不知的不知’——有些事,我们不知道我们不知道. 上一章 ...

  5. 机器学习实战5:k-means聚类:二分k均值聚类+地理位置聚簇实例

    k-均值聚类是非监督学习的一种,输入必须指定聚簇中心个数k.k均值是基于相似度的聚类,为没有标签的一簇实例分为一类. 一 经典的k-均值聚类 思路: 1 随机创建k个质心(k必须指定,二维的很容易确定 ...

  6. Python实现kMeans(k均值聚类)

    Python实现kMeans(k均值聚类) 运行环境 Pyhton3 numpy(科学计算包) matplotlib(画图所需,不画图可不必) 计算过程 st=>start: 开始 e=> ...

  7. 多核模糊C均值聚类

    摘要: 针对于单一核在处理多数据源和异构数据源方面的不足,多核方法应运而生.本文是将多核方法应用于FCM算法,并对算法做以详细介绍,进而采用MATLAB实现. 在这之前,我们已成功将核方法应用于FCM ...

  8. 机器学习理论与实战(十)K均值聚类和二分K均值聚类

    接下来就要说下无监督机器学习方法,所谓无监督机器学习前面也说过,就是没有标签的情况,对样本数据进行聚类分析.关联性分析等.主要包括K均值聚类(K-means clustering)和关联分析,这两大类 ...

  9. 第十篇:K均值聚类(KMeans)

    前言 本文讲解如何使用R语言进行 KMeans 均值聚类分析,并以一个关于人口出生率死亡率的实例演示具体分析步骤. 聚类分析总体流程 1. 载入并了解数据集:2. 调用聚类函数进行聚类:3. 查看聚类 ...

随机推荐

  1. bzoj2806 [Ctsc2012]Cheat

    我们的目的就是找到一个最大的L0,使得该串的90%可以被分成若干长度>L0的字典串中的子串. 明显可以二分答案,对于二分的每个mid 我们考虑dp:f[i]表示前i个字符,最多能匹配上多少个字符 ...

  2. 数字证书中读取PublicKey

    1. 读取https签发证书中的key 1) 在下面的代码中,是实现读取证书字符串来读取key的,CERTIFICATE 就是一个证书的字符串, 而方法cf.generateCertificate() ...

  3. 【Java进阶】并发编程

    PS:整理自极客时间<Java并发编程> 1. 概述 三种性质 可见性:一个线程对共享变量的修改,另一个线程能立刻看到.缓存可导致可见性问题. 原子性:一个或多个CPU执行操作不被中断.线 ...

  4. 菜鸟如何反转到资深Web安全工程师

    90后理工男,计算机专业,毕业于985院校,从事Web安全工作,两年多的时间里先后跳槽3家公司,跳槽理由主要有以下几点:加班多.薪资低.工作内容枯燥,不想安于现状,寄希望于通过跳槽找到一个“钱多.活少 ...

  5. 带着新人看java虚拟机02

    上一节是把大概的流程给过了一遍,但是还有很多地方没有说到,后续的慢慢会涉及到,敬请期待! 这次我们说说垃圾收集器,又名gc,顾名思义,就是收集垃圾的容器,那什么是垃圾呢?在我们这里指的就是堆中那些没人 ...

  6. python之循序渐进学习装饰器

    python装饰器的定义:在代码运行期间在不改变原函数定义的基础上,动态给该函数增加功能的方式称之为装饰器(Decorator) 装饰器的优点和用途: 1. 抽离出大量函数中与函数功能本身无关的的雷同 ...

  7. Spring中的@conditional注解

    今天主要从以下几方面来介绍一下@Conditional注解 @Conditional注解是什么 @Conditional注解怎么使用 1,@Conditional注解是什么 @Conditional注 ...

  8. JQuery --- 第二期 (jQuery属性操作)

    个人学习笔记 1.JQuery的内容选择器 <!DOCTYPE html> <html lang="en"> <head> <meta c ...

  9. css实现发光文字,以及一点点js特效

    效果图: 代码如下: </head> <style> body{ background-color:#000; } .textArea{ font-size:100px; co ...

  10. Web学习的第四天

    今天通过前面的学习,自己写了段代码. 今天还学习了列表,列表的分类有:无序列表.菜单列表.目录列表.有序列表.定义列表. 列表类型                     标记符号          ...