Python3生成手写体数字方法

Hazel
Hazel
发布时间:2026-10-13| 4人浏览过
原创

本文介绍一种使用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

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。