Python3生成手写体数字方法


Posted in Python onJanuary 30, 2018

0.引言

平时上网干啥的基本上都会接触验证码,或者在机器学习学习过程中,大家或许会接触过手写体识别/验证码识别之类问题,会用到手写体的数据集;

自己尝试写了一个生成手写体图片的python程序,在此分享下生成单张 30*30像素的手写体数字1-9图像 的一种实现方法;

我是利用random生成随机数1-9,然后PIL写到图像上,然后经过旋转、扭曲处理,得到“手写体”,这里没有加干扰线和干扰点;

得到的手写体数字图像如图1所示;

实现比较简单,用了PIL库,不需要额外安装opencv啥的,有兴趣可以自己试试。

Python3生成手写体数字方法

图1 生成的手写体数字1-9

Python3生成手写体数字方法

图2 利用generate_pngs.py写入到文件夹3的数字3图像

如果你想生成手写体的字母/汉字也可以:

Python3生成手写体数字方法Python3生成手写体数字方法

图3 利用generate_single_png.py生成汉字的手写体

源码上传到了我的GitHub: https://github.com/coneypo/Generate_handwritten_number

1.设计流程

Python3生成手写体数字方法

图4 整体设计流程

Python3生成手写体数字方法

图5 生成的图像经过的处理

1.1 新建一个空白图像img_50,尺寸大小为50*50 

img_50_blank = Image.new('RGB', (50, 50), (255, 255, 255))

为什么我这里要先生成50*50的空白图像?

因为图像背景(50*50像素的画布)初始化的时候设置为白色(颜色数组(255, 255, 255)),而背景色之外的其实是黑色;

之后需要进行旋转处理,如果直接新建30*30像素的画布,旋转之后边上会出现黑边,如图6所示;

所以我新建了一个50*50,然后旋转之后从中间裁出来一个30*30的图像出来;

Python3生成手写体数字方法

图6 直接用30*30像素的画布写字旋转(会出现黑边)

1.2 利用PIL在图像上写文字

利用PIL的ImageDraw,创建画笔,然后利用draw.text在指定位置写字;

xy=(18,11)是从图像左上角开始的坐标,取值自己根据需求调整;

# 创建画笔
 draw = ImageDraw.Draw(img_50_blank)

# 生成随机数1-9
num = str(random.randint(1, 9))

# 设置字体,这里选取字体大小25
font = ImageFont.truetype('simsun.ttc', 20)

# xy是左上角开始的位置坐标
draw.text(xy=(18, 11), font=font, text=num, fill=(0, 0, 0))

1.3 将图像随机旋转一定角度

利用 rotate(angel) 进行旋转图像,angel取的是度数,这里让它随机旋转-10到+10度:

# 随机旋转-10-10角度
 random_angle = random.randint(-10, 10)
 img_50_rotated = img_50_blank.rotate(random_angle)

1.4 图像扭曲

这里是生成“手写体”数字的核心步骤,一个正常的图像经过扭曲之后就可以得到想要的验证码了:

# 图形扭曲参数
params = [1 - float(random.randint(1, 2)) / 100,
      0,
      0,
      0,
      1 - float(random.randint(1, 10)) / 100,
      float(random.randint(1, 2)) / 500,
      0.001,
      float(random.randint(1, 2)) / 500]

# 创建扭曲
img_50_transformed = img_50_rotated.transform((50, 50), Image.PERSPECTIVE, params)

2.py源码介绍

2.1 generate_folders_1to9.py

因为我们要将指定的图像分类放入指定文件夹,所以我们需要先在项目目录下面新建9个文件夹:

 (当然你也可以自己新建,新建9个文件夹工作量还不大,但是如果要生成的验证码包含英文字母那就比较多了,大写A-Z共24个+小写a-z共24个+数字1-9共9个=57个子文件夹)

# 2018-01-9
# By TimeStamp
# cnblogs: http://www.cnblogs.com/AdaminXie/
# generate_folders_1to9.py
# 在目录下生成用来存放数字1-9的9个文件夹,分别用1-9命名


import os

path_folders = "F:/***/P_generate_handwritten_number/data_pngs/"

# 1-9
for i in range(49,58):
  if (os.path.isdir(path_folders + chr(i))):
    pass
  else:
    # print(i,": ",path_1+chr(i))
    # 生成目录
    os.mkdir(path_folders+chr(i))

Python3生成手写体数字方法

图7 自动生成的用来存放指定图像的文件夹

2.2 generate_pngs.py

根据给定随机次数samples, 生成samples个手写体数字1-9,然后存放到本地文件夹1-9生成数据集;

在49行可以修改生成图像的大小,我这里取的是30*30像素;

# 2018-01-9
# By TimeStamp
# cnblogs: http://www.cnblogs.com/AdaminXie/
# generate_pngs.py
# 生成手写体数字


import random
from PIL import Image, ImageDraw, ImageFilter, ImageFont

random.seed(3)

# 生成单张扭曲的数字图像
def generate_single():

  # 先绘制一个50*50的空图像
  img_50_blank = Image.new('RGB', (50, 50), (255, 255, 255))

  # 创建画笔
  draw = ImageDraw.Draw(img_50_blank)

  # 生成随机数1-9
  num = str(random.randint(1, 9))

  # 设置字体,这里选取字体大小25
  font = ImageFont.truetype('simsun.ttc', 20)

  # xy是左上角开始的位置坐标
  draw.text(xy=(18, 11), font=font, text=num, fill=(0, 0, 0))

  # 随机旋转-10-10角度
  random_angle = random.randint(-10, 10)
  img_50_rotated = img_50_blank.rotate(random_angle)

  # 图形扭曲参数
  params = [1 - float(random.randint(1, 2)) / 100,
       0,
       0,
       0,
       1 - float(random.randint(1, 10)) / 100,
       float(random.randint(1, 2)) / 500,
       0.001,
       float(random.randint(1, 2)) / 500]

  # 创建扭曲
  img_50_transformed = img_50_rotated.transform((50, 50), Image.PERSPECTIVE, params)

  # 生成新的30*30空白图像,(在此处可以更改生成的图像大小)
  img_30 = img_50_transformed.crop([10, 10, 40, 40])

  return img_30, num

path_pic = "F:/***/P_generate_handwritten_number/data_pngs/"


# 生成手写体数字1-9存入指定文件夹1-9

# 用cnt_num[1]-cnt_num[9]来计数数字1-9生成的个数,方便之后进行命名
cnt_num = []
for i in range(10):
  cnt_num.append(0)

# 生成次数
samples = 200

for m in range(1, samples+1):

  # 调用生成图像文件函数
  img, generate_num = generate_single()

  # 取灰度
  imgray = img.convert('1')

  # 计数生成的数字1-9的个数,用来命名图像文件
  for j in range(1, 10):
    if(generate_num == str(j)):
      cnt_num[j] = cnt_num[j]+1

      # 路径如 "F:/code/***/P_generate_handwritten_number/data_pngs/1/1_231.png"
      # 输出显示路径
      print(path_pic + str(j) + "/" + str(j) + "_" + str(cnt_num[j]) + ".png")
      # 将图像保存在指定文件夹中
      imgray.save(path_pic + str(j) + "/" + str(j) + "_" + str(cnt_num[j]) + ".png")

# 输出显示1-9的分布
print("\n", "生成的1-9的分布:")
for k in range(9):
  print(k+1, ":", cnt_num[k+1], "张")

output

D:\***\anaconda\python.exe F:/***/P_generate_handwritten_number/generate_pngs.py
F:/***/P_generate_handwritten_number/data_pngs/4/4_1.png
F:/***/P_generate_handwritten_number/data_pngs/1/1_1.png
F:/***/P_generate_handwritten_number/data_pngs/8/8_1.png
F:/***/P_generate_handwritten_number/data_pngs/3/3_1.png
F:/***/P_generate_handwritten_number/data_pngs/1/1_2.png
...

生成的1-9的分布:
: 25 张
: 17 张
: 21 张
: 19 张
: 20 张
: 22 张
: 25 张
: 24 张
: 27 张

修改 generate_pngs.py中的samples, 你就可以生成指定大小的数据集;

2.3 generate_single_png.py

更改27行的char=" "(可以是数字/字母/汉字),生成单张手写体扭曲图像:

# 2018-01-9
# By TimeStamp
# cnblogs: http://www.cnblogs.com/AdaminXie/
# generate_single_png.py
# 生成手写体数字/字母/汉字


import random
from PIL import Image, ImageDraw, ImageFilter, ImageFont

random.seed(3)

# 生成单张扭曲的数字图像
def generate_single():

  # 先绘制一个50*50的空图像
  img_50_blank = Image.new('RGB', (50, 50), (255, 255, 255))

  # 创建画笔
  draw = ImageDraw.Draw(img_50_blank)

  # 设置字体,这里选取字体大小25
  font = ImageFont.truetype('simsun.ttc', 20)

  # xy是左上角开始的位置坐标
  # text是你想要显示的内容,数字/字母/汉字
  char ="呵"
  draw.text(xy=(12, 11), font=font, text=char, fill=(0, 0, 0))

  # 随机旋转-10-10角度
  random_angle = random.randint(-10, 10)
  img_50_rotated = img_50_blank.rotate(random_angle)

  # 图形扭曲参数
  params = [1 - float(random.randint(1, 2)) / 100,
       0,
       0,
       0,
       1 - float(random.randint(1, 10)) / 100,
       float(random.randint(1, 2)) / 500,
       0.001,
       float(random.randint(1, 2)) / 500]

  # 创建扭曲
  img_50_transformed = img_50_rotated.transform((50, 50), Image.PERSPECTIVE, params)

  # 生成新的30*30空白图像
  img_30 = img_50_transformed.crop([10, 10, 40, 40])

  return img_30, char

path_pic = "F:/code/python/P_generate_handwritten_number/"


# 调用生成图像文件函数
img, generated_char = generate_single()
imgray = img.convert('1')

print(path_pic + "test.png")
# 将图像保存在指定文件夹中
imgray.save(path_pic + "test.png")

2.4 del_pngs.py

删除指定目录下子文件夹1-9中的所有图片:

# 2018-01-9
# By TimeStamp
# cnblogs: http://www.cnblogs.com/AdaminXie/
# del_pngs.py
# 删除路径下生成的图像文件

import os

path_pic = "F:/***/P_generate_handwritten_number/data_pngs/"

#删除路径下的图片
def del_pic():
  for i in range(1, 10):
   #  print(path_png+chr(i))
    namedir = os.listdir(path_pic+str(i))

    for tmppng in namedir:
      if( tmppng in namedir):
       # print(tmppng)
        os.remove(path_pic+str(i)+"/"+tmppng)

del_pic()

3.总结

自己动手丰衣足食,有兴趣可以自己做手写体数字数据集,字母和汉字的数据集稍加修改也可以做;

# GitHub: https://github.com/coneypo/Generate_handwritten_number

Python 相关文章推荐
python中wx将图标显示在右下角的脚本代码
Mar 08 Python
初步认识Python中的列表与位运算符
Oct 12 Python
Python搜索引擎实现原理和方法
Nov 27 Python
Python通过调用有道翻译api实现翻译功能示例
Jul 19 Python
python 多线程重启方法
Feb 18 Python
Python中__repr__和__str__区别详解
Nov 07 Python
python给指定csv表格中的联系人群发邮件(带附件的邮件)
Dec 31 Python
python闭包、深浅拷贝、垃圾回收、with语句知识点汇总
Mar 11 Python
python实现控制台输出彩色字体
Apr 05 Python
详解Java中一维、二维数组在内存中的结构
Feb 11 Python
Tensorflow与RNN、双向LSTM等的踩坑记录及解决
May 31 Python
关于Python中*args和**kwargs的深入理解
Aug 07 Python
python字符串的方法与操作大全
Jan 30 #Python
Python实现带参数与不带参数的多重继承示例
Jan 30 #Python
Python实现的随机森林算法与简单总结
Jan 30 #Python
Python决策树和随机森林算法实例详解
Jan 30 #Python
在Python 2.7即将停止支持时,我们为你带来了一份python 3.x迁移指南
Jan 30 #Python
python使用Tkinter实现在线音乐播放器
Jan 30 #Python
Python字典及字典基本操作方法详解
Jan 30 #Python
You might like
smarty 缓存控制前的页面静态化原理
2013/03/15 PHP
TP(thinkPHP)框架多层控制器和多级控制器的使用示例
2018/06/13 PHP
Prototype使用指南之range.js
2007/01/10 Javascript
Add a Picture to a Microsoft Word Document
2007/06/15 Javascript
javascript学习(二)javascript常见问题总结
2013/01/02 Javascript
javascript使用location.search的示例
2013/11/05 Javascript
AngularJS入门教程之ng-class 指令用法
2016/08/01 Javascript
RGB和YUV 多媒体编程基础详细介绍
2016/11/04 Javascript
AngularJS中指令的四种基本形式实例分析
2016/11/22 Javascript
jQuery Checkbox 全选 反选的简单实例
2016/11/29 Javascript
JavaScript的变量声明提升问题浅析(Hoisting)
2016/11/30 Javascript
详解Vue.js动态绑定class
2016/12/20 Javascript
Vue2.0+Vux搭建一个完整的移动webApp项目的示例
2019/03/19 Javascript
Async/Await替代Promise的6个理由
2019/06/15 Javascript
layui 数据表格+分页+搜索+checkbox+缓存选中项数据的方法
2019/09/21 Javascript
JS插入排序简单理解与实现方法分析
2019/11/25 Javascript
js实现中文实时时钟
2020/01/15 Javascript
使用typescript快速开发一个cli的实现示例
2020/12/09 Javascript
[06:53]2018DOTA2国际邀请赛寻真——为复仇而来的Newbee
2018/08/15 DOTA
[40:31]Secret vs Alliacne 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
JS设计模式之责任链模式实例详解
2018/02/03 Python
python爬虫基本知识
2018/03/05 Python
解决使用pycharm提交代码时冲突之后文件丢失找回的方法
2018/08/05 Python
PyCharm代码回滚,恢复历史版本的解决方法
2018/10/22 Python
在Python中构建增广矩阵的实现方法
2019/07/01 Python
利用python查看数组中的所有元素是否相同
2021/01/08 Python
使用css创建三角形 使用CSS3创建3d四面体原理及代码(html5实践)
2013/01/06 HTML / CSS
惊艳的手工时装首饰:Migonne Gavigan
2018/02/23 全球购物
The North Face北面德国官网:美国著名户外品牌
2018/12/12 全球购物
英国Office鞋店德国网站:在线购买鞋子、靴子和运动鞋
2018/12/19 全球购物
Interhome丹麦:在线预订度假屋和公寓
2019/07/18 全球购物
.NET程序员的几道面试题
2012/06/01 面试题
临床护士自荐信
2014/01/31 职场文书
化学专业自荐信
2014/05/28 职场文书
上班离岗检讨书
2014/09/10 职场文书
JavaScript展开运算符和剩余运算符的区别详解
2022/02/18 Javascript