在上一节、我们已经介绍了使用HOG和SVM实现目标检测和识别,这一节我们将介绍使用词袋模型BOW和SVM实现目标检测和识别。
一 词袋介绍
词袋模型(Bag-Of-Word)的概念最初不是针对计算机视觉的,但计算机视觉会使用该概念的升级。词袋最早出现在神经语言程序学(NLP)和信息检索(IR)领域,该模型忽略掉文本的语法和语序,用一组无序的单词来表达一段文字或者一个文档。
我们使用BOW在一系列文档中构建一个字典,然后使用字典中每个单词次数构成向量来表示每一个文档。比如:
- 文档1:I like OpenCV and I like Python;
- 文档2:I like C++ and Python;
- 文档3:I don't like artichokes;
对于这三个文档,我们建立如下的字典:
dic = {1:'I', 2:'like', 3:'OpenCV', 4:'and', 5:'Python', 6:'C++', 7:'don\'t', 8:'artichokes'}
该字典一共有8项。使用这8项构成的向量来表示每个文档,每个向量包含字典中的所有单词,向量的每个元素表示文档中每个单词出现的次数。则上面三个文档可以使用如下向量来表示:
[2,2,1,1,1,0,0,0]
[1,1,0,1,1,1,0,0]
[1,1,0,0,0,0,1,1]
每一个向量都可以看做是一个文档的直方图表示或被当做特征,这些特征可以用来训练分类器。在实际中,也有许多有效地应用,比如垃圾邮箱过滤。
二 计算机视觉中的BOW
与应用到文本的BOW模型类比,我们可以把BOW模型应用到计算机视觉,我们把图像的特征当做单词,把图像“文字化”之后,有助于大规模的图像检索。
1、BOW基本步骤
- 特征提取:提取数据集中每幅图像的特征点,然后提取特征描述符,形成特征数据(如:SIFT或者SURF方法);
- 学习词袋:把处理好的特征数据全部合并,利用聚类把特征词分为若干类,此若干类的数目由自己设定,每一类相当于一个视觉词汇;
- 利用视觉词袋量化图像特征:每一张图像由很多视觉词汇组成,我们利用统计的词频直方图,可以表示图像属于哪一类;
这个过程需要获取视觉词汇(visual word)字典,从一定程度上来说,词汇越多越好,因此我们需要的数据集也相应的越大越好;
2、BOW可视化
下面我们来对BOW过程进行可视化,
- 假设我们有三个目标类,分别是人脸、自行车和吉他。首先从图像中提取出相互独立的视觉词汇(假设使用SIFT方法):
通过观察会发现,同一类目标的不同实例之间虽然存在差异,但我们仍然可以找到它们之间的一些共同的地方,比如说人脸,虽然说不同人的脸差别比较大,但眼睛,嘴,鼻子等一些比较细小的部位,却观察不到太大差别,我们可以把这些不同实例之间共同的部位提取出来,作为识别这一类目标的视觉词汇。
- 将所有的视觉词汇集合在一起:
- 利用K-means算法构造词汇字典。K-means算法是一种基于样本间相似性度量的间接聚类方法,此算法以$k$为参数,把$n$个对象分为$k$个簇,以使簇内具有较高的相似度,而簇间相似度较低。SIFT算法提取的视觉词汇向量之间根据距离的远近,可以利用K-Means算法将词义相近的词汇合并,作为词汇字典中的基础词汇,假定我们将$k$设为4,那么词汇字典的构建过程如下:
- 利用词汇字典的中词汇表示图像。利用SIFT算法,可以从每幅图像中提取很多个特征点,这些特征点都可以用词汇字典中的词汇近似代替,通过统计词汇字典中每个词汇在图像中出现的次数,可以将图像表示成为一个$k=4$维数值向量:
上图中,我们从人脸、自行车和吉他三个目标类图像中提取出的不同视觉词汇,而构造的词汇字典中,会把词义相近的视觉词汇合并为同一类,经过合并,词汇表中只包含了四个视觉词汇,分别按索引值标记为1,2,3,4。通过观察可以看到,它们分别属于自行车、人脸、吉他、人脸类。统计这些词汇在不同目标类中出现的次数可以得到每幅图像的直方图表示(我们假定存在误差,实际情况亦不外如此):
人脸: [3,30,3,20] 自行车:[20,3,3,2] 吉他: [8,12,32,7]
其实这个过程非常简单,就是针对人脸、自行车和吉他这三个文档,抽取出相似的部分(或者词义相近的视觉词汇合并为同一类),构造一个字典,字典中包含4个视觉单词,即:
dic = {1:'自行车', 2:'人脸', 3:'吉他', 4:'人脸类'}
最终人脸、自行车和吉他这三个文档皆可以用一个4维向量表示,最后根据三个文档相应部分出现的次数绘制对应的直方图。
需要说明的是,以上过程只是针对三个目标类非常简单的一个示例,实际应用中,为了达到较好的效果,单词表中的词汇数量$k$往往非常庞大,并且目标类数目越多,对应的$k$值也越大,一般情况下,$k$的取值在几百到上千,在这里取$k=4$仅仅是为了方便说明。
三 目标识别
对于图像和视频检测中的目标类型没有具体限制,但是为了使结果的准确度在可以接收的范围内,需要一个足够大的数据集,包括训练图像的大小也需要一样。
如果自己构建数据集将会花费较长的时间,因此,在这里我们利用现成的数据集,在网上可以下载这样的数据集,这里我们使用猫和狗的数据集:
https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data
训练集一共包含25000张照片,其中一半是狗(正样本),一半是猫(负样本),在这里我们就使用其中的部分数据集,训练一个二分类器;
- 首先我们选取一定数量的正负样本(这里选择的为10,没有选择全部样本是因为数据量大,计算速度就会很慢,而且该数值小一些有时候效果会更好),然后使用SIFT算法提取特征数据,并使用聚类分类(k=40),形成词汇字典;
- 选取更多正负样本数据集(这里选择的是400),利用视觉词袋(即词汇字典)量化每一个样本特征,并使用SVM进行训练;
- 对100个样本进行测试;
代码如下:
# -*- coding: utf-8 -*- """ Created on Wed Oct 17 09:38:26 2018 @author: zy """ ''' 词袋模型BOW+SVM 目标识别 以狗和猫数据集二分类为例 如果是狗 返回True 如果是猫 返回False ''' import numpy as np import cv2 class BOW(object): def __init__(self,): #创建一个SIFT对象 用于关键点提取 self.feature_detector = cv2.xfeatures2d.SIFT_create() #创建一个SIFT对象 用于关键点描述符提取 self.descriptor_extractor = cv2.xfeatures2d.SIFT_create() def path(self,cls,i): ''' 用于获取图片的全路径 ''' return '%s/%s/%s.%d.jpg'%(self.train_path,cls,cls,i+1) def fit(self,train_path,k): ''' 开始训练 args: train_path:训练集图片路径 我们使用的数据格式为 train_path/dog/dog.i.jpg train_path/cat/cat.i.jpg k:k-means参数k ''' self.train_path = train_path #FLANN匹配 参数algorithm用来指定匹配所使用的算法,可以选择的有LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex,这里选择的是KTreeIndex(使用kd树实现最近邻搜索) flann_params = dict(algorithm=1,tree=5) flann = cv2.FlannBasedMatcher(flann_params,{}) #创建BOW训练器,指定k-means参数k 把处理好的特征数据全部合并,利用聚类把特征词分为若干类,此若干类的数目由自己设定,每一类相当于一个视觉词汇 bow_kmeans_trainer = cv2.BOWKMeansTrainer(k) pos = 'dog' neg = 'cat' #指定用于提取词汇字典的样本数 length = 10 #合并特征数据 每个类从数据集中读取length张图片(length个狗,length个猫),通过聚类创建视觉词汇 for i in range(length): bow_kmeans_trainer.add(self.sift_descriptor_extractor(self.path(pos,i))) bow_kmeans_trainer.add(self.sift_descriptor_extractor(self.path(neg,i))) #进行k-means聚类,返回词汇字典 也就是聚类中心 voc = bow_kmeans_trainer.cluster() #输出词汇字典 <class 'numpy.ndarray'> (40, 128) print(type(voc),voc.shape) #初始化bow提取器(设置词汇字典),用于提取每一张图像的BOW特征描述 self.bow_img_descriptor_extractor = cv2.BOWImgDescriptorExtractor(self.descriptor_extractor,flann) self.bow_img_descriptor_extractor.setVocabulary(voc) #创建两个数组,分别对应训练数据和标签,并用BOWImgDescriptorExtractor产生的描述符填充 #按照下面的方法生成相应的正负样本图片的标签 1:正匹配 -1:负匹配 traindata,trainlabels = [],[] for i in range(400): #这里取200张图像做训练 traindata.extend(self.bow_descriptor_extractor(self.path(pos,i))) trainlabels.append(1) traindata.extend(self.bow_descriptor_extractor(self.path(neg,i))) trainlabels.append(-1) #创建一个SVM对象 self.svm = cv2.ml.SVM_create() #使用训练数据和标签进行训练 self.svm.train(np.array(traindata),cv2.ml.ROW_SAMPLE,np.array(trainlabels)) def predict(self,img_path): ''' 进行预测样本 ''' #提取图片的BOW特征描述 data = self.bow_descriptor_extractor(img_path) res = self.svm.predict(data) print(img_path,'\t',res[1][0][0]) #如果是狗 返回True if res[1][0][0] == 1.0: return True #如果是猫,返回False else: return False def sift_descriptor_extractor(self,img_path): ''' 特征提取:提取数据集中每幅图像的特征点,然后提取特征描述符,形成特征数据(如:SIFT或者SURF方法); ''' im = cv2.imread(img_path,0) return self.descriptor_extractor.compute(im,self.feature_detector.detect(im))[1] def bow_descriptor_extractor(self,img_path): ''' 提取图像的BOW特征描述(即利用视觉词袋量化图像特征) ''' im = cv2.imread(img_path,0) return self.bow_img_descriptor_extractor.compute(im,self.feature_detector.detect(im)) if __name__ == '__main__': #测试样本数量,测试结果 test_samples = 100 test_results = np.zeros(test_samples,dtype=np.bool) #训练集图片路径 狗和猫两类 进行训练 train_path = './data/cat_and_dog/data/train' bow = BOW() bow.fit(train_path,40) #指定测试图像路径 for index in range(test_samples): dog = './data/cat_and_dog/data/train/dog/dog.{0}.jpg'.format(index) dog_img = cv2.imread(dog) #预测 dog_predict = bow.predict(dog) test_results[index] = dog_predict #计算准确率 accuracy = np.mean(test_results.astype(dtype=np.float32)) print('测试准确率为:',accuracy) #可视化最后一个 font = cv2.FONT_HERSHEY_SIMPLEX if test_results[0]: cv2.putText(dog_img,'Dog Detected',(10,30),font,1,(0,255,0),2,cv2.LINE_AA) cv2.imshow('dog_img',dog_img) cv2.waitKey(0) cv2.destroyAllWindows()
运行结果如下: