在最初的文本分类问题中Bag of word模型, 用特征矢量表示文档。基本思想:忽略文本的句词、语序等细节而将其作为一个词汇集合,文本中的词都是互相独立。
举个例子:将对于一个物体的部分描述放到一个文本中,如果文本中红色,圆的,表面光滑,甜等词汇出现的多,而黄色,表面粗糙,酸等词出现的少,就可判断这个文本是苹果而不是菠萝。
BOW应用在视觉领域的图像分类中,基本思想将图像特征看为一个特征集合,然后通过统计图像中特征出现的次数来对图像进行分类。本文利用BOW对图像进行分类。以下代码参考猫狗分类问题参考源码
代码中的K值。
现有以下5类数据集: CDHP:承兑汇票 DZD:对账单 Tax:税单 YHHD:银行回单: ZZS:增值税发票
文件夹中为对应的各类票据图片,现在需要对新输入的图片进行粗分类出对应的类别。
首先我们来预览下数据集都是什么样子:
(为避免有不必要的麻烦,原图信息已进行打码处理)
再看下原图的详细信息:
开始楼主试着直接将原数据集(一张图200多kb)丢进训练模型中去跑,可能是文件太大,只尝试着跑了100张图都花了不少时间,所以继续想解决方案:
将原图进行压缩,当然得看下原图的长宽比,否则图片会变形严重,原图:2480X1164 宽高比2.14:1:压缩到500X500中为:500X233.
pic_resize.py
# -*- code:utf-8 -*- '''图片处理,修改大小和名字''' import os from PIL import Image pwd = os.getcwd() file_list = os.listdir(pwd) image_list = [] for file in file_list: if os.path.isfile(file): name, expand = os.path.splitext(file) if expand == '.jpg' or expand == '.png' or expand == 'jpeg': image_list.append(file) if image_list: width = int(input("请输入图片尺寸的宽度,默认200:")) if width == 0: width == 200 else: pass height = int(input("请输入图片尺寸的高度,默认200:")) if height == 0: height == 200 else: pass imagePrefix = str(input("请输入需要生产图片的前缀名:")) if imagePrefix is None: imagePrefix = "1" else: pass else: input("目录下没有图片,请按任意键退出!") exit() # 处理图片 outDir = os.path.join(pwd, 'outImage') if os.path.exists(outDir): os.remove(outDir) os.mkdir(outDir) for imageFile in image_list: ima = Image.open(os.path.join(pwd, imageFile)) im_out = ima.resize((width, height), Image.NEAREST) outPath = os.path.join(outDir, imagePrefix + imageFile) im_out.save(outPath)
试着将压缩后的图放入BOW模型中训练,但是实际预测结果并不理想。
重回到原图,大致分析了下YHHD数据集的图片特征,特征分布比较有规律,所以思路为对源数据集进行定位裁剪,裁剪出图中的
裁剪代码如下:
pic_cut.py(放在需要裁减图片的同级目录下)
import numpy as np import cv2 import os def update(input_img_path, output_img_path): image = cv2.imread(input_img_path) print(image.shape) cropped = image[70:200, 1600:1750] # 裁剪坐标为[y0:y1, x0:x1] cv2.imwrite(output_img_path, cropped) dataset_dir = r'D:\Python_Learning\opencv_target_track\train_path\YHHD\JSYH'#建设银行 output_dir = r'D:\Python_Learning\opencv_target_track\train_path\YHHD\out' # 获得需要转化的图片路径并生成目标路径 image_filenames = [(os.path.join(dataset_dir, x), os.path.join(output_dir, x)) for x in os.listdir(dataset_dir)] # 转化所有图片 for path in image_filenames: update(path[0], path[1])
裁剪后的,思路:通过裁剪后的"回单"图片集进行训练,判别图片中是否存在"回单"目标。
为了方便训练的迭代所以统一命名,写了以下重命名脚本:
rename.py
#coding=gbk import os import sys def rename(): path=input("请输入路径(例如D:\\\\picture):") name=input("请输入开头名:") startNumber=input("请输入开始数:") fileType=input("请输入后缀名(如 .jpg、.txt等等):") print("正在生成以"+name+startNumber+fileType+"迭代的文件名") count=0 filelist=os.listdir(path) for files in filelist: Olddir=os.path.join(path,files) if os.path.isdir(Olddir): continue Newdir=os.path.join(path,name+str(count+int(startNumber))+fileType) os.rename(Olddir,Newdir) count+=1 print("一共修改了"+str(count)+"个文件") rename()
classify.py
''' 词袋模型BOW+SVM 目标识别 ''' import numpy as np import cv2 import os 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): ''' 用于获取图片的全路径 ''' # train_path/zzsfp/out/zzsfp.i.jpg return '%s\%s\out\%s.%d.jpg' % (self.train_path, cls, cls, i + 1) def fit(self, train_path, k): ''' 开始训练 args: train_path:训练集图片路径 我们使用的数据格式为 train_path/zzsfp/zzsfp.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 = 'YHHD' neg = 'ZZS' # 指定用于提取词汇字典的样本数 length = 50 # 合并特征数据 每个类从数据集中读取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(100): # 这里取50张图像做训练 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]) # 如果是YHHD 返回True if res[1][0][0] == 1.0: return True # 如果是ZZSFP,返回False else: return False def sift_descriptor_extractor(self, img_path): ''' 特征提取:提取数据集中每幅图像的特征点,然后提取特征描述符,形成特征数据(如:SIFT或者SURF方法); ''' im = cv2.imread(img_path, 0) # cv2.imshow('imshow', im) # cv2.waitKey(0) # cv2.destroyAllWindows() 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) # cv2.imshow('imshow', im) # cv2.waitKey(0) # cv2.destroyAllWindows() return self.bow_img_descriptor_extractor.compute(im, self.feature_detector.detect(im)) # def set_gpus(gpu_index): # if type(gpu_index) == list: # gpu_index = ','.join(str(_) for _ in gpu_index) # if type(gpu_index) == int: # gpu_index = str(gpu_index) # os.environ["CUDA_VISIBLE_DEVICES"] = gpu_index if __name__ == '__main__': # 测试样本数量,测试结果 test_samples = 20 test_results = np.zeros(test_samples, dtype=np.bool) # 训练集图片路径 进行训练 train_path = r'D:\Python_Learning\opencv_target_track\train_path' bow = BOW() bow.fit(train_path, 40) # 指定测试图像路径 for index in range(test_samples): zzsfp = "data_test/{0}.jpg".format(index+1) test_img = cv2.imread(zzsfp) # 预测 zzsfp_predict = bow.predict(zzsfp) test_results[index] = zzsfp_predict print(test_results[index]) # font = cv2.FONT_HERSHEY_SIMPLEX if test_results[index]: cv2.putText(test_img, 'YHHD', (10, 30), font, 1, (0, 255, 0), 2, cv2.LINE_AA) else: cv2.putText(test_img, 'ZZS', (10, 30), font, 1, (0, 255, 0), 2, cv2.LINE_AA) cv2.imshow('test_img', test_img) cv2.waitKey(0) cv2.destroyAllWindows() # 计算准确率 accuracy = np.mean(test_results.astype(dtype=np.float32)) print('测试准确率为:', accuracy)
楼主进行了测试,对于以和两个数据集进行训练的模型,来预测
这些测试的图片效果并不理想
if test_results[index]: cv2.putText(test_img, 'YHHD', (10, 30), font, 1, (0, 255, 0), 2, cv2.LINE_AA) else: cv2.putText(test_img, 'ZZS', (10, 30), font, 1, (0, 255, 0), 2, cv2.LINE_AA)
所有图片都会被认为是ZZS,分析了代码并不是识别成ZZS,而是因为不是YHHD所以上述代码意思为不是YHHD的所有都是ZZS,YHHD并未识别。还得继续找别的方案。