Python实现识别手写数字 Python图片读入与处理


Posted in Python onMarch 23, 2020

写在前面

在上一篇文章Python徒手实现手写数字识别—大纲中,我们已经讲过了我们想要写的全部思路,所以我们不再说全部的思路。

我这一次将图片的读入与处理的代码写了一下,和大纲写的过程一样,这一段代码分为以下几个部分:

  • 读入图片;
  • 将图片读取为灰度值矩阵;
  • 图片背景去噪;
  • 切割图片,得到手写数字的最小矩阵;
  • 拉伸/压缩图片,得到标准大小为100x100大小矩阵;
  • 将图片拉为1x10000大小向量,存入训练矩阵中。

所以下面将会对这几个函数进行详解。

代码分析

基础内容

首先我们现在最前面定义基础变量

import os
from skimage import io
import numpy as np

##Essential vavriable 基础变量
#Standard size 标准大小
N = 100
#Gray threshold 灰度阈值
color = 100/255

其中标准大小指的是我们在最后经过切割、拉伸后得到的图片的尺寸为NxN。灰度阈值指的是在某个点上的灰度超过阈值后则变为1.

接下来是这图像处理的一部分的主函数

filenames = os.listdir(r"./num/")
pic = GetTrainPicture(filenames)

其中filenames得到在num目录下所有文件的名称组成的列表。pic则是通过函数GetTrainPicture得到所有训练图像向量的矩阵。这一篇文章主要就是围绕这个函数进行讲解。

GetTrainPicture函数

GetTrainPicture函数内容如下

#Read and save train picture 读取训练图片并保存
def GetTrainPicture(files):
 Picture = np.zeros([len(files), N**2+1])
 #loop all pictures 循环所有图片文件
 for i, item in enumerate(files):
 #Read the picture and turn RGB to grey 读取这个图片并转为灰度值
 img = io.imread('./num/'+item, as_grey = True)
 #Clear the noise 清除噪音
 img[img>color] = 1
 #Cut the picture and get the picture of handwritten number
 #将图片进行切割,得到有手写数字的的图像
 img = CutPicture(img)
 #Stretch the picture and get the standard size 100x100
 #将图片进行拉伸,得到标准大小100x100
 img = StretchPicture(img).reshape(N**2)
 #Save the picture to the matrix 将图片存入矩阵
 Picture[i, 0:N**2] = img
 #Save picture's name to the matrix 将图片的名字存入矩阵
 Picture[i, N**2] = float(item[0])
 return Picture

可以看出这个函数的信息量非常大,基本上今天做的所有步骤我都把封装到一个个函数里面了,所以这里我们可以看到图片处理的所有步骤都在这里。

提前准备

首先是创建了一个用来存放所有图像向量的矩阵Picture,大小为fx10001,其中f代表我们拥有的训练图片的数目,10001的前10000位代表图片展开后的向量长度,最后一维代表这一个向量的类别,比如说时2就代表这个图片上面写的数字是2.

接下来用的是一个for循环,将files里面每一个图片进行一次迭代,计算出向量后存入picture。

在循环中的内容就是对每一张图片进行的操作。

读入图片并清除背景噪音

首先是io.imread函数,这个函数是将图片导出成为灰度值的矩阵,每一个像素点是矩阵上的一个元素。

接下来是img[img>color]=1这一句。这一句运用了逻辑运算的技巧,我们可以将其分为两部分

point = img > color
img[point] = 1

首先是img>color,img是一个矩阵,color是一个数。意义就是对img中所有元素进行判断是否大于color这个数,并输出一个与img同等大小的矩阵,对应元素上是该值与color判断后的结果,有False与True。如果大于这个数,那么就是Ture,否则是False。下面举个例子,不再赘述。

a = np.array([1, 2, 3, 4])
print(a>2)

#以下为输出结果
[False False True True]

之后的img[point] = 1说明将所有True的值等于1。举个例子

a = np.array([1, 2, 3, 4])
p = a > 2
a[p] = 0
print(a)

#以下为输出结果
[1 2 0 0]

因此我通过这样的方法来清除掉了与数字颜色差别太大的背景噪音。

切割图像

首先切割图像的函数我写的是CutPicture。我们来说一下这个切割图像的意思。比如说有一个人写字写的特别小,另一个人写字写的特别大。就像是下图所示,所以我们进行这样的操作。沿着图片的边进行切割,得到了下面切割后的图片,让数字占满整个图片,从而具有可比性。

Python实现识别手写数字 Python图片读入与处理

所以下面贴出代码,详细解释一下我是怎么做的。

#Cut the Picture 切割图象
def CutPicture(img):
 #初始化新大小
 size = []
 #图片的行数
 length = len(img)
 #图片的列数
 width = len(img[0,:])
 #计算新大小
 size.append(JudgeEdge(img, length, 0, [-1, -1]))
 size.append(JudgeEdge(img, width, 1, [-1, -1]))
 size = np.array(size).reshape(4)
 print(size)
 return img[size[0]:size[1]+1, size[2]:size[3]+1]

首先函数导入过来的的参数只有一个原img。

我第一步做的是把新的大小初始化一下,size一共会放入四个值,第一个值代表原图片上的手写数字图案的最高行,第二个值代表的是最低行,第三个值代表数字图案的最左列,,第四个只代表最右列**。这个还看不明白的话就看上面的图示,就是沿着图片切割一下就好了。

接下来的length和width分别代表着原图片的行数与列数,作用在下面。我又创建了一个JudgeEdge函数,这个函数是输出它的行数或者列数的两位数字。第一个append是给size列表放入了两个行序号(最高行和最低行),第二个append是给size放进两个列序号(最左列和最右列)。所以接下来就看JudgeEdge函数是干什么的。

def JudgeEdge(img, length, flag, size):
 for i in range(length):
 #Cow or Column 判断是行是列
 if flag == 0:
  #Positive sequence 正序判断该行是否有手写数字
  line1 = img[img[i,:]<color]
  #Negative sequence 倒序判断该行是否有手写数字
  line2 = img[img[length-1-i,:]<color]
 else:
  line1 = img[img[:,i]<color]
  line2 = img[img[:,length-1-i]<color]
 #If edge, recode serial number 若有手写数字,即到达边界,记录下行
 if len(line1)>=1 and size[0]==-1:
  size[0] = i
 if len(line2)>=1 and size[1]==-1:
  size[1] = length-1-i
 #If get the both of edge, break 若上下边界都得到,则跳出
 if size[0]!=-1 and size[1]!=-1:
  break
 return size

JudgeEdge函数的参数flag就是用来判断是行还是列,当flag=0时,说明以行为基础开始循环;当flag=1时说明以列为基础进行循环。所以参数length传递的时候就是行数和列数。接下来的for循环就是根据length的大小看似是循环。

当是行时,我这里用line1和line2起到的是一个指针的作用,即在第i行时,line1的内容就是这一行拥有非白色底(数值为1)的像素的个数;line2的作用则是反序的,也就是他计算的是倒数i行非白色像素个数,这样做的目的是能够快一点,让上下同时开始进行寻找,而不用line1把整个图片循环一遍,line2把整个图片循环一遍,大大节省了时间。

寻找这一行拥有非白色底的像素的个数这一个语句同样的运用了逻辑判断,和上文中去噪的原理一模一样。

当line里面有数的时候,说明已经到达了手写数字的边缘。这时候就记录下来,然后就不再改变。当两个line都不等于初始值-1时,说明已经找到了两个边缘,这时候就可以跳出循环并且return了。

这个函数就是这样。所以说切割图像的完整代码就是这样子,下面就要把切割的大小不一的图像给拉伸成标准大小100x100了。

拉伸图像

因为切割以后的图像有大有小,一张图片的大小可能是21x39(比如说数字2),另一张可能是4x40(比如说数字1)。所以为了能够让他们统一大小称为100x100,我们就要把他们拉伸一下。

Python实现识别手写数字 Python图片读入与处理

大概就是像图上的一样。实际情况的图案可能会更复杂,所以我们下面展示一下代码

#Stretch the Picture 拉伸图像
def StretchPicture(img):
 newImg = np.ones(N**2).reshape(N, N)
 ##Stretch/Compress each cows/columns 对每一行/列进行拉伸/压缩
 #The length of each cows after stretching 每一行拉伸/压缩的步长
 step1 = len(img[0])/100
 #Each columns blabla 每一列拉伸/压缩的步长
 step2 = len(img)/100
 #Operate on each cows 对每一行进行操作
 for i in range(len(img)):
 for j in range(N):
  newImg[i, j] = img[i, int(np.floor(j*step1))]
 #Operate on each columns 对每一列进行操作
 for i in range(len(img[0])):
 for j in range(N):
  newImg[j, i] = img[int(np.floor(j*step2)), i]
 return newImg

首先初始化一个新的图片矩阵,这个大小就是标准大小100x100。接下来才是重头戏。我这里用的方法是比较简单基础的方法,但是可能依旧比较难。

首先定义两个步长step1和step2,分别代表拉伸/压缩行与列时的步长。这里的原理就是把原来的长度给他平均分成100份,然后将这100个像素点分别对应上原本的像素点。

如下图所示,图像1我们假设为原图像,图像2我们假设为标准图像,我们需要把图像1转化为图像2,其中每一个点代表一个像素点,也就是图像1有五个像素点,图像2有四个像素点。

Python实现识别手写数字 Python图片读入与处理

所以我的思想就是直接让图像2的像素点的值等于距离它最近的图像1的像素点。

我们为了方便起见,在这里定义一个语法:图像2第三个数据点我们可以写为2_3.

所以2_1对应的就是1_1,2_2对应的就是1_2,2_3对应的是1_4,2_4对应的是1_5。就这样我们就能够得到了图像2所有的数据点。

利用数学的形式表现出来,就是假设图像1长度为l_1,图像2长度为l_2,所以令图像2的步长为l_1/l_2,也就是说当图像2的第一个像素点对应图像1第一个像素点,图像2的最后一个像素点对应图像1最后一个像素点。然后图像2第二个像素点位置就是2*l_1/l_2,对应图像1第floor(2*l_1/l_2)个像素点。以此类推就行。因此再回头看一下那一段代码,这一段是不是就好理解了?

之后对行与列分别进行这个操作,所以就可以得到标准的图片大小。然后再返回到GetTrainPicture即可。

再GetTrainPicture函数中,我用了reshape函数把原本100x100大小的图片拉伸成为1x10000大小的向量,然后存入矩阵当中,并将这一张图片的类别存入矩阵最后一个。

以上就是图片处理的所有内容。

小结

以上就是把一张图片经过处理后存入矩阵的内容。

本文已被收录到专题《python图片处理操作》 ,欢迎大家点击学习更多精彩内容。

本文中的所有算法、代码均是我自己构思的,所以可能存在一些不足之处,我没有系统的学习过图像的相关知识,也并不是计算机专业,因此可能在理论上有一些不合乎情况,所以如果有错误的话欢迎一起讨论,谢谢。

Python 相关文章推荐
Python入门篇之文件
Oct 20 Python
讲解Python中for循环下的索引变量的作用域
Apr 15 Python
详细介绍Python的鸭子类型
Sep 12 Python
Python查找第n个子串的技巧分享
Jun 27 Python
使用Python实现租车计费系统的两种方法
Sep 29 Python
浅谈Python的list中的选取范围
Nov 12 Python
使用Python3内置文档高效学习以及官方中文文档
May 19 Python
详解Django CAS 解决方案
Oct 30 Python
学python最电脑配置有要求么
Jul 05 Python
python 监控logcat关键字功能
Sep 04 Python
如何完美的建立一个python项目
Oct 09 Python
python openCV自制绘画板
Oct 27 Python
Python实现PS滤镜特效Marble Filter玻璃条纹扭曲效果示例
Jan 29 #Python
Python实现识别手写数字大纲
Jan 29 #Python
django文档学习之applications使用详解
Jan 29 #Python
Python实现PS滤镜Fish lens图像扭曲效果示例
Jan 29 #Python
python实现识别手写数字 python图像识别算法
Mar 23 #Python
Python实现简易版的Web服务器(推荐)
Jan 29 #Python
python实现图像识别功能
Jan 29 #Python
You might like
php实现网站插件机制的方法
2009/11/10 PHP
ThinkPHP中url隐藏入口文件后接收alipay传值的方法
2014/12/09 PHP
PHP 实现代码复用的一个方法 traits新特性
2015/02/22 PHP
PHP实现的DES加密解密实例代码
2016/04/06 PHP
PHP编程实现微信企业向用户付款的方法示例
2017/07/26 PHP
Chrome中JSON.parse的特殊实现
2011/01/12 Javascript
拖动table标题实现改变td的大小(css+js代码)
2013/04/16 Javascript
zTree插件之单选下拉菜单实例代码
2013/11/07 Javascript
jQuery中val()方法用法实例
2014/12/25 Javascript
js实现模拟计算器退格键删除文字效果的方法
2015/05/07 Javascript
使用Browserify配合jQuery进行编程的超级指南
2015/07/28 Javascript
强大的 Angular 表单验证功能详细介绍
2017/05/23 Javascript
JavaScript算法教程之sku(库存量单位)详解
2017/06/29 Javascript
原生JS封装_new函数实现new关键字的功能
2018/08/12 Javascript
JavaScript定时器设置、使用与倒计时案例详解
2019/07/08 Javascript
利用JS如何获取form表单数据
2019/12/19 Javascript
解决vue+webpack项目接口跨域出现的问题
2020/08/10 Javascript
python3中dict(字典)的使用方法示例
2017/03/22 Python
Python 查看文件的读写权限方法
2018/01/23 Python
Python实现定时备份mysql数据库并把备份数据库邮件发送
2018/03/08 Python
Python中XlsxWriter模块简介与用法分析
2018/04/24 Python
Python 删除整个文本中的空格,并实现按行显示
2018/07/24 Python
python for和else语句趣谈
2019/07/02 Python
python 调试冷知识(小结)
2019/11/11 Python
tensorflow tf.train.batch之数据批量读取方式
2020/01/20 Python
python小程序基于Jupyter实现天气查询的方法
2020/03/27 Python
jupyter notebook读取/导出文件/图片实例
2020/04/16 Python
Python3爬虫里关于识别微博宫格验证码的知识点详解
2020/07/30 Python
施华洛世奇天猫官方旗舰店:SWAROVSKI
2017/04/17 全球购物
全球销量第一生发产品:Viviscal
2017/12/21 全球购物
金融专业推荐信
2013/11/14 职场文书
土地转让协议书
2014/04/15 职场文书
班主任工作经验交流材料
2014/05/13 职场文书
公司周年庆典标语
2014/10/07 职场文书
教师工作总结范文2014
2014/11/10 职场文书
2015年乡镇统计工作总结
2015/04/22 职场文书