前文中我们介绍了搭建搜索引擎的第一步:定义图像描述子。先来回顾一下描述图像的三个方面。
颜色:表示一个颜色特征的描述子可以用来发掘图像每个通道中像素密度分布模型。这些方法包括了基本颜色统计,比如:均值、标准差、斜率,彩色直方图,可以在一维和多维实现。
纹理:纹理描述符是用于表示图像的感觉、整体外观、物体的触觉质量的模型。一些描述子需要将图像变换到灰度图像,然后及时灰度共生矩阵,并基于此矩阵计算一些统计量,包括对比度、相关、熵等等。一些更高层次的纹理描述子像傅里叶变换、小波变换也是用灰度图像。
形状:许多建立形状描述子的方法依赖于抽取图像中对象的轮廓。一旦有了这些轮廓,就可以计算简单的统计量来表示轮廓特征,就像OpenCV里Hu矩表示的那样。这些统计量就能很好的表示图像中对象的特征。
当选择描述子来提取数据集中的特征时,先想想图像中的哪一方面是我们想要描述的。颜色比较重要?形状呢?纹理的相关结果也很重要啊?
看看Flowers 17数据集里的一些样本,里面有17种花作为示例:
Figure 1 – Flowers 17数据集的一些样本。可以看出,一些花无法用颜色或形状来区分(比如郁金香和流星草的颜色分布很相似)。但是通过提取颜色和形似特征能获取更好的结果。
假如要描述这些图像的目的是搭建一个图片搜索引擎,那么我会首先选择颜色描述子。通过将这些花的颜色特征化,就可以从搜索引擎中返回类似色调的花。
然而,由于我们的图片搜索引擎只是返回相似颜色的花,并不意味着所以的结果都是相关的。许多花也有相同的颜色,却属于完全不相同的种类。
为了确保我们的搜索引擎返回的是相似的花种,我们来研究一下怎么描述这些花瓣的形状。现在我们有两个描述子——用于特征化花瓣的不同色调的颜色描述子,以及表示花瓣的不同轮廓的形状描述子。
联合使用这两个描述子就可以在花朵数据集里建立简单的图片搜索引擎。当然,得先知道怎么索引这些数据集。
建立数据集索引
定义:建立索引就是这样一个过程:用图像描述子从数据集中的每个图像提取特征,然后量化的过程。通常情况下,这些特征会存储在磁盘上供以后使用。
就用上面的这些花朵样本数据,目标是简单的遍历数据集中的图像,提取特征并在磁盘上存储特征。
原则上这是一个简单的概念,但实际上可能会很复杂,主要要看看数据的大小和尺度。相对而言,Flowers数据集是相对小了些。总共只有1300个图像(17种*80张),相比之下像TinEye这类的图片搜索引擎的数据达到了十亿数量级。
下面开始第一步,实例化描述子。
1.实例化描述子
在之前的随笔中提到过我更倾向于用类来抽象图像描述子而不是用函数。而且,我会在类的构造函数里设置一些输入相关的参数(比如直方图上bin的个数)。
为什么要这样设置?
因为使用类(包含构造函数里的描述子参数)而不是函数可以确保对于每个图像都用同样参数和同样的描述子。如果我用cPickle在磁盘上写了描述子,并且以后一直要加载,比如说用户在查询的时候,这时候用类来设置是非常有用的。
为了比较两幅图像,你需要用用你的图像描述子通过某些方式来表示。要是有两张相似的图像,一张用32个bin,另一张用个28bin表示,那我就不知道怎么比较着两张图像了。让我们看看一个通用的图像描述符的Python框架代码:
<span style="font-size:14px;">class GenericDescriptor:
def __init__(self, paramA, paramB):
# store the parameters for use in the 'describe' method
self.paramA = paramA
self.paramB = paramB
def describe(self, image):
# describe the image using self.paramA and self.paramB
# as supplied in the constructor
pass</span>
首先主要这个初始化方法(__init__),这里我给出了相关描述子的相关参数。
然后,看一下描述方法,只有一个要描述图像的参数。当调用这个描述方法的时候,在构造函数里的参数将用于每幅图像。这样可以确保我的图像都是用相同的描述子参数表示。此时类和函数仍一时看不出优劣,但在搭建更大更复杂的图片搜索引擎的时候,面对大量代码,用类的优势就显而易见了。
2.串行或并行?
或许更应该称作“单核或多核?”
原本提取图像特征是一项串行任务,但根据数据集的大小和尺度,用多核处理技术在核/线程之间分工图区图像的特征向量。如果你还只是刚入门计算机视觉和图片搜索引擎,这看起来有点困难。为什么要添加额外的复杂性呢?用多线程/进程调试程序比单线程执行困难。除非你的数据集相当大并能有效用多核处理,否则我不会把这个时间片分成多索引进程来处理,所以,并不用为此费尽心思。当然,将来肯定会有关于用并行任务建立实际所以方法的博文。
3.写到磁盘
这一步骤似乎显而易见,如果你要遍历所有数据提取的特征成果,最好把索引写到磁盘上。
对于小数据集,用一个简单的Python字典就足够了,索引键可以是图片的文件名(假设文件名唯一),值就是用描述子提取的特征值。最后用cPickle将索引载入到文件里。
对于大数据而言,你得进一步考虑如何操作你的特征(比如尺度、归一化、降维等),最好还是使用h5py将特性写到磁盘。
到底用哪种方法?还真得看实际情况。
如果你仅仅是计算机视觉和图片搜索引擎的初学者,用的数据量也比较小,建议用Python的内置字典和cPickle。如果你在NumPy上有点经验,建议考虑用h5py试一试,并和上诉方法做个比较。
作为起步,我在代码例子里用的是cPickle,但随着进度深入,我也会在例程中逐渐引进h5py。
小结:
我们探索了如何索引图像数据集。建立索引是一个从数据集提取特征然后写到像硬盘这样的永久性存储设备的过程。首先建立数据索引就决定了你要用什么描述子去表示。你得考虑一下你要描述图像的哪一方面的特征。颜色分布?纹理和触觉质量?还是图像里对象的形状?然后遍历所有数据,用描述子提取数据集里图像特征向量。这可以串行或并行运行。最后,提取完数据特征之后,得把特征索引写到文件里。简单一点就用Python内置字典方式,更高层次的选择就是用h5py。