In this post, we are going to learn about object recognition and how we can use it to build a visual search engine.
目标检测与目标识别
你一定经常听到目标检测和目标识别这两个术语,它们经常被误认为是同一件事,其实两者之间有非常明显的区别。目标检测是指检测给定场景中特定对象的存在。我们不知道目标可能是什么。目标识别是识别给定图像中的对象的过程。例如,目标识别系统可以告诉你给定的图像是包含一件衣服还是一双鞋子。事实上,我们可以训练一个目标识别系统来识别许多不同的物体。问题是目标识别是一个很难解决的问题。几十年来,计算机视觉研究人员一直回避它,它已经成为计算机视觉的圣杯。
完美的目标识别器(object recognizer)会给你所有与该对象相关的信息。如果目标识别器知道对象的位置,它就会更加准确。因此,第一步是检测目标并获得边界框。一旦有了这个,我们就可以运行目标识别器来提取更多信息。
什么是 Dense 特征检测器?
为了从图像中提取有意义的大量信息,我们需要确保我们的特征提取器从给定图像的所有部分提取特征。考虑以下图像:
如果使用特征提取器提取特征,它将如下所示:
cv2.FeaturetureDetector_create("Dense")
检测器已经在 OpenCV 3.2 中删除了,因此我们需要自己实现一个:
我们也可以控制密度。让它变得稀疏:
这样做我们可以确保图像中的每个部分都得到处理。下面是执行此操作的代码:
1 | import sys |
这给了我们对提取的信息的密切控制。当我们使用 SIFT 检测器时,图像的某些部分会被忽略。当我们构建一个目标识别器,我们需要评估图像的所有部分。因此,我们使用 dense 检测器,然后从这些关键点提取特征。
什么是视觉词典?
我们将使用词袋(Bag of Words)模型来构建我们的目标识别器。每个图像被表示为视觉单词的直方图。这些视觉单词基本上是使用从训练图像中提取的所有关键点构建的 N 个质心。其管道如下图所示:
从每个训练图像中,我们检测一组关键点,并提取每个关键点的特征。每幅图像都会产生不同数量的关键点。为了训练分类器,每个图像必须使用固定长度的特征向量来表示。该特征向量仅仅是一个直方图,其中每个 bin 对应于一个视觉单词。
当我们从训练图像的所有关键点提取所有特征时,我们执行 K-means 聚类并提取 N 个质心。N 是给定图像的特征向量的长度。每个图像将表示为直方图,其中每个 bin 对应于 N 个质心中的一个。为了简单起见,假设 N 设为 4。现在,在给定的图像中,我们提取 K 个关键点。在这 K 个关键点中,一些最接近第一质心,一些最接近第二质心,依此类推。因此,我们根据离每个关键点最近的质心建立直方图。这个直方图成为我们的特征向量。这个过程称为矢量量化(vector quantization)。
为了理解矢量量化,让我们考虑一个例子。假设我们有一幅图像,并且我们已经从中提取了一定数量的特征点。现在我们的目标是以特征向量的形式来表示这个图像。考虑以下图像:
正如你所看到的,我们有四个质心。请记住,图中所示的点代表特征空间,而不是图像中这些特征点的实际几何位置,图像中许多不同几何位置的点可以在特征空间中彼此靠近。我们的目标是将这张图像表示为直方图,其中每个 bin 对应于一个质心。这样,无论我们从图像中提取多少个特征点,它总是会被转换成固定长度的特征向量。因此,我们将每个特征点舍入到其最近的质心,如下图所示:
如果为此图像构建直方图,它将如下所示:
现在,如果考虑具有不同特征点分布的不同图像,如下所示:
聚类将如下所示:
直方图如下所示:
正如你所看到的,两个图像的直方图差别很大。有许多不同的方法可以做到这一点,其准确性取决于你希望的细粒度。如果你增加质心的数量,你将能够更好地代表图像,从而增加特征向量的唯一性。
什么是监督学习和无监督学习?
监督学习(supervised learning)是指基于标记样本构建函数。无监督学习(unsupervised learning)刚好相反,没有标记数据。假设我们有一堆图像,我们只想把它们分成三组。我们不知道标准是什么。因此,一个无监督的学习算法试图以最好的方式将给定的数据集分成三组。我们讨论这些的原因是,我们将使用监督学习和无监督学习相结合的方法来构建我们的目标识别系统。
什么是支持向量机?
支持向量机(support vector machines,SVM)是机器学习领域中非常流行的监督学习模型。SVMs 非常擅长分析标记数据和检测模式。给定一堆数据点和相关标签,SVMs将以最佳方式构建分离超平面。
等等,什么是超平面?为了理解这一点,让我们考虑下图:
正如你所看到的,这些点被与这些点等距的直线边界分隔开。很容易在二维上可视化。如果是三维,分隔将是平面。当我们为图像构建特征时,特征向量的长度通常在 6 内。所以,当我们去这样一个高维空间时,线的等效物就是超平面。一旦超平面形成,我们使用这个数学模型根据未知数据在映射上的位置对其进行分类。
如果不能用简单的直线将数据分开会怎么样?
我们在 SVMs 中使用了一种叫做核技巧(kernel trick)的东西。考虑以下图像:
正如我们所看到的,我们不能画一条简单的直线来区分红色点和蓝色点。给出一个完美的曲线边界来满足所有的点是非常计算昂贵的。SVMs 非常擅长画直线,它们可以在任意维度上绘制这些直线。因此,从技术上讲,如果你将这些点投影到一个高维空间中,在那里它们可以被一个简单的超平面分开,SVMs 将会给出一个精确的边界。一旦我们有了边界,我们就可以将它投影回原始空间。这个超平面在我们原始的低维空间上的投影看起来是弯曲的,如下图所示:
SVMs的主题非常深入,我们将无法在这里详细讨论。如果你真的感兴趣,网上有大量的资料。
我们到底是如何实现的?
现在,让我们构建一个目标识别器,该识别器可以识别给定图像是包含裙子、鞋子还是包。可以很容易地扩展这个系统来检测任意数量的物品。
在开始之前,需要确保我们有一组训练图像。在线上有许多数据库,其中的图像整理好了。Caltech256 是最受欢迎的对象识别数据库之一。创建一个名为 images 的文件夹,并在其中创建三个子文件夹,即 dress、footwear 以及 bag。在每个子文件夹中,添加 20 幅与该项相对应的图片。你可以从互联网上下载这些图片,但是要确保这些图片有一个干净的背景。
现在我们有 60 张培训图片,可以准备开始了。另外,目标识别系统实际上需要成千上万的训练图像才能在现实世界中表现良好。因为我们正在构建一个目标识别器来检测三种类型的对象,所以我们将只对每个对象取 20 幅训练图像。添加更多的训练图像将提高我们系统的准确性和鲁棒性。
这里的第一步是从所有训练图像中提取特征向量并构建视觉词典。首先,重用我们以前的 DenseDetector
类,加上 SIFT 特征检测器(见前面代码)。
然后用 Quantizer
类计算矢量量化并建立特征矢量:
1 | # Vector quantization |
另一个必需的类是 FeaturementExtractor
类,它用于提取每个图像的质心:
1 | class FeatureExtractor(object): |
下面的脚本将为我们提供一个特征字典来分类图像:
1 | import os |
背后细节?
我们需要做的第一件事是提取质心。这就是我们将如何构建我们的视觉词典。FeaturementExtractor
类中的 get_centroids
方法就是为了这样做而设计的。我们不断收集图像特征,直到我们有足够的特征。因为我们使用的是 dense 检测器,所以 10 幅图像就足够了。我们取 10 张照片的原因是因为已经产生了大量的特征,即使添加了更多的特征,质心也不会有太大变化。
一旦提取了质心,我们就可以进入下一步的特征提取。质心集是我们的视觉词典。函数 extract_feature_map
将从每幅图像中提取一个特征向量,并将其与相应的标签相关联。我们这样做的原因是因为我们需要这个映射来训练我们的分类器。我们需要一组关键点,每个关键点都应该与一个标签相关联。因此,我们从图像开始,提取特征向量,然后将其与相应的标签(如 bag、dress 或 footwear)相关联。
Quantizer
类旨在实现矢量量化并构建特征矢量。对于从图像中提取的每个关键点,get_feature_vector
方法会在字典中找到最近的视觉单词。这样我们最终基于视觉词典建立了一个直方图。现在,每个图像都被表示为一组视觉单词的组合。因此有了这个名字,Bag of Words。
下一步是使用这些特征训练分类器。为此,我们实现了ClassifierTrainer
类。现在,基于我们之前的特征字典,我们生成 SVM 文件:
1 | import os |
我们是如何构建训练的?
我们使用 scikit-learn 软件包建立 SVM 模型和 scipy 为数学优化工具。
我们从标记的数据开始,并将其提供给 OneVsOneClassifier
方法。我们有 classify
方法,对输入图像进行分类,并将标签与其相关联。
让我们试一试,创建一个名为 models 的文件夹,学习模型将存储在其中。在终端上运行以下命令来创建特征并训练分类器:
1 | $ python create_features.py --samples bag images/bag/ --samples dress images/dress/ --samples footwear images/footwear/ --codebook-file models/codebook.pkl --feature-map-file models/feature_map.pkl |
现在分类器已经过训练,我们需要一个模块来分类输入图像并检测里面的物体:
1 | import os |
我们从输入图像中提取 feature
向量,并将其用作分类器的输入参数。让我们去看看这是否行得通。从网上下载一张随机的鞋子图片,确保它有一个干净的背景。运行以下命令:
1 | $ python classify_data.py --input-image new_image.jpg --svm-file models/svm.pkl --codebook-file models/codebook.pkl |
我们可以使用同样的技术来构建视觉搜索引擎。视觉搜索引擎查看输入图像,并显示一堆与输入图像相似的图像。我们可以重用对象识别框架来构建它。从输入图像中提取特征向量,并将其与训练数据集中的所有特征向量进行比较。挑选最匹配的并显示结果。