这个是我Python课程设计的项目,原本要做的是一个能够直接识别多位数字的模型,但我对机器学习了解的不多,所以采取的是OpenCV提取数字+单位数字依次识别的方法,即利用OpenCV提取出图片中的数字,然后依次放入模型中进行识别,最后得到多位数字
其中,手写数字识别模型使用的是https://github.com/NH4L/handwrittenNumberRecognition,这个模型使用mnist数据集训练的,只能识别单位数字,模型识别率达到99.14%。
不过这个模型接收的图片格式为28×28,利用OpenCV处理得到的图片压缩成28×28时像素损失的太多,而且原模型对数字‘9’不太敏感,因此我和我队友将模型的输入格式扩大到了64×64,并且追加了4500张数据集,进一步提升了模型的准确率
因为时间比较急,我用了150张多位数字图片进行测试,然后只错了一张,正确率是99.33%
不过模型对于噪点较多的图片以及部分图片中的数字'9'还是会出现识别错误的情况(虽然我已经追加了数据集,但是有时候它还是不认识9,我也不知道为什么......;不能处理噪点过多的图片是因为我的沾染法并未成功实现!!!)
运行shorttable.py会生成一个简单的GUI页面,选择图片即可识别
运行owndata_train.py就会开始训练模型,我训练时发生过过拟合现象,训练次数超过17600次时准确率会从0.98-1突降至0.03-0.14,所以代码里设置的训练次数是17000次;训练后的模型保存在Model64_64/路径下
运行owndata_test.py可以对文件夹下所有图片依次进行识别,测试集(150张图片,测试错误的图片是298.png)已经压缩好了,运行时注意改下代码里的路径就行了
work/test01.py就是利用OpenCV提取数字的全部代码,test02.py可以忽略
work/npy_dataset.py是利用图片生成npy格式数据集的代码,这段代码的作用很多
-
因为我要把原本28×28的模型扩充成64×64,那么原本Mnist数据集的格式也需要改变,我就是用work/npy_dataset.py对Mnist数据集中的图片先用单线性插值法放大成64×64的图片,然后再制成npy格式的数据集
-
有些读者看到npy_dataset.py中的内容可能会不理解,因为我最终制成的数据集格式是【4096,1】,而不是【64,64】。这个的原因是我本身不了解机器学习,但是课设还得做,所以直接用的别人的模型https://github.com/NH4L/handwrittenNumberRecognition,这位大佬又直接把训练代码放上去了,所以我也直接用他的代码训练的。
他的训练代码中用的是【786,1】(786=28×28),我不知道怎么换成【28,28】进行训练,所以就强行把数据集制成【4096,1】的格式直接用了......
work/Method.py是训练模型用到的代码,作用就是从数据集中随机选取m个数据进行训练,因为Mnist数据集自带的有next_batch,但是自制数据集肯定没有,我就用它代替的next_batch(这段代码是直接粘贴的,作者是批量读取数据next_batch()的简单函数实现_深度强化学习(DeepRL)探索博客-CSDN博客
- Python 3.6.5
- TensorFlow 1.14
- Numpy 1.16.5
OpenCV-Python库功能强大,具有很多图像处理方面的通用算法。结合模型支持的图片格式,小组将图片预处理分为灰度化、二值化、膨胀、降噪、提取数字、居中、清除模糊。详细流程如图所示。
当我们拿到一种图片的时候,这张图片可能是多种颜色集合在一起的,而我们为了方便处理这张图片,我们首先会将这张图片灰度化。图片灰度化之前这张图片的像素值是三维数组,灰度化之后将变为二维数组5。
图片的二值化就是将灰度化得到的图片数组化为0和1的形式6。转化方法有多种,其中一种常用的方法是设定一个阈值7,大于这个阈值的像素点我们将其设置为1,小于这个阈值的像素点我们将其设置为0。
但是鉴于不同图片对应的最佳阙值往往不同,小组采用的是OpenCV库中的自适应阙值图形算法adaptiveThreshold,它可以根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值8。
数字0-9中,数字5比较特别,因为我们在手写‘5’的时候上面的横可能会和‘5’的身体部分分开,这就容易导致OpenCV在提取轮廓时将一个数字‘5’提取成两个轮廓。
图像的膨胀操作是取每一个位置的矩形领域内值的最大值作为该位置的输出灰度值。不同的是,这里的领域不再单纯是矩形结构的,也可以是椭圆形结构的、十字交叉形结构的等,其中红色是参考点,也称为锚点(anchor point)。
在OpenCV中,我们可以自定义锚点,实现自定义膨胀效果。小组为限制锚点的范围,将图片压缩成200×200的格式,一方面可以在大致保全数字的情况下简化计算,另一方面可以限制锚点范围不能过大。
二值化后的图片符合我们的计算需求,不过有些图片在二值化后会出现很多噪点,这些噪点也会干扰我们提取数字。由于图片中噪点的大小各不相同,小组采用了沾染法+轮廓面积降噪法进行降噪。沾染法适合清除面积较大的噪点,轮廓面积降噪法适合处理面积较小的噪点。
在计算的过程中,每扫描到一个黑色(灰度值为0)的点,就使用OpenCV中的floodFill方法将与该点连通的所有点的灰度值都改为1,因此这一个连通域的点都不会再次重复计算了。继续遍历所有点,下一个灰度值为0的点所有连通点的颜色都改为2,这样依次递加,知道所有的点都扫描完10。
接下来再次扫描所有的点,统计每一个灰度值对应的点的个数,每一个灰度值的点的个数对应该连通域的大小,并且不同连通域由于灰度值不同,因此每个点只计算一次,不会重复。这样一来就统计到了每个连通域的大小,设置阙值为20,如果该连通域大小小于20,则判断为噪点,将之消除。
经过沾染法处理的图片已经消除了大面积结点,大图片中仍可能存在细小零碎的噪点。这时候可以利用OpenCV中的findContour方法识别出图片中的所有轮廓,然后利用contourArea方法求出每个轮廓的面积,设置阙值为20,如果轮廓的大小小于20,则判断为噪点,将之消除。
清除噪点后,此时图片中的轮廓就是数字,利用OpenCV中的findContour方法即可提取出图片中所有数字的轮廓信息,并可根据轮廓信息求出数字的外界矩形和具体位置。
不过,考虑到数字之间可能出现距离较近的情况,直接使用外界矩形截取图形可能会使图片中出现干扰像素。小组采取的办法是生成一张和数字轮廓外界矩形等同大小的新图片,然后用for循环将轮廓中的每个点对应的像素值复制到新图片中,实现数字的高精度提取。
提取出数字之后还不能直接使用,因为Mnist数据集中使用的图片数字都是居中的,而我们提取出的数字图片中数字是刚好填充满整个图片的,这样的图片直接输入模型的话识别率很低。因此要对数字进行居中处理,小组采取的方法是:
建立一张新的正方形图片(因为模型需要图片的格式是64*64),这个正方形图片的长度以数字图片长宽的最大值为准。创建好图片后,使用for循环将数字图片中的像素复制到正方形图片中,然后再缩放为64×64的格式。
图片缩放之后难免会有像素损失,这也会干扰模型的识别率。这里我们采取的是比较简单的处理方法,我们设置了阙值200,然后遍历图片,如果像素点的灰度值大于阙值,则转换为255;反之转换为0
Mnist是目前网络上能找到的手写数字最好的数据来源,它最初来源于NIST(National Institute of Standards and Technology,美国国家标准与技术研究院)数据库,后经过预处理成为更适合机器学习算法使用的Mnist。
Mnist由60000个训练集和10000个测试集组成,其中每个数字是28x28的一幅灰度图,每个手写数字都被归一化到20x20的框内(并非每个数字都被拉伸为20x20,只是保持之前手写的比例将其缩放到20x20的框内),每个数字都被放到了28x28的图像中央。
不同于经典的多层全连接神经网络,对手写体图片进行识别时,会将每个像素点数据分别读入,哪怕是28*28像素,一个通道的灰度图片,也具有784个点,需要使用CNN(卷积神经网络)模型进行多次卷积,提取特征点;经过池化,特征降维,压缩数据和参数的数量,减小过拟合,以提高效率,最后再进行全连接。
CNN网络一共有5个层级结构:输入层,卷积层,激活层,池化层,全连接层。可以有多个卷积层与池化层,提高识别精度。
输入数据分别有两部分,x为各图片的像素点的灰度值张量,y为图片所对应的0-9的数字张量,格式框架如下:
x= tf.placeholder('float', shape=[None,image_size])
y= tf.placeholder(' float', shape=[None,labels_count])
其中,None为每个输入的batch的长度,image_size为图片大小即28*28,labels_count为0-9十个数。Lables是每张图片所对应的数字,但需要转化为独热码(one-hot编码),使得各lable之间不存在数学联系,以免神经网络在学习的过程中出现不想要出现的错误结果2。
在卷积层中,需要使用卷积核对输入数据进行卷积操作,提取特征值,输出特征图。对于一个输入数据可以产出多张特征图,n个特征图就提取了这个图片的n维特征。卷积过程的计算与输出如下图所示:
激活层把卷积层的结果做非线性映射。所谓激活,实际上是对卷积层的输出结果做一次非线性映射。如若不加激活层,则从输入层到全连接层之间经过的都是线性关系,与现实实际情况不符,对准确度有一定程度的影响。
在卷积层卷积提取特征图之后,特征图仍然过大。池化层的意义主要就是用于特征降维,压缩数据和参数的数量,减小过拟合,同时提高模型的容错性。经过多层卷积层与池化层(本项目采用了双层),即可较大程度压缩数据大小3。
池化的计算过程:filter分别取出特征图中范围内的最大值,如下图所示:
池化后,将池化层输出的张量resharp成一个向量,供全连接层使用。
在全连接层中,接受池化层输出的向量,输出我们想要的结果:0-9的概率。但此时得到的数字并不能算是严格意义上的概率,还需经过softmax,转化为真正的概率。在全连接层,考虑到神经元数目过大以及设备的问题,为了提升速度,采用了dropout操作,随机删除神经网络中的部分神经元。
在全连接层的操作,其实就是经典的y=w*x+b。
在每次迭代中,都会输出一个loss(损失函数)。本项目的loss函数采用的是经典的交叉熵损失函数。
在训练中,给定一个步长(本项目采用步长为1e-4),每次迭代都喂入一个batch,使用Adam下降算法,逐渐获得较为合适的各w和b。
在此过程中,每经过一次迭代,都会使用测试数据集测试输出精度,可以很可观的看出精度在迭代中不断地上升。