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
1 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 个文件夹:
(当然你也可以自己新建, 新建 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 的分布:
1 : 25 张
2 : 17 张
3 : 21 张
4 : 19 张
5 : 20 张
6 : 22 张
7 : 25 张
8 : 24 张
9 : 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
来源: https://www.cnblogs.com/AdaminXie/p/8379749.html