KNN识别图像上的数字及python实现
图像文本识别的步骤一般为图像预处理,图片切割,特征提取、文本分类和图像文本输出几个步骤,我们也可以按这个步骤来识别图像中的数字。
一、图像预处理
在图像预处理中,验证码识别还要对图像进行去燥,文字还原等比较复杂的处理,由于我的图像没什么干扰因素,所以直接对其进行二值处理即可。处理结果如下:
因为图片上的数据灰度值都是75或76,所以只需把灰度值等于75或76的赋为1,其余的为0即可,代码如下:
def pretreatment(ima): ima=ima.convert('L') #转化为灰度图像 im=numpy.array(ima) #转化为二维数组 for i in range(im.shape[0]):#转化为二值矩阵 for j in range(im.shape[1]): if im[i,j]==75 or im[i,j]==76: im[i,j]=1 else: im[i,j]=0 return im ima=PIL.Image.open('e://bwtest2//6//6-1.png') #读入图像 im=pretreatment(ima) #调用图像预处理函数 for i in im: print(i)
二、图片切割
因为最小的识别单位是10以内的个位数字,所以有必要对图片中的数据进行切割,然后逐个对其识别。切割的过程如下:
切割的过程其实就是对二值矩阵进行分片的过程,只要找到0到1和1到0的过度点就可以判断出切割点的位置了。
三、特征提取
对图片进行切割后,得提取出每个图片的特征,然后才能做后续的处理。图像的特征很多,本文选取1占所在区域的比例作为图片特征。首先将图片分为四部分,如下图所示:
然后计算左上部分1所占的比例A1、左下部分1所占的比例A2、右上部分1所占的比例A3、右下部分1所占的比例A4和所有1占整副图的比例A5。这样,就提取出了此幅图的特征向量A=[A1,A2,A3,A4,A5]。664那副图的特征分别为:
可见两个6的特征极其相似,和4的特征相差有点大。切割图片并提取特征的代码如下:
#提取图片特征 def feature(A): midx=int(A.shape[1]/2)+1 midy=int(A.shape[0]/2)+1 A1=A[0:midy,0:midx].mean() A2=A[midy:A.shape[0],0:midx].mean() A3=A[0:midy,midx:A.shape[1]].mean() A4=A[midy:A.shape[0],midx:A.shape[1]].mean() A5=A.mean() AF=[A1,A2,A3,A4,A5] return AF #切割图片并返回每个子图片特征 def incise(im): #竖直切割并返回切割的坐标 a=[];b=[] if any(im[:,0]==1): a.append(0) for i in range(im.shape[1]-1): if all(im[:,i]==0) and any(im[:,i+1]==1): a.append(i+1) elif any(im[:,i]==1) and all(im[:,i+1]==0): b.append(i+1) if any(im[:,im.shape[1]-1]==1): b.append(im.shape[1]) #水平切割并返回分割图片特征 names=locals();AF=[] for i in range(len(a)): names['na%s' % i]=im[:,range(a[i],b[i])] if any(names['na%s' % i][0,:]==1): c=0 elif any(names['na%s' % i][names['na%s' % i].shape[0]-1,:]==1): d=names['na%s' % i].shape[0]-1 for j in range(names['na%s' % i].shape[0]-1): if all(names['na%s' % i][j,:]==0) and any(names['na%s' % i][j+1,:]==1): c=j+1 elif any(names['na%s' % i][j,:]==1) and all(names['na%s' % i][j+1,:]==0): d=j+1 names['na%s' % i]=names['na%s' % i][range(c,d),:] AF.append(feature(names['na%s' % i])) #提取特征 for j in names['na%s' % i]: print(j) return AF ima=PIL.Image.open('e://bwtest2//test//5.png') im=pretreatment(ima) #图片预处理 AF=incise(im) #切割图片并提取特征 for i in AF: print(i)
四、数字分类
文本分类的方法有k最近邻算法、支持向量机、神经网络等,本文选用的是最简单的最近邻算法。
K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。KNN算法中,所选择的邻居都是已经正确分类的对象。相比于其他算法,其时间复杂度和空间复杂度都很高,但具有精度高、对异常值不敏感、无输入数据假定的优点。
要让计算机识别东西,首先得告诉它这东西长啥样,然后它才能正确的识别。所以我把系统中的“0、1、2、3、4、5、6、7、8、9、.”各截了15副图放到文件夹中,如下图所示:
其中,‘‘10’’夹中放的是“.”,test文件夹中放的是待识别的数据。KNN算法的具体实现步骤如下:
1)提取1-10文件夹中所有图片的特征并保存在字典train_set中
2)分割并提取待识别图片的的特征
3)每个特征都和train_set中的所有特征计算距离
4)对距离进行排序,并选出排名前K个的键值
5)统计K个键值中每个类别出现的个数,出现个数最多的类别就是最终的分类
6)合并所有子特征的分类结果即是这幅图片上的数字
664那副图的KNN分类结果和源程序如下
#训练已知图片的特征 def training(): train_set={} for i in range(11): value=[] for j in range(15): ima=PIL.Image.open('e://bwtest2//'+str(i)+'//'+str(i)+'-'+str(j)+'.png') im=pretreatment(ima) AF=incise(im) value.append(AF[0]) train_set[i]=value #把训练结果存为永久文件,以备下次使用 output=open('e://bwtest2//train_set.pkl','wb') pickle.dump(train_set,output) output.close() return train_set #计算两向量的距离 def distance(v1,v2): vector1=numpy.array(v1) vector2=numpy.array(v2) Vector=(vector1-vector2)**2 distance = Vector.sum()**0.5 return distance #用最近邻算法识别单个数字 def knn(train_set,V,k): key_sort=[11]*k value_sort=[11]*k for key in range(11): for value in train_set[key]: d=distance(V,value) for i in range(k): if d<value_sort[i]: for j in range(k-2,i-1,-1): key_sort[j+1]=key_sort[j] value_sort[j+1]=value_sort[j] key_sort[i]=key value_sort[i]=d break max_key_count=-1 key_set=set(key_sort) for key in key_set: if max_key_count<key_sort.count(key): max_key_count=key_sort.count(key) max_key=key return max_key #合并一副图片的所有数字 def identification(train_set,AF,k): result='' for i in AF: key=knn(train_set,i,k) if key==10: key='.' result=result+str(key) return float(result) train_set=training() ima=PIL.Image.open('e://bwtest2//test//5'.png') im=pretreatment(ima) #预处理 AF=incise(im) #分割并提取图片 identification(train_set,AF,7) #knn识别
五、图片数字输出
最后,我们需要识别test文件夹中的所有图片并把识别出的数字输出到EXCEL文件中。并对数据的状态做一些简单的判断,输出部分结果如下:
其中,第一列为昨日的备份数据(尚未采集),第二列为今日需识别的数据,第三列为今日数据的状态。此程序识别正确率达到百分之百,看来用图像识别的方法完成录入图片数据工作不失为一种好的策略啊。本文用的版本为python3.5,完整程序如下:
import numpy import PIL.Image import pickle import xlwt #图片预处理 def pretreatment(ima): ima=ima.convert('L') #转化为灰度图像 im=numpy.array(ima) #转化为二维数组 for i in range(im.shape[0]):#转化为二值矩阵 for j in range(im.shape[1]): if im[i,j]==75 or im[i,j]==76: im[i,j]=1 else: im[i,j]=0 return im #提取图片特征 def feature(A): midx=int(A.shape[1]/2)+1 midy=int(A.shape[0]/2)+1 A1=A[0:midy,0:midx].mean() A2=A[midy:A.shape[0],0:midx].mean() A3=A[0:midy,midx:A.shape[1]].mean() A4=A[midy:A.shape[0],midx:A.shape[1]].mean() A5=A.mean() AF=[A1,A2,A3,A4,A5] return AF #切割图片并返回每个子图片特征 def incise(im): #竖直切割并返回切割的坐标 a=[];b=[] if any(im[:,0]==1):#避免截图没截好的情况 a.append(0) for i in range(im.shape[1]-1): if all(im[:,i]==0) and any(im[:,i+1]==1): a.append(i+1) elif any(im[:,i]==1) and all(im[:,i+1]==0): b.append(i+1) if any(im[:,im.shape[1]-1]==1): b.append(im.shape[1]) #水平切割并返回分割图片特征 names=locals() #初始化分割后子图片的动态变量名 AF=[] #初始化子图片的特征列表 for i in range(len(a)): names['na%s' % i]=im[:,range(a[i],b[i])] if any(names['na%s' % i][0,:]==1): c=0 elif any(names['na%s' % i][names['na%s' % i].shape[0]-1,:]==1): d=names['na%s' % i].shape[0]-1 for j in range(names['na%s' % i].shape[0]-1): if all(names['na%s' % i][j,:]==0) and any(names['na%s' % i][j+1,:]==1): c=j+1 elif any(names['na%s' % i][j,:]==1) and all(names['na%s' % i][j+1,:]==0): d=j+1 names['na%s' % i]=names['na%s' % i][range(c,d),:] AF.append(feature(names['na%s' % i])) for j in names['na%s' % i]: print(j) return AF #训练已知图片的特征 def training(): train_set={} for i in range(11): value=[] for j in range(15): ima=PIL.Image.open('e://bwtest2//'+str(i)+'//'+str(i)+'-'+str(j)+'.png') im=pretreatment(ima) AF=incise(im) #切割并提取特征 value.append(AF[0]) train_set[i]=value #把训练结果存为永久文件,以备下次使用 output=open('e://bwtest2//train_set.pkl','wb') pickle.dump(train_set,output) output.close() return train_set #计算两向量的距离 def distance(v1,v2): vector1=numpy.array(v1) vector2=numpy.array(v2) Vector=(vector1-vector2)**2 distance = Vector.sum()**0.5 return distance #用最近邻算法识别单个数字 def knn(train_set,V,k): key_sort=[11]*k value_sort=[11]*k for key in range(11): for value in train_set[key]: d=distance(V,value) for i in range(k):#从小到大排序 if d<value_sort[i]: for j in range(k-2,i-1,-1): key_sort[j+1]=key_sort[j] value_sort[j+1]=value_sort[j] key_sort[i]=key value_sort[i]=d break max_key_count=-1 key_set=set(key_sort) for key in key_set: #统计每个类别出现的次数 if max_key_count<key_sort.count(key): max_key_count=key_sort.count(key) max_key=key return max_key #合并一副图片的所有数字 def identification(train_set,AF,k): result='' for i in AF: key=knn(train_set,i,k) if key==10: key='.' result=result+str(key) return float(result) #识别文件夹中的所有图片上的数据并输出到EXCELL中 def main(k,trained=None,backup=None): # k:knn算法的的k,即取训练集中最相邻前k个样本进行判别 #trained:trained=0则程序会开始训练出训练集,否则会用已保存的训练集 #backup:backup=0则程序令昨日数据均为0,并备份今日数据到昨日数据和昨日测试数据 # backup=1则程序会令昨日数据为昨日测试数据,并备份今日数据到昨日测试数据 # backup等于其他时则程序会令昨日数据为昨日数据,并备份今日数据到昨日数据 if trained==0: train_set=training() else: pkl_file=open('e://bwtest2//train_set.pkl','rb') train_set=pickle.load(pkl_file) pkl_file.close() if backup==0: yestoday_data=[0]*32 elif backup==1: pkl_file=open('e://bwtest2//yestoday_data_test.pkl','rb') yestoday_data=pickle.load(pkl_file) pkl_file.close() else: pkl_file=open('e://bwtest2//yestoday_data.pkl','rb') yestoday_data=pickle.load(pkl_file) pkl_file.close() backups=[] workbook = xlwt.Workbook('e://bwtest2//bwtest2.xls') sheet = workbook.add_sheet("record and contrast") #设置EXCEL字体为绿色 font = xlwt.Font() font.colour_index = 3 style = xlwt.XFStyle() style.font = font #识别所有图片的数字并输出到EXCELL中 for i in range(1,33): ima=PIL.Image.open('e://bwtest2//test//'+str(i)+'.png') im=pretreatment(ima) AF=incise(im) result=identification(train_set,AF,k) backups.append(result) sheet.write(i-1, 0, yestoday_data[i-1]) if result==yestoday_data[i-1]: sheet.write(i-1, 1, result,style) sheet.write(i-1, 2, '正常') else: sheet.write(i-1, 1, result) sheet.write(i-1, 2, '待定') workbook.save('e://bwtest2//bwtest2.xls') if backup==0: output=open('e://bwtest2//yestoday_data_test.pkl','wb') pickle.dump(backups,output) output.close() output=open('e://bwtest2//yestoday_data.pkl','wb') pickle.dump(backups,output) output.close() elif backup==1:#/yestoday_data_test用来测试之用 output=open('e://bwtest2//yestoday_data_test.pkl','wb') pickle.dump(backups,output) output.close() else: output=open('e://bwtest2//yestoday_data.pkl','wb') pickle.dump(backups,output) output.close() main(4,0,0) #表示:4近邻,还没有训练集备份,还没有昨日数据备份