手写数字识别
测试环境:python3.8 .5
pyqt5 5.15.4
算法原理:传统机器学习算法距离分类
参考文献:
书名:图像识别与项目实践:VC++、MATLAB技术实现
作者:杨淑莹
以及:
画板工具参考博客
手写数字的特征提取
(1)搜索数据区,找出手写数字的上、下、左、右边界。
(2)将数字区域平均分为5×5的小区域。
(3)计算5×5的每一个小区域中黑像素所占比例。第一行的5个比例值保存到特征的前5个,第二行对应着特征的6~10个,以此类推。
(4)若特征大于0.1(指定值,可以根据需求改变),则将对应的5×5区域置黑。(在画笔工具绘制图像时,画笔粗细将影响图像特征提取结果,程序能够根据画笔粗细程度更改参数以达到较好的提取效果。)
基于模板匹配方法的手写数字识别
模板匹配方法将待分类样品与标准模板进行比较,看跟哪个模板匹配程度更好些,从而确定待测试样品的分类,属于近邻法则。它将训练样品集中的每个样品都作为模板,用测试样品与每个模板作比较,看与哪个模板最相似(即为近邻),就按最近似的模板的类别作为自己的类别。手写数字分10个类别,每个数字提取特征,为每个数字建立训练样品集,任何一个待测试样品在分类时与样品集里的每一个模板都算一算相似度,最相似的那一个样品的类别,就作为待测试样品的类别。因此,原理上说近邻法是最简单的,但是近邻法有一个明显的缺点,那就是计算量大,存储量大,要存储的模板很多,每个测试样品要对每个模板计算一次相似度,因此在模板数量很大时,计算量也是很大的。
本文实例采用模板匹配法。选取的特征数n为100维,特征库中每一类的样品数为1000个左右。考虑到计算复杂度及运算时间,采用模板匹配法进行手写数字的识别完全能够满足现实需要。样品与样品之间的距离计算采用欧氏距离法。
程序主要功能:
1. 初始化:
初始化,即对样本进行训练,获取手写数字图像的特征矩阵
2. 手写数字:
暂时可以较为准确地识别所写数字。
3.画板工具参考
主要代码:
图像预处理:
"""
-*- coding = utf-8 -*-
-------------------------------------------------
@time:2021/5/18 13:17
Author:CGG
We chat:CGG19991224
-------------------------------------------------
"""
import numpy as np
class imgtreat:
def __init__(self, Image):
self.Image = Image
self.shape = Image.shape
(m, n) = self.shape
self.bottom = m
self.right = n
self.left = 0
self.top = 0
self.temprow=100#缓存矩阵行数
self.tempcol=100#缓存矩阵列数
self.featurerow=10#特征矩阵行数
self.featurecol=10#特征矩阵列数
self.temp = np.zeros((self.temprow,self.tempcol)) # 设置缓存矩阵
self.Feature = np.zeros((self.featurerow, self.featurecol)) # 设置特征提取矩阵
def adapt(self):
(m, n) = self.shape
sumx = self.Image.sum(axis=1) # 将数组横向求和
sumy = self.Image.sum(axis=0) # 将数组纵向求和
# 获取图像左边界self.top
i = 1
while sumx[i - 1] <= 0:
i = i + 1
self.top = i
# 获取图像右边界self.bottom
i = m-1
while sumx[i] <= 0:
i = i - 1
self.bottom = i
# 获取图像上边界self.left
i = 0
while sumy[i] <= 0:
i = i + 1
self.left = i
# 获取图像上下左右边界self.right
i = n-1
while sumy[i] <= 0:
i = i - 1
self.right = i
for i in range(99):
for j in range(99):
self.temp[i, j] = self.Image[round((self.bottom-self.top)*i/99+self.top),round((self.right-self.left)*j/99+self.left)]
def getfeature(self,thickness):
(h, w) = self.temp.shape
h = round(h / self.featurerow)
w = round(w / self.featurecol)
for p in range(self.featurerow):
for q in range(self.featurecol):
sum = 0
for i in range(h):
for j in range(w):
sum = sum + self.temp[p*h+i, q*w+j]
if sum / (w * h) > 0.1*thickness/10:
self.Feature[p, q] = 1
def pretreatment(self,thickness):
self.adapt()
self.getfeature(thickness)
return self.Feature
识别函数代码:
"""
"""
-*- coding = utf-8 -*-
-------------------------------------------------
@time:2021/5/17 12:32
Author:CGG
We chat:CGG19991224
-------------------------------------------------
"""
import operator
from os import listdir
import matplotlib
import numpy as np
from PyQt5.QtCore import Qt
# Image=np.array()
import pandas as pd
import matplotlib.image
from PyQt5.QtWidgets import QProgressDialog, QMessageBox
import pretreat
# 识别图像类
class Recognize:
def __init__(self):
self.k = 100 # 分类过程中取出的最相近的个数
self.featurerows=10
self.featurecols=10
# 训练函数
def train(self, trainpath):
self.trainpath = trainpath
self.trainlist = listdir(self.trainpath)
self.numlist = len(self.trainlist) # 训练样本的数量
print("训练集数量:", self.numlist)
self.pd=QProgressDialog()
self.pd.setWindowTitle("初始化进度")
self.pd.setMinimumDuration(5)
self.pd.setFixedSize(300,70)
self.pd.setWindowModality(Qt.WindowModal)
self.pd.setRange(0, self.numlist)
FeatureMat = np.zeros((self.numlist, self.featurerows*self.featurecols)) # 设置特征矩阵来存储提取的特征矩阵
labels = np.zeros((self.numlist, 1)) # 设置标签矩阵存储样本对应的数字
for i in range(self.numlist):
filename = self.trainlist[i] # 获取文件名
fileStr = filename.split('.')[0] # 去掉文件后缀
labels[i] = int(fileStr.split('_')[0]) # 取文件名第一位文件的标签
trainmat = matplotlib.image.imread(self.trainpath + '/' + filename)
trainmat = np.around(trainmat / 255)
temp = pretreat.imgtreat(trainmat)
FeatureMat[i, :] = temp.pretreatment(10).reshape((1, self.featurerows*self.featurecols))
self.pd.setValue(i)
if self.pd.wasCanceled():#判断我们是否按下取消按钮
break
# print("\r初始化进度:%.2f %% " % ((i + 1) * 100 / self.numlist), end="")
df = pd.DataFrame(FeatureMat)
lf = pd.DataFrame(labels)
df.to_csv("test/FeatureMat.csv", index=False) # 保存特征矩阵
lf.to_csv("test/labels.csv", index=False) # 保存标签
# 识别函数
def classify(self, testfile,thickness):
mat = matplotlib.image.imread(testfile) # 读取所写图像
mat = 1 - (mat[:, :, 0]+mat[:, :, 1]+mat[:, :, 2])/3 # 将图像反转
image = pretreat.imgtreat(mat) # 实例化图像处理类
self.testfeature = image.pretreatment(thickness) # 图像预处理,提取进行识别的图像的特征矩阵
c = pd.DataFrame(self.testfeature)
c.to_csv("test/testpicture_feature.csv", index=False, header=False) # 保存特征矩阵
self.trainfeaturemat = pd.read_csv("test/FeatureMat.csv", header=1) # 读取训练的特征矩阵
labels = pd.read_csv("test/labels.csv")
self.trainrows = self.trainfeaturemat.shape[0] # 获取训练结果行数
[m, n] = self.testfeature.shape
self.testfeature = self.testfeature.reshape((1, m * n))
distances = self.dis(self.testfeature, self.trainfeaturemat)
q = distances.argsort() # 按照距离从低到高排位,返回的是索引
classCount = {}
# 依次取出最近的样本数据
for i in range(self.k): # 根据我们的k来统计出现频率,样本类别
votelabel = labels.loc[q[i]][0] # q[i]是索引值,通过labels来获取对应标签
classCount[votelabel] = classCount.get(votelabel, 0) + 1 # 统计每个标签的次数
# 对类别出现的频率次数排序,从高到低
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回频率次数出现最高的类别
return sortedClassCount[0][0]
def dis(self, testdata, traindata):
difference = np.tile(testdata, (self.trainrows, 1)) - traindata # 求差
distances = np.sqrt(np.sum(np.square(difference),axis=1)) # 求横向欧式距离
return distances