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的常见命令注入威胁
Feb 18 Python
Python实现多行注释的另类方法
Aug 22 Python
Python中在脚本中引用其他文件函数的实现方法
Jun 23 Python
python求最大值,不使用内置函数的实现方法
Jul 09 Python
Python 通过微信控制实现app定位发送到个人服务器再转发微信服务器接收位置信息
Aug 05 Python
Pycharm 2019 破解激活方法图文详解
Oct 11 Python
python3将变量写入SQL语句的实现方式
Mar 02 Python
python 安装教程之Pycharm安装及配置字体主题,换行,自动更新
Mar 13 Python
如何利用Python动态模拟太阳系运转
Sep 04 Python
详解python定时简单爬取网页新闻存入数据库并发送邮件
Nov 27 Python
python中PyQuery库用法分享
Jan 15 Python
python实现自动清理文件夹旧文件
May 10 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
基于文本的搜索
2006/10/09 PHP
CentOS下搭建PHP环境与WordPress博客程序的全流程总结
2016/05/07 PHP
Laravel框架基于ajax和layer.js实现无刷新删除功能示例
2019/01/17 PHP
身份证号码前六位所代表的省,市,区, 以及地区编码下载
2007/04/12 Javascript
理解Javascript_06_理解对象的创建过程
2010/10/15 Javascript
JQUERY设置IFRAME的SRC值的代码
2010/11/30 Javascript
jQuery中对未来的元素绑定事件用bind、live or on
2014/04/17 Javascript
jquery JSON的解析方式示例介绍
2014/07/27 Javascript
javascript的push使用指南
2014/12/05 Javascript
javascript搜索框效果实现方法
2015/05/14 Javascript
js采用concat和sort将N个数组拼接起来的方法
2016/01/21 Javascript
Vue实现双向绑定的方法
2016/12/22 Javascript
Angular5给组件本身的标签添加样式class的方法
2018/04/07 Javascript
关于vue编译版本引入的问题的解决
2018/09/17 Javascript
使用微信小程序开发弹出框应用实例详解
2018/10/18 Javascript
在vue-cli3.0 中使用预处理器 (Sass/Less/Stylus) 配置全局变量操作
2020/08/10 Javascript
vue绑定class的三种方法
2020/12/24 Vue.js
[45:15]Optic vs VP 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
[50:38]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第二场 3月7日
2021/03/11 DOTA
python base64 decode incorrect padding错误解决方法
2015/01/08 Python
在 Python 应用中使用 MongoDB的方法
2017/01/05 Python
《与孩子一起学编程》python自测题
2018/05/27 Python
Python安装selenium包详细过程
2019/07/23 Python
python实现数据清洗(缺失值与异常值处理)
2019/12/02 Python
Python Tornado实现WEB服务器Socket服务器共存并实现交互的方法
2020/05/26 Python
伦敦剧院及景点门票:Encore Tickets
2018/07/01 全球购物
大学生毕业的自我鉴定
2013/11/13 职场文书
幼师自我鉴定
2014/02/01 职场文书
书法大赛策划方案
2014/06/04 职场文书
2014最新党员批评与自我批评材料
2014/09/24 职场文书
简单的离婚协议书范本
2014/11/16 职场文书
骨干教师申报材料
2014/12/17 职场文书
任命书怎么写
2015/03/02 职场文书
傅雷家书读书笔记
2015/06/29 职场文书
Python闭包的定义和使用方法
2022/04/11 Python
MySQL 逻辑备份 into outfile
2022/05/15 MySQL