Python3生成手写体数字方法
本文介绍一种使用Python3和PIL库生成手写体数字1-9图像的方法。通过随机生成数字、旋转及扭曲处理,模拟真实手写效果,无需额外安装OpenCV。文章详细解析了从空白图像创建、文字绘制到最终裁剪的完整流程,并提供相关Python源码供参考。
0.引言
日常上网时,验证码是常见的存在;在机器学习的学习过程中,手写体识别或验证码识别也是常见课题,通常需要用到手写体数据集。
本文分享一种使用Python生成单张30*30像素手写体数字1-9图像的实现方法。
该方法利用random模块生成1-9的随机数,通过PIL库将数字写入图像,并经过旋转和扭曲处理以模拟“手写体”效果,过程中未添加干扰线和干扰点。
生成的手写体数字图像如图1所示。
实现过程较为简单,仅使用了PIL库,无需额外安装OpenCV等库,有兴趣的读者可自行尝试。

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

图2 利用generate_pngs.py写入到文件夹3的数字3图像
若需生成手写体字母或汉字,也可参考以下方法:


图3 利用generate_single_png.py生成汉字的手写体
源码已上传至GitHub:https://github.com/coneypo/Generate_handwritten_number
1.设计流程

图4 整体设计流程

图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的图像。

图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个文件夹。
当然也可以手动新建,若生成包含英文字母的验证码,则需创建更多子文件夹(大写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))

图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
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









