在上一篇中,已经探讨了如何找出图片中轮廓,并将图片根据轮廓进行裁剪,做二值化处理,归一化大小。通过这样的一系列流程,我们已经能够得到了大小统一的二值图图片,在此基础上,可以构建字符识别的训练集。
在本文中,将探讨如何基于支持向量机构建识别模型。本文的主要流程是:将统一大小的图片(尺寸为32*50)进行分区,如将之分为80个区域,每个区域的尺寸为4*5,计算每个区域内的平均灰度值,最后每个训练图片都得到一个长度为80的一维数组,以此作为训练集。之所以这样做主要是考虑降低训练图片的自由度,若直接将图片转化为32*50=1600的一维数组,这样每张图片所包含的自由度太高,由于训练集数量有限,太高的自由度会降低模型识别的准确度。以下是基于该思路定义的方法。
from eight_step import modeling
import os
import numpy as np
from sklearn.svm import SVC
from sklearn.externals import joblib
from sklearn import preprocessing
import cv2
def convImage(image,width,height):
'''将图片分区,计算每个区域的hsv值'''
'''image:被截取的图片;width和height分别为截取后每个区域的宽和高'''
images = [] #用于保存每一个区域的平均灰度
mount = width * height #计算每个区域有多少像素
'''分别获得图片的宽和高'''
w = len(image[0])
h = len(image)
'''定义宽高方向上截取的次数'''
wm = int(w / width)
hm = int(h / height)
print('wm = ',wm,',hm = ',hm)
'''截取的顺序是一行一行截取,每一行都是从左到右截取,截取完该行就截取该行的下一行'''
for i in range(0,hm):
for j in range(0,wm):
crop = image[i*height:(i+1)*height,j*width:(j+1)*width] #截图
'''说明:crop = image[y1:y2,x1:x2],
其中(x1,y1),(x2,y2)分别为截图矩形的左上点,右下点的坐标,'''
array_crop = np.ravel(crop) #转为一维数组
cropMean = sum(array_crop) / mount #求平均灰度
images.append(cropMean)
return images
构建训练集的流程就是读取指定路径下的图片,通过以上方法得到每一张图片每个区域的平均灰度值组成的数组(作为x_train),再通过图片的文件获取标签(作为y_train),然后构建模型。代码如下。
def getLabel(filename):
'''根据图片名称得到图片对应的标签,如标签名A_resizeB.jpg,记标签为A'''
labelStr = filename.split('_')[0]
return labelStr
def getDataAndLabels(dir,width,height):
'''获取训练模型所需的数据和标签,dir为训练图片的路径,
width和height分别为截取后每个区域的宽和高'''
data = [] #用于保存每张图片各个区域平均灰度的标签
labels = [] #用于保存每张图片所对应的标签
for filename in os.listdir(dir): #遍历指定路径下的所有图片
bins = modeling.bins_image(dir, filename, 180) #转化为二值图
image = convImage(bins,width,height) #将图片分割成若干区域,计算每个区域的平均灰度值
data.append(image)
label = getLabel(filename) #得到标签
labels.append(label)
return data,labels
def createModel(data,labels):
'''训练支持向量机模型,data为一个二维数组,每一个元素是一个数组,表示每一张图片的信息,
label是一个一维数组,每一个元素是一张图片的标签'''
clf = SVC(decision_function_shape='ovo') # ovo为一对一
clf.fit(data, labels) #训练模型
return clf
综合以上步骤:
def train(dir,width,height):
data,labels = getDataAndLabels(dir,width,height)
data_scale = preprocessing.scale(data) #对训练数据进行归一化
clf = createModel(data_scale,labels)
joblib.dump(clf,MODEL) #将训练好的模型持久化,clf为模型对象,MODEL为模型持久化的路径和名称
return clf
以上就是训练模型的过程。构建了模型之后,就可以利用模型进行预测了,代码如下:
def simpleRead(dir,filename,width,height,convWidth,convHeight):
'''读取图片,转化为指定大小的图片,最终转为为二值图,划分区域'''
'''dir为图片的路径,filename为图片名称,width和height分别为将图片归一化的宽和高,
convWidth和convHeight分别是切割区域的宽和高'''
bins = modeling.bins_image(dir,filename,130) #图片二值化
arr_bins, image = modeling.normalize_bins(bins,width,height) #统一尺寸
#image_open = opening(image)
cv2.imwrite(dir+'bins_'+filename,image)
x_test = convImage(image,convWidth,convHeight) #切割图片,计算每一个区域的平均灰度值
return x_test
def getXtest(dir,filenames,width,height,convWidth,convHeight):
'''读取指定路径dir下的所有图片,构建测试集,filenames为测试集图片名称的列表'''
x_list = []
for i in range(0,len(filenames)):
array = simpleRead(dir,filenames[i],width,height,convWidth,convHeight)
x_list.append(array)
x_test = preprocessing.scale(x_list) #将测试集归一化,若不归一化,每次测试都会得到一个结果
return x_test
def predict(filenames,width,height,convWidth,convHeight):
'''对测试图片进行预测'''
x_test = getXtest(TEST_DIR,filenames,width,height,convWidth,convHeight)
clf = joblib.load(MODEL) #加载模型,MODEL为模型的路径和名称
result = clf.predict(x_test)
print('预测结果:')
print(result)
print('正确结果:')
showTure(filenames)
需要注意的是,在对图片继续预测时,一定要将测试图片进行标准化处理,否则总是会得到一个结果。sklearn的preprocessing.scale()方法就可以提供这样一个功能,这样操作的本质就是将数据按其属性(按列进行)减去其均值,然后除以其方差,使得所有数据都聚集在0,附近,方差为1(参见点击打开链接)。用代码说明如下。
def standard():
'''标准化'''
a = np.array([[2.,3.,4.,5.],
[5.,3.,6.,7.],
[2.,1.,9.,3.]])
mean = a.mean(axis=0) #计算每一列的平均值
print('mean = ',mean)
std = a.std(axis=0) #计算每一列的方差
print('std = ',std)
x1 = (a-mean)/std
print('x1 = ',x1)
x2 = preprocessing.scale(a)
print('x2 = ',x2)
print(x1 == x2)
显示的结果为:
由此可见,x1和x2的结果完全相等。