(本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )html
视觉词袋模型(Bag Of Visual Words,BOVW)来源于天然语言处理中的词袋模型(Bag Of Words, BOW),关于词袋模型,能够参考个人博文【火炉炼AI】机器学习038-NLP建立词袋模型.在NLP中,BOW的核心思想是将一个文档当作一个袋子,里面装着各类各样的单词,根据单词出现的频次或权重来衡量某个单词的重要性。BOW的一个重要特性是不考虑单词出现的顺序,句子语法等因素。git
视觉词袋模型BOVW是将BOW的核心思想应用于图像处理领域的一种方法,为了表示一幅图像,咱们能够将图像看作文档,即若干个“视觉单词”的集合,和BOW同样,不考虑这些视觉单词出现的顺序,故而BOVW的一个缺点是忽视了像素之间的空间位置信息(固然,针对这个缺点有不少改进版本)。BOVW的核心思想能够从下图中看出一二。github
有人要问了,提取图像的特征方法有不少,好比SIFT特征提取器,Star特征提取器等,为何还要使用BOVW模型来表征图像了?由于SIFT,Star这些特征提取器获得的特征矢量是多维的,好比SIFT矢量是128维,并且一幅图像一般会包含成百上千个SIFT矢量,在进行下游机器学习计算时,这个计算量很是大,效率很低,故而一般的作法是用聚类算法对这些特征矢量进行聚类,而后用聚类中的一个簇表明BOVW中的一个视觉单词,将同一幅图像的SIFT矢量映射到视觉视觉单词序列,生成视觉码本,这样,每一幅图像均可以用一个视觉码本矢量来描述,在后续的计算中,效率大大提升,有助于大规模的图像检索。算法
关于BOVW的更详细描述,能够参考博文:视觉词袋模型BOW学习笔记及matlab编程实现编程
BOVW主要包括三个关键步骤:app
1,提取图像特征:提取算法可使用SIFT,Star,HOG等方法,好比使用SIFT特征提取器,对数据集中的每一幅图像都使用SIFT后,每个SIFT特征用一个128维描述特征向量表示,假若有M幅图像,一共提取出N个SIFT特征向量。dom
2,聚类获得视觉单词:最经常使用的是K-means,固然能够用其余聚类算法,使用聚类对N个SIFT特征向量进行聚类,K-means会将N个特征向量分红K个簇,使得每一个簇内部的特征向量都具备很是高的类似度,而簇间的类似度较低,聚类后会获得K个聚类中心(在BOVW中,聚类中心被称为视觉单词)。计算每一幅图像的每个SIFT特征到这K个视觉单词的距离,并将其映射到距离最近的一个簇中(即该视觉单词的对应词频+1)。这样,每一幅图像都变成了一个与视觉单词相对应的词频矢量。机器学习
3,构建视觉码本:由于每一幅图像的SIFT特征个数不相等,因此须要对这些词频矢量进行归一化,将每幅图像的SIFT特征个数变为频数,这样就获得视觉码本。函数
整个流程能够简单地用下图描述:性能
下面开始准备数据集,首先从Caltech256图像抽取3类,每一类随机抽取20张图片,组成一个小型数据集,每个类别放在一个文件夹中,且文件夹的命名以数字和“-”开头,数字就表示类别名称。这个小数据集纯粹是验证算法是否能跑通。以下为准备的数据集:
首先来看第一步的代码:提取图像特征的代码:
def __img_sift_features(self,image):
''' 提取图片image中的Star特征的关键点,而后用SIFT特征提取器进行计算, 获得N行128列的矩阵,每幅图中提取的Star特征个数不同,故而N不同, 可是通过SIFT计算以后,特征的维度都变成128维。 返回该N行128列的矩阵 '''
keypoints=xfeatures2d.StarDetector_create().detect(image)
gray=cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_,feature_vectors=xfeatures2d.SIFT_create().compute(gray,keypoints)
return feature_vectors
复制代码
而后将获得的全部图片的N行128列特征集合起来,组成M行128列特征,构建一个聚类算法,用这个算法来映射获得含有32个聚类中心(视觉单词)的模型,将这128列特征映射到32个视觉单词中(因为此处Kmeans我使用32个簇,故而获得32个视觉单词,越复杂的项目,这个值要调整的越大,从几百到几千不等。),在统计每个特征出现的频次,组成一个词袋模型,以下代码:
def __map_feature_to_cluster(self,img_path):
'''从单张图片中提取Star特征矩阵(N行128列), 再将该特征矩阵经过K-means聚类算法映射到K个类别中,每一行特征映射到一个簇中,获得N个簇标号的向量, 统计每个簇中出现的特征向量的个数,至关于统计词袋中某个单词出现的频次。 '''
img_feature_vectors=self.__img_sift_features(self.__get_image(img_path)) # N 行128列
cluster_labels=self.cluster_model.predict(img_feature_vectors)
# 计算这些特征在K个簇中的类别,获得N个数字,每一个数字是0-31中的某一个,表明该Star特征属于哪个簇
# eg [30 30 30 6 30 30 23 25 23 23 30 30 16 17 31 30 30 30 4 25]
# 统计每一个簇中特征的个数
vector_nums=np.zeros(self.clusters_num) # 32个元素
for num in cluster_labels:
vector_nums[num]+=1
# 将特征个数归一化处理:获得百分比而非个数
sum_=sum(vector_nums)
return [vector_nums/sum_] if sum_>0 else [vector_nums] # 一行32列,32 个元素组成的list
复制代码
上面仅仅是用一部分图片来获得聚类中心,没有用所有的图像,由于部分图像彻底能够表明所有图像。
第三步:获取多张图片的视觉码本,将这些视觉码本组成一个P行32列的矩阵。
def __calc_imgs_clusters(self,img_path_list):
'''获取多张图片的视觉码本,将这些视觉码本组成一个P行32列的矩阵,P是图片张数,32是聚类的类别数。 返回该P行32列的矩阵'''
img_paths=list(itertools.chain(*img_path_list)) # 将多层list展开
code_books=[]
[code_books.extend(self.__map_feature_to_cluster(img_path)) for img_path in img_paths]
return code_books
复制代码
完整的准备数据集的代码比较长,以下:
# 准备数据集
import cv2,itertools,pickle,os
from cv2 import xfeatures2d
from glob import glob
class DataSet:
def __init__(self,img_folder,cluster_model_path,img_ext='jpg',max_samples=12,clusters_num=32):
self.img_folder=img_folder
self.cluster_model_path=cluster_model_path
self.img_ext=img_ext
self.max_samples=max_samples
self.clusters_num=clusters_num
self.img_paths=self.__get_img_paths()
self.all_img_paths=[list(item.values())[0] for item in self.img_paths]
self.cluster_model=self.__load_cluster_model()
def __get_img_paths(self):
folders=glob(self.img_folder+'/*-*') # 因为图片文件夹的名称是数字+‘-’开头,故而能够用这个来获取
img_paths=[]
for folder in folders:
class_label=folder.split('\\')[-1]
img_paths.append({class_label:glob(folder+'/*.'+self.img_ext)})
# 每个元素都是一个dict,key为文件夹名称,value为该文件夹下全部图片的路径组成的list
return img_paths
def __get_image(self,img_path,new_size=200):
def resize_img(image,new_size):
'''将image的长或宽中的最小值调整到new_size'''
h,w=image.shape[:2]
ratio=new_size/min(h,w)
return cv2.resize(image,(int(w*ratio),int(h*ratio)))
image=cv2.imread(img_path)
return resize_img(image,new_size)
def __img_sift_features(self,image):
''' 提取图片image中的Star特征的关键点,而后用SIFT特征提取器进行计算, 获得N行128列的矩阵,每幅图中提取的Star特征个数不同,故而N不同, 可是通过SIFT计算以后,特征的维度都变成128维。 返回该N行128列的矩阵 '''
keypoints=xfeatures2d.StarDetector_create().detect(image)
gray=cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_,feature_vectors=xfeatures2d.SIFT_create().compute(gray,keypoints)
return feature_vectors
def __calc_imgs_features(self,img_path_list):
'''获取多张图片的特征矢量,这些特征矢量是合并到一块儿的,最终组成M行128列的矩阵,返回该矩阵. 此处的M是每张图片的特征矢量个数之和,即N1+N2+N3....'''
img_paths=list(itertools.chain(*img_path_list)) # 将多层list展开
feature_vectors=[]
[feature_vectors.extend(self.__img_sift_features(self.__get_image(img_path))) for img_path in img_paths]
return feature_vectors
def __create_save_Cluster(self):
'''因为folders中含有大量图片,故而取一小部分(max_samples)图片来作K-means聚类。 '''
# 获取要进行聚类的小部分图片的路径
cluster_img_paths=[list(item.values())[0][:self.max_samples] for item in self.img_paths]
feature_vectors=self.__calc_imgs_features(cluster_img_paths)
cluster_model = KMeans(self.clusters_num, # 创建聚类模型
n_init=10,
max_iter=10, tol=1.0)
cluster_model.fit(feature_vectors) # 对聚类模型进行训练
# 将聚类模型保存,之后就不须要再训练了。
with open(self.cluster_model_path,'wb+') as file:
pickle.dump(cluster_model,file)
print('cluster model is saved to {}.'.format(self.cluster_model_path))
return cluster_model
def __map_feature_to_cluster(self,img_path):
'''从单张图片中提取Star特征矩阵(N行128列), 再将该特征矩阵经过K-means聚类算法映射到K个类别中,每一行特征映射到一个簇中,获得N个簇标号的向量, 统计每个簇中出现的特征向量的个数,至关于统计词袋中某个单词出现的频次。 '''
img_feature_vectors=self.__img_sift_features(self.__get_image(img_path)) # N 行128列
cluster_labels=self.cluster_model.predict(img_feature_vectors)
# 计算这些特征在K个簇中的类别,获得N个数字,每一个数字是0-31中的某一个,表明该Star特征属于哪个簇
# eg [30 30 30 6 30 30 23 25 23 23 30 30 16 17 31 30 30 30 4 25]
# 统计每一个簇中特征的个数
vector_nums=np.zeros(self.clusters_num) # 32个元素
for num in cluster_labels:
vector_nums[num]+=1
# 将特征个数归一化处理:获得百分比而非个数
sum_=sum(vector_nums)
return [vector_nums/sum_] if sum_>0 else [vector_nums] # 一行32列,32 个元素组成的list
def __calc_imgs_clusters(self,img_path_list):
'''获取多张图片的视觉码本,将这些视觉码本组成一个P行32列的矩阵,P是图片张数,32是聚类的类别数。 返回该P行32列的矩阵'''
img_paths=list(itertools.chain(*img_path_list)) # 将多层list展开
code_books=[]
[code_books.extend(self.__map_feature_to_cluster(img_path)) for img_path in img_paths]
return code_books
def __load_cluster_model(self):
'''从cluster_model_path中加载聚类模型,返回该模型,若是不存在或出错,则调用函数准备聚类模型'''
cluster_model=None
if os.path.exists(self.cluster_model_path):
try:
with open(self.cluster_model_path, 'rb') as f:
cluster_model = pickle.load(f)
except:
pass
if cluster_model is None:
print('No valid model found, start to prepare model...')
cluster_model=self.__create_save_Cluster()
return cluster_model
def get_img_code_book(self,img_path):
'''获取单张图片的视觉码本,即一行32列的list,每一个元素都是对应特征出现的频率'''
return self.__map_feature_to_cluster(img_path)
def get_imgs_code_books(self,img_path_list):
'''获取多张图片的视觉码本,即P行32列的list,每一个元素都是对应特征出现的频率'''
return self.__calc_imgs_clusters(img_path_list)
def get_all_img_code_books(self):
'''获取img_folder中全部图片的视觉码本'''
return self.__calc_imgs_clusters(self.all_img_paths)
def get_img_labels(self):
'''获取img_folder中全部图片对应的label,能够从文件夹名称中获取'''
img_paths=list(itertools.chain(*self.all_img_paths))
return [img_path.rpartition('-')[0].rpartition('\\')[2] for img_path in img_paths]
def prepare_dataset(self):
'''获取img_folder中全部图片的视觉码本和label,构成数据集'''
features=self.get_all_img_code_books()
labels=self.get_img_labels()
return np.c_[features,labels]
复制代码
极端随机森林是随机森林算法的一个提高版本,能够参考我之前的文章【火炉炼AI】机器学习007-用随机森林构建共享单车需求预测模型.使用方法和随机森林几乎同样。
# 极端随机森林分类器
from sklearn.ensemble import ExtraTreesClassifier
class CLF_Model:
def __init__(self,n_estimators=100,max_depth=16):
self.model=ExtraTreesClassifier(n_estimators=n_estimators,
max_depth=max_depth, random_state=12)
def fit(self,train_X,train_y):
self.model.fit(train_X,train_y)
def predict(self,newSample_X):
return self.model.predict(newSample_X)
复制代码
其实,这个分类器很简单,不必写成类的形式。
对该分类器进行训练:
dataset_df=pd.read_csv('./prepared_set.txt',index_col=[0])
dataset_X,dataset_y=dataset_df.iloc[:,:-1].values,dataset_df.iloc[:,-1].values
model=CLF_Model()
model.fit(dataset_X,dataset_y)
复制代码
以下,我随机测试三张图片,均获得了比较好的结果。
# 用训练好的model预测新图片,看看它属于哪一类
new_img1='E:\PyProjects\DataSet\FireAI/test0.jpg'
img_code_book=dataset.get_img_code_book(new_img1)
predicted=model.predict(img_code_book)
print(predicted)
new_img2='E:\PyProjects\DataSet\FireAI/test1.jpg'
img_code_book=dataset.get_img_code_book(new_img2)
predicted=model.predict(img_code_book)
print(predicted)
new_img3='E:\PyProjects\DataSet\FireAI/test2.jpg'
img_code_book=dataset.get_img_code_book(new_img3)
predicted=model.predict(img_code_book)
print(predicted)
复制代码
-------------------------------------输---------出--------------------------------
[0] [1] [2]
--------------------------------------------完-------------------------------------
########################小**********结###############################
1,这个项目的难点在于视觉词袋模型的理解和数据集准备,因此我将其写成了类的形式,这个类具备必定的通用性,能够用于其余项目数据集的制备。
2,从这个项目能够看出视觉词袋模型相对于原始的Star特征的优点:若是使用原来的Star特征,一张图片会获得N行128列的特征数,而使用了BOVW模型,咱们将N行128列的特征数据映射到1行32列的空间中,因此极大的下降了特征数,使得模型简化,训练和预测效率提升。
3,一旦准备好了数据集,就能够用各类常规的机器学习分类器进行分类,也能够用各类方法评估该分类器的优劣,好比性能报告,准确率,召回率等,因为这部分我在前面的文章中已经讲过屡次,故而此处省略。
#################################################################
注:本部分代码已经所有上传到(个人github)上,欢迎下载。
参考资料:
1, Python机器学习经典实例,Prateek Joshi著,陶俊杰,陈小莉译