目录
我们的目标是构建一个可以识别手写数字的应用程序。为此,我们需要创建一个手写数字的图片文件 “ digits.png
”,图像的像素为:( 320*40 ), 其中包含 32个手写数字(每个数字16个),每个数字都是20x20
的图像,如下图。
因此,我们的第 1 步是将图像分割成 32 个不同的数字。对于每个数字,我们将其展平为400
像素的一行。那就是我们的训练集,即所有像素的强度值。这是我们可以创建的最简单的功能集。我们将每个数字的前8个样本用作训练数据,然后将后8个样本用作测试数据。
- import numpy as np # 导入numpy库,用于进行矩阵运算
- import cv2 as cv # 导入OpenCV库,用于图像处理
-
- img = cv.imread('digits.png') # 读取名为'digits.png'的图像
- gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY) # 将图像转换为灰度图像
-
- # 现在我们将图像分割为32个单元格,每个单元格为20x20
- cells = [np.hsplit(row,16) for row in np.vsplit(gray,2)] # 将图像分割成2行16列的单元格,每个单元格为20x20像素
-
- # 使其成为一个Numpy数组。它的大小将是(2,16,20,20)
- x = np.array(cells) # 将分割后的图像转换为一个Numpy数组
-
- # 现在我们准备train_data和test_data。
- train = x[:,:8].reshape(-1,400).astype(np.float32) # 取数组的前50%作为训练数据,并将其形状转换为(16,400)的浮点数类型
- test = x[:,8:16].reshape(-1,400).astype(np.float32) # 取数组的后50%作为测试数据,并将其形状转换为(16,400)的浮点数类型
-
- # 为训练和测试数据创建标签
- k = np.arange(2) # 创建一个包含数字0到1的一维数组
- train_labels = np.repeat(k,8)[:,np.newaxis] # 将数组重复8次,得到一个(16,1)的二维数组,作为训练数据的标签
- test_labels = train_labels.copy() # 复制训练数据的标签,作为测试数据的标签
-
- # 初始化kNN,训练数据,然后使用k = 1的测试数据对其进行测试
- knn = cv.ml.KNearest_create() # 创建一个kNN分类器对象
- knn.train(train, cv.ml.ROW_SAMPLE, train_labels) # 使用训练数据和标签训练分类器
- ret,result,neighbours,dist = knn.findNearest(test,k=1) # 使用测试数据和k=1的参数调用分类器的findNearest方法,得到返回值、预测结果、最近邻样本和距离
-
- # 现在,我们检查分类的准确性
- #为此,将结果与test_labels进行比较,并检查哪个错误
- matches = result==test_labels # 将预测结果与测试标签进行比较,得到一个布尔类型的数组
- correct = np.count_nonzero(matches) # 统计布尔数组中非零元素的数量,即预测正确的数量
- accuracy = correct*100.0/result.size # 计算预测准确率,即正确预测的数量占总数量的比例
- print(accuracy) # 输出预测准确率
-
-
- # 打印预测结果。这是一个新样本的标签。
- print( "result: {}\n".format(result) )
-
- # 打印最近邻样本的索引。这是一个新样本的1个最近邻样本在训练数据集中的索引。
- print( "neighbours: {}\n".format(neighbours) )
-
- # 打印最近邻样本的距离。这是一个新样本与它的1个最近邻样本的距离。
- print( "distance: {}\n".format(dist) )
我们通过获取result变量里面的值,确定1,2行的后8个样本图像被识别为数字几,来确定我们的准确率。通过下面我们可以看出,第1行后8个样本图像数字0,有2个被错误的识别为1,第2行后8个样本图像数字1,都被正确的识别为1。
上面的例程是识别0和1,只是为了更直观的进行演示,可以通过增加图像数据为 0~9 的手写图像数据,就可以识别全部的数字。而且自己可以通过增加更多的图像数据来训练,从而提高识别的准确率, 这里我开始做进一步的详细教程。
为此,我们需要创建一个手写数字的文件 “ digits.png
”,图像的像素为:( 1800*400 ), 其中包含 900 个手写数字(每个数字 90 个),每个数字都是20x20
的图像,如下图。
因此,我们的第 1 步是将图像分割成 900 个不同的数字。对于每个数字,我们将其展平为400
像素的一行。那就是我们的训练集,即所有像素的强度值。这是我们可以创建的最简单的功能集。我们将每个数字的 前45个样本 用作训练数据,然后将 后45个样本 用作测试数据。
- import numpy as np # 导入numpy库,用于进行矩阵运算
- import cv2 as cv # 导入OpenCV库,用于图像处理
-
- img = cv.imread('digits.png') # 读取名为'digits.png'的图像
- gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY) # 将图像转换为灰度图像
-
- # 现在我们将图像分割为900个单元格,每个单元格为20x20
- cells = [np.hsplit(row,90) for row in np.vsplit(gray,10)] # 将图像分割成10行90列的单元格,每个单元格为20x20像素
-
- # 使其成为一个Numpy数组。它的大小将是(10,90,20,20)
- x = np.array(cells) # 将分割后的图像转换为一个Numpy数组
-
- # 现在我们准备train_data和test_data。
- train = x[:,:45].reshape(-1,400).astype(np.float32) # 取数组的前50%作为训练数据,并将其形状转换为(450,400)的浮点数类型
- test = x[:,45:90].reshape(-1,400).astype(np.float32) # 取数组的后50%作为测试数据,并将其形状转换为(450,400)的浮点数类型
-
- # 为训练和测试数据创建标签
- k = np.arange(10) # 创建一个包含数字0到10的一维数组
- train_labels = np.repeat(k,45)[:,np.newaxis] # 将数组重复45次,得到一个(450,1)的二维数组,作为训练数据的标签
- test_labels = train_labels.copy() # 复制训练数据的标签,作为测试数据的标签
-
- # 初始化kNN,训练数据,然后使用k = 1的测试数据对其进行测试
- knn = cv.ml.KNearest_create() # 创建一个kNN分类器对象
- knn.train(train, cv.ml.ROW_SAMPLE, train_labels) # 使用训练数据和标签训练分类器
- ret,result,neighbours,dist = knn.findNearest(test,k=1) # 使用测试数据和k=1的参数调用分类器的findNearest方法,得到返回值、预测结果、最近邻样本和距离
-
- # 现在,我们检查分类的准确性
- #为此,将结果与test_labels进行比较,并检查哪个错误
- matches = result==test_labels # 将预测结果与测试标签进行比较,得到一个布尔类型的数组
- correct = np.count_nonzero(matches) # 统计布尔数组中非零元素的数量,即预测正确的数量
- accuracy = correct*100.0/result.size # 计算预测准确率,即正确预测的数量占总数量的比例
- print("预测准确率:",accuracy) # 输出预测准确率
-
-
- # 打印预测结果。这是一个新样本的标签。
- print( "result:\n{}\n".format(result) )
-
- # 打印最近邻样本的索引。这是一个新样本的1个最近邻样本在训练数据集中的索引。
- print( "neighbours: {}\n".format(neighbours) )
-
- # 打印最近邻样本的距离。这是一个新样本与它的1个最近邻样本的距离。
- print( "distance: {}\n".format(dist) )
我们通过获取result变量里面的值,确定1~10行的后45个样本图像被识别为数字几,来确定我们的准确率。
- 预测准确率: 87.11111111111111
- result:
- [
- [0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.][0.]
- [1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][9.][1.][9.][1.][8.][8.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][1.][8.][8.]
- [2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][4.][7.][2.][5.][3.][3.][2.][2.][2.][2.][2.][6.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][2.][5.][2.]
- [3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][1.][3.][3.][7.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][3.][1.]
- [4.][4.][4.][4.][4.][4.][4.][4.][5.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][4.][5.][4.][4.][0.][4.][4.][5.][4.][4.][4.][4.][4.][4.][0.][4.][1.]
- [5.][5.][5.][5.][5.][5.][5.][5.][5.][5.][5.][5.][5.][1.][5.][5.][5.][5.][5.][1.][5.][5.][5.][5.][5.][5.][3.][5.][5.][5.][5.][3.][2.][5.][3.][5.][5.][5.][5.][5.][5.][8.][5.][5.][5.]
- [6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][0.][6.][6.][6.][6.][6.][6.][6.][1.][6.][6.][6.][6.][6.][8.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.][6.]
- [7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][1.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][9.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][7.][1.][7.][7.][7.][7.][7.][7.][1.]
- [6.][8.][8.][8.][4.][8.][8.][8.][8.][3.][8.][3.][8.][8.][8.][8.][8.][8.][8.][8.][8.][8.][8.][8.][9.][8.][8.][8.][8.][8.][4.][3.][8.][8.][8.][8.][4.][8.][8.][8.][8.][1.][8.][8.][0.]
- [9.][9.][9.][8.][9.][9.][9.][9.][9.][9.][9.][9.][7.][9.][9.][9.][9.][9.][9.][9.][1.][1.][9.][1.][9.][9.][9.][9.][9.][9.][9.][9.][9.][9.][9.][9.][7.][7.][9.][9.][7.][1.][7.][1.][7.]
- ]
通过上面我们可以看出,错误还是有很多的,那是为什么呢?其实我们查看图片就能清晰的看出前45个样本图像和后45个样本图像的相关性(字体样式)有很大区别,所以造成识别的准确率这么低。
上面的例程只是教我们识别已经保存好的图像,进行数字的识别,但是这种通过自己手动保存,再去识别的方式太过笨拙了。那我们可以进一步扩展,通过摄像头拍照获取图像数据流,然后经过OpenCV的前处理后,再保存为测试数据,通过训练好的kNN近邻进行数字的识别,那我们这个项目是不是就更加的完美了呢?