Open-CV K近邻算法(k-Nearest Neighbour)OCR手写识别

1.概念描述

1.1 什么是特征空间?

首先,K近邻算法是监督分类中最简单的算法,实际上也就是一个分类算法,另外分类也有很多很多种,例如一维空间物体的分类、二维空间物体的分类、三维空间物体的分类...被分类的物体所占据的空间叫做特征空间,例如你要分类的是一维物体,在数学上,它只有坐标x,那么一维空间就是它的特征空间,如果你要分类二维物体,数学上,它有(x,y),那么二维空间是它的特征空间。

1.2 K近邻算法是怎么分类的?

下面的图片就是二维空间物体的分类问题,我们很明显可以看出图中有两类。绿色的圆型是新添加的一个图形。

然后解释一下什么是K近邻算法的计算过程,由于OpenCV官方的教程过于形象,在此只做简单的翻译:

看上面的图片:我们究竟要把它放在红色的队伍里面,还是放在蓝色的队伍里面。

最起初的观点,我们直接把它放在红色里面,因为红色离他最近!(这就是近邻的意思)反驳的观点要说了,如果蓝色很多怎么办,那样我们就要重新考虑了,现在假设我们考虑距离它最近的三个邻居的颜色,决定它的分类,我们看到它周围最近的3个邻居中,红色2个>蓝色1个(蓝色虽然两个等距,但是限定了取最近的三个,所以蓝色取1个),那么它还是归红类,如果我们找它周围5个邻居呢,红色2个<蓝色3个,那就把它归为蓝色,如果7个邻居呢,红色2个<蓝色5个,归为蓝色类。从上面的分类过程中,我们就可以把选取邻居的个数抽象成K,这就是K近邻算法中K的含义。下面的表格把上面的分析过程整理了一下:

K 红色个数 蓝色个数 分类结果
1 1 0 红色
3 2 1 红色
5 2 3 蓝色
7 2 5 蓝色
9 3 6 蓝色

并且我们发现,K只能取奇数(如果是偶数,如果出现2:2之类的就分不清了)

1.3 就这么简单吗?

当然没有这么简单,在上面,我们只限定了要考虑的邻居个数K,和离它最近的邻居的个数,但是这些邻居有近有远,对它的影响理所应当不同。

所以如果我们按照距离给这些邻居分配影响权重,那么我们可以把加了权重的算法成为加权KNN或改进CNN。(其实就是这么简单[狗头])

一个简单的例子:

先把原始的点画出来,随机生成25个点,有红有蓝

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 在0-100的范围内,随机生成25个点(x,y)
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)
# 随机给这25个点分类,分为0,1两类,可以理解为上面说的红、蓝
responses = np.random.randint(0,2,(25,1)).astype(np.float32)
# 把0筛选出来,等会画成红的
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')
# 把1筛选出来,等会画成蓝的
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')
#展示
plt.show()

然后,生成一个新点,把这个点化成绿的,使用cv.ml.KNearest_create()先训练之前的红绿数据,然后定义K的值,判断新点应该归为蓝还是红,这里的数据都是随机数,所以每次运行结果都不一样。

#生成一个随机点,并画出来
newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')
#把原始的红蓝两类数据输入KNN中,让它知道这些点怎么分布
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
#给绿点分类:ret表示是否分类成功,results是分类结果,neighbours是邻居个数,dist是邻居距离,都是我们上面距离里面说过的概念,然后knn.findNearest(newcomer, 3)中的3就是K
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)
print( "result:  {}\n".format(results) )
print( "neighbours:  {}\n".format(neighbours) )
print( "distance:  {}\n".format(dist) )
plt.show()

分类图示,绿点周围3个点,两个蓝,一个红,归为蓝色!

2.OCR实践准备

这个OCR识别实际上就是上面理论的实践,所以不要畏难,上面是二维空间的分类,但是OCR有可能是16维、25维等甚至更多,但是他们理论上都是一样的。

实践需要手写字体的图片(密集恐惧症下载下来放大看),反正不是我写的,是官网附带的:

 观察上面的图像,它的数字从0-9.每个数字5行,一共50行,每行数字都是一样的,一共100列,所以一共5000个数字。

我们把它分成左右两部分,左边用来训练,右边用来验证,也就是训练2500,验证2500,也就是训练集和验证集都是50行*50列

在这里每个数字都是20*20像素的,400个像素,我们把这400个像素一字排开,作为一个400维的特征空间(这就是刚才介绍特征空间概念的原因),然后把像素图像和实际的数字对应起来,那么这样训练集就做好了。

验证集跟上面的制作是一样的,只不过它不参与训练,只用来后面的结果验证(看起来有点浪费样本,但是留出数据做验证是必要的)

3.代码解释

上面解释的比较详细,代码里面的注释也详细,直接看,直接看代码和输出的结果吧。

import numpy as np
import cv2 as cv
#读取图片,并转为灰度图像
img = cv.imread('../digits.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 把图片切割开来,也就是把每个数字从整张图里面分出来,对应图片里面的100行,59
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
# 把上面的cells变成(50,100,20,20)的array,也就是100行,50列,然后每个图像的尺寸是20*20像素
x = np.array(cells)
# 把上面的图片分为两组,本来一共是100*50等于5000个,2500个数字用来训练,2500个用来检测精度
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)
#上面为什么是400?实际上,手写图像是20*20像素,把像素格从前往后排开把它变成(x,y,z,...)一个400维的“点”
# 把训练集的图像对应的数组放在数组里面,也就是上面的response,这里的命名是train_labels,另外也把验证的结果做出来,用于验证:test_labels
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()
# 下面的代码就和上面那段一样了,创建knn开始训练,然后输出结果,只不过多了一步验证
knn = cv.ml.KNearest_create()
knn.train(train, cv.ml.ROW_SAMPLE, train_labels)
ret,result,neighbours,dist = knn.findNearest(test,k=5)

# 把自己的分类结果和实际的test_labels比较,如果一样,那就数一下对了多少个,最后算出精度,并打印。

matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print( accuracy )

结果是这样的,精度超过了90%,为什么精度这么高呢,因为官网给的示例数据比较好,并且只有黑白两色,所以得到的结果也不错,如果自己制作样本,结果应该达不到这样。

虽然达到了90%,不过这还远远不够,不足以作为实际应用,想要提高精度,还要想办法多一些样本。

猜你喜欢

转载自blog.csdn.net/JJSXBL/article/details/127676704