Python实现基于KNN算法的笔迹识别功能详解
本文详细介绍利用Python和KNN算法实现手写数字识别的完整流程。内容涵盖数据读取、Max-Min及L2范数归一化方法、基于余弦相似度的最近邻搜索策略,以及使用OpenCV进行图像预处理。同时针对识别率低的问题,分析了图像缩放、二值化及笔画粗细对结果的影响,并提供了相应的优化建议与OpenCV环境配置指南。
本文分享一个使用Python实现基于KNN(K近邻)算法的手写笔迹识别示例,供读者参考学习。
实现该功能主要依赖以下库:
- Numpy库
- Pandas库
- 手写识别数据集,点击此处本站下载。
数据集说明:
数据文件包含785列,其中第一列为标签(label),其余784列存储了28x28像素灰度图像(像素值范围0~255)的像素数据。
KNN(K近邻算法)原理:
在训练集中寻找与新数据最相似的K条记录,依据这些记录的主要分类结果来决定新数据的类别。
这里的“主要分类”可依据不同判别标准确定,例如“多数投票”、“最近邻”或“距离加权”。
程序主要包含以下三个部分:
1. 数据归一化处理(normalization); 2. (关键步骤)找出与测试数据(test)最接近的训练数据(train)编号,根据该编号获取对应的标签(label),并将此标签赋给测试数据的预测值; 3. 统计测试数据真实标签与预测标签的一致率,即正确率。
Step 1
导入Numpy与Pandas库
import numpy as np import pandas as pd
Step 2
对数据进行归一化处理
数据归一化有多种方法,例如:
一、Max-Min标准化
Max-Min标准化是对原始数据进行线性变换。设minA和maxA分别为属性A的最小值和最大值,将A的一个原始值x通过Max-Min标准化映射到区间[0,1]中的值x',其公式为:
新数据 = (原数据 - 最小值) / (最大值 - 最小值)
二、
新数据 = 原数据 / (原数据的平方和开根号)
三、
y = ( x - min )/( max - min ),其中min为x的最小值,max为x的最大值,输入向量为x,归一化后的输出向量为y。该公式将数据归一化到[0, 1]区间,适用于激活函数采用S形函数(值域为(0,1))的情况。
本文采用第二种方法。
def normalize(x): norms = np.apply_along_axis(np.linalg.norm,1,x) return x / np.expand_dims(norms,-1)
调用np中的linalg.norm(x)和 apply_along_axis(func, axis, x)函数
linalg.norm(x)函数的作用是 return sum(abs(xi)**2)**0.5,
apply_along_axis(func, axis, x)函数的作用是将x按axis方向执行func函数,axis=0表示沿列方向运算,axis=1表示沿行方向运算。
Step 3
找出与测试数据最接近的训练数据,这是最关键的一步。
在此场景中,测试数据与训练数据可视作空间中的两个向量,问题转化为计算这两个向量的相似程度。
可以将它们想象为从原点([0, 0, ...])出发指向不同方向的线段。若夹角为0度,方向相同且重合;若为90度,方向正交不相似;若为180度,方向相反。因此,可通过夹角大小判断向量相似性,夹角越小代表越相似。
假设a向量为[x1, y1],b向量为[x2, y2],余弦定理可改写为以下形式:

余弦定理公式
def nearest_neighbor(norm_func,train_data,train_label,test_data): train_data = norm_func(train_data) test_data = norm_func(test_data) cos = np.dot(train_data,np.transpose(test_data))#np.transpose为求转置,dot为矩阵的乘积,结果为cos的一列值为test与train的相似度 max_cos = np.argmax(cos,axis=0)#np.argmax为cos中一列上方的最大值 test_pred = train_label[max_cos]#train_label为一列,max_cos为一个数组,train_label[max_cos]会读出train_label中max_cos数组编号的元素 return test_pred#返回test的预测值
Step 4
统计预测值的正确率
def validate(test_pred,test_label): c=len(test_pred)#在数组里面套数组的时候,len得到的是大数组里数组的个数,在只有一层数组的时候,得到的是数组中元素的个数 correct=(test_pred == test_label).sum()#统计两个数组中有多少个元素相同 return float(correct)/c#必须转变成浮点数再做除法,之前使用correct/c得到0
测试代码示例:
if __name__ == '__main__':
train_num = 200
test_num = 300#测试数据起始是test_num-train_num
x = pd.read_csv('train.csv')
x_train = x.values[0:train_num,1:]#读取pandas中读取出来的数据,需要用data.values[]
x_train_label = x.values[0:train_num,0]#第一列是label,每幅图的数据是一行
x_test = x.values[train_num:test_num,1:]
x_test_label = x.values[train_num:test_num,0]
test_pred=nearest_neighbor(normalize,x_train,x_train_label,x_test)
prec=validate(test_pred,x_test_label)
print u"正确率为%.2f"%(prec)#浮点数是%f
完整代码点击此处本站下载。
注解:
上述部分主要讲解KNN算法,使用的是现成的28x28数据。在实际笔迹分析中,首先需将图像转换为矩阵数据。
下面介绍图像与矩阵相互转换的方法。
矩阵转化为图像
需使用图像处理库Python Imaging Library (PIL)。
在Windows下使用pip install PIL安装失败,采取下载PIL.exe双击安装的方法。 下载地址:
PIL官方下载地址
import pandas as pd
import numpy as np
from PIL import Image
# load data
train = pd.read_csv('train.csv')
# now draw the numbers
for ind, row in train.iloc[0:3].iterrows():#iloc方法(介绍见后)来获得前3行数据
i = row[0]#[0]为标签项
arr = np.array(row[1:], dtype=np.uint8)#1-784列组成一幅图,,uint8为8位无符号整数
#arr = np.array(255 - row[1:], dtype=np.uint8)#如果需要颜色取反,用255减去当前每个像素点的值
arr.resize((28, 28))#把它变成28*28的矩阵
#save to file
im = Image.fromarray(arr)
im.save("./train_pics/%s-%s.png" % (ind, i))#第一个%s(ind)表示它是第几幅图像,第二个%s表示这个图像里面数字是几 ,注意该语句不能产生文件夹,需要现在指定目录建一个文件夹
.iloc()方法介绍
iloc[行位置,列位置] df.iloc[1,1]#选取第二行,第二列的值,返回的为单个值 df.iloc[0,2],:]#选取第一行及第三行的数据
图像转化为矩阵
需使用opencv(open source computer vision)库,下载安装方式请参照附录:python_OpenCV安装。
这里主要介绍其几个简单功能。
1. 静态图像的输入与输出。
cv2.imread('xxx.png')#输入,#这里输入image的维度image.shape = (w,h,3),w*h是图片的长宽,3是BGR等三种颜色的channel值,每个值为0~255
cv2.imwrite('xxx.jpg', image)#输出
2. 将图片转化为灰度图片。
#灰度图片的颜色channel只有一个,0~255表示灰度值
grayImage = cv2.imread('xxx.png',cv2.CV_LOAD_IMAGE_GRAYSCALE)
3. 改变图像的大小。
print grayImage.shape#查看图像的shape,shape为(137,301),如果查看的是图像的size,则为42137(41237=137*301) res=cv2.resize(grayImage,(28,28),interpolation=cv2.INTER_CUBIC)#将图片grayImage以cv2.INTER_CUBIC方式变化为(28,28)大小的图片
变换方法包括:
- CV_INTER_NN - 最近邻插值
- CV_INTER_LINEAR - 双线性插值(默认使用)
- CV_INTER_AREA - 使用像素关系重采样。图像缩小时可避免波纹,放大时类似于CV_INTER_NN方法
- CV_INTER_CUBIC - 立方插值
以下是关于输入、输出、转换为灰度图、改变图像大小及显示的完整程序,请注意图像在窗口中的显示方式。
import cv2
image = cv2.imread('111.png')#读
cv2.imwrite('111.jpg', image)#写
grayImage = cv2.imread('111.png',cv2.CV_LOAD_IMAGE_GRAYSCALE)
print grayImage.shape
res=cv2.resize(grayImage,(28,28),interpolation=cv2.INTER_CUBIC)
#显示图像
cv2.imshow('test',grayImage)#显示灰度图
cv2.imshow('change',res)#显示改变了大小的图
#捕获键盘输入
k=cv2.waitKey(0)
if k==27:#27表示ESC键
cv2.destroyWindow()
cv2.imshow()用于将图片显示在窗口中,其后必须紧跟cv2.waitKey()函数以维持显示,否则程序中止后窗口将关闭。cv2.waitKey()函数用于捕获键盘输入,cv2.destroyWindow()用于释放窗口。
掌握图片读取与输出后,即可使用已编写的KNN算法识别笔迹。
问题:
使用大量手写数据验证识别准确性时,发现准确率尚不理想。主要存在的问题包括:
1. 图片尺寸问题:不应简单将整张图片缩放为28x28,而应识别包含数字的中心区域,去除周围白边。
2. 手写数字照片中,书写区域未必保证为黑色(像素值为255)。
解决方法:需使用滤波器处理因纸张或拍摄问题导致的像素值变化。在未使用滤波器的情况下,可将照片替换为在画图板上书写的数字进行测试。
3. 书写笔画的粗细会影响判断结果。
解决办法:这可能是训练样本不足或整体字迹偏粗所致,导致细笔迹无法识别。此外,应监测输入字体的粗细,对极细笔迹进行膨胀处理,对极粗笔迹进行腐蚀处理。
附录:python_OpenCV安装指南
网上许多教程基于VS环境安装OpenCV,而本文作者一直在Windows 7 32位系统下,使用sublime+cmd环境进行Python编程,因此摸索出在此条件下的OpenCV安装方法。
使用pip install numpy语句安装numpy。 (若出现错误:Microsoft Visual C++ 9.0 is required <unable to find vcvarsall.bat>,请以管理员身份安装Microsoft Visual C++ 9.0,重启计算机后,再次使用pip install numpy语句安装numpy。
opencv2.4.10下载
下载后解压(任意目录),将解压目录中opencv文件夹下的build->python->2.7->x86中的cv2.pyd文件复制到python2.7\Lib\site-packages中。
测试安装是否成功,执行解压目录下的sources\samples\python\drawing.py,或进入python环境,使用
import cv2。
更多关于Python相关内容感兴趣的读者可查看本站专题:《Python数学运算技巧总结》、《Python数据结构与算法教程》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》、《Python入门与进阶经典教程》及《Python文件与目录操作技巧汇总》。
希望本文所述对大家Python程序设计有所帮助。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













