【OpenCV】利用OpenCV中的KNN算法实现手写数字和手写字母的识别
一、KNN算法(K近邻)
-
KNN(K Near Neighbor)
:k个最近的邻居,即每个样本都可以用它最接近的k个邻居来代表。 -
距离度量:一般采用欧式距离度量,常用的距离度量方式还有:曼哈顿距离、切比雪夫距离
-
K值是可以设置的变量:
- K值过小:k值小,特征空间被划分为更多子空间,整体模型变复杂,容易发生过拟合,k值越小,选择的范围就比较小,训练的时候命中率较高,近似误差小,而用测试集的时候就容易出错,估计误差大,容易过拟合。
- K值过大:无论输入,都将简单的预测它属于训练实例中最多的类。
-
举例分析:
-
有两个类,红色、蓝色。将红色点标记为0,蓝色点标记为1。创建25个训练数据,把它们分别标记为0或者1。Numpy中随机数产生器可以帮助我们完成这个任务
import cv2 import numpy as np import matplotlib.pyplot as plt # 包含25个已知/训练数据的(x,y)值的特征集 trainData = np.random.randint(0, 100, (25, 2)).astype(np.float32) # 用数字0和1分别标记红色和蓝色 responses = np.random.randint(0, 2, (25, 1)).astype(np.float32) # 画出红色的点 red = trainData[responses.ravel() == 0] plt.scatter(red[:, 0], red[:, 1], 80, 'r', '^') # 画出蓝色的点 blue = trainData[responses.ravel() == 1] plt.scatter(blue[:, 0], blue[:, 1], 80, 'b', 's') plt.show()
得到:
-
下面就是
KNN
的初始化,通过cv2.ml.KNearest_create()
调用KNN
传入一个训练数据集,以及对应的label
。给它一个测试数据即测试集,让它来进行分类。OpenCV
中使用knn.findNearest()
函数- 参数1:测试数据
- 参数2:k的值
- 返回值1:由kNN算法计算得到的测试数据的类别标志
- 返回值2:k的最近邻居的类别标志
- 返回值3:每个最近邻居到测试数据的距离
测试数据被标记为绿色。
得到:
结果为:
result: [[0.]] #测试数据被分为红色 neighbours: [[0. 1. 0.]] #测试数据有三个邻居,其中两红一蓝 distance: [[ 185. 338. 2205.]]#测试数据与邻居之间的距离
二、KNN识别手写数字
KNN算法需要训练集和测试集。OpenCV 安装包中有一张图片digits.png
,其中有5000个手写数字(每个数字重复500次)。每个数字是一个20x20的小图。所以第一步就是将这个图像分割成5000个不同的数字,将拆分后的每一个数字图像展成400(20x20)
个像素点的图像。这个就是我们的特征集,所有像素的灰度值。我们使用每个数字的前250个样本做训练数据,剩余的250个做测试数据。
import numpy as np
import cv2
img = cv2.imread('digits.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 我们把图片分成5000张,每张20 x 20个像素
cells = [np.hsplit(row, 100) for row in np.vsplit(gray, 50)] #np.hsplit是将数组纵向划分
# 使cells变成一个numpy数组,它的维度为(50, 100, 20, 20)
x = np.array(cells)
# 训练数据和测试数据
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)
# 为训练集和测试集创建一个label
k = np.arange(10)
train_labels = np.repeat(k, 250)[:, np.newaxis]
test_labels = train_labels.copy()
# 初始化KNN,训练数据、测试KNN,k=5
knn = cv2.ml.KNearest_create()
knn.train(train, cv2.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)
#结果为:91.76
digits.png
:
三、KNN识别英文字母
OpenCV自带的数据文件letter-recognition.data
。有20000行,每一行的第一列是我们的一个字母标记,接下来的16个数字是它的不同特征。取前10000个作为训练集,剩下的10000个作为测试集。先把字母换成ASCII码,因为我们不直接处理字母。
import cv2
import numpy as np
# 加载数据,并转换字母为ASCII码
data = np.loadtxt('letter-recognition.data', dtype='float32', delimiter=',',
converters={0: lambda ch: ord(ch) - ord('A')})
# 拆分数据为俩份,训练数据,测试数据各10000
train, test = np.vsplit(data, 2) # np.vsplit(data, 2) 将data横向分成两组,20000/2 = 10000
# 拆分训练数据集为特征及分类
responses, trainData = np.hsplit(train, [1]) # np.hsplit(train,[1])函数的作用是在第1个索引处将数组train纵向划分成两组,即responses保存的是原来的字母
labels, testData = np.hsplit(test, [1])
# 初始化KNN,训练模型,度量其准确性
knn = cv2.ml.KNearest_create()
knn.train(trainData, cv2.ml.ROW_SAMPLE, responses) #训练,以以trainData去标记response
ret, result, neighbours, dist = knn.findNearest(testData, k=5)
correct = np.count_nonzero(result == labels)#result为训练测试集特征得到的预测结果,labels存储的是本来的真实值,result==labels得到的为1的话,即为预测正确,np.count_nonzero(result == labels)即得到其中为1的总个数
accuracy = correct * 100.0 / 10000
print('OCR alphabet accuracy: ', accuracy)
#结果为:93.06