本文章节

  1. 验证码
  2. PIL库
  3. 识别字符验证码

验证码

知识点一: 什么是验证码

验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,
是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。
这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类

知识点二: 验证码的类别

图像验证码
对图像验证码来讲,这类验证码大多是数字、字母的组合,国内也有使用汉字的。在这个基础上增加噪点、干扰线、变形、重叠、不同字体颜色等方法来增加识别难度。相应的这种验证码的识别大概分为以下几个步骤:

  1. 灰度处理
  2. 二值化
  3. 降噪
  4. 识别

滑动验证码

滑动验证码也可以叫做行为验证,其中最出名的就是极验。现在极验验证码已经更新到了 3.0 版本,截至 2017 年 7月全球已有十六万家企业正在使用极验,每天服务响应超过四亿次,广泛应用于直播视频、金融服务、电子商务、游戏娱乐、政府企业等各大类型网站。
滑动验证码的原理就是使用机器学习中的深度学习技术,根据一些特征来区分是否为正常用户。
比如,可以通过记录用户的滑动平均速度,还有每一小段时间的瞬时速度,用户鼠标点击情况,以及滑动后的匹配程度来识别。而且,不是说滑动到正确位置就是验证通过,而是根据特征识别来区分是否为真用户,滑到正确位置只是一个必要条件。

点击验证码
常见的点击类验证码都是给出一张包含文字的图片,通过文字提醒用户点击图中相同字的位置进行验证。
如:

字符型验证码tensorflow端到端的图片说明 验证码字符是什么_人工智能

PIL库

PIL (Python Image Library) 是 Python 平台处理图片的事实标准,兼具强大的功能和简洁的 API。

另外虽然介绍的是 PIL,但实际上安装的却是 Pillow。PIL 的更新速度很慢,而且存在一些难以配置的问题,不推荐使用;而 Pillow 库则是 PIL 的一个分支,维护和开发活跃,Pillow 兼容 PIL 的绝大多数语法,推荐使用。
安装:
sudo pip install pillow
安装好之后,打开 Python 解释器,输入 from PIL import Image 来测试是否安装成功。

PIL中所涉及的基本概念有如下几个:模式(mode)、通道(bands)、尺寸(size)、坐标系统(coordinate system)、调色板(palette)、信息(info)和滤波器(filters)。

 模式

图像的模式定义了图像的类型和像素的位宽。当前支持如下模式:

1:1位像素,表示黑和白,但是存储的时候每个像素存储为8bit。
L:8位像素,表示黑和白。
P:8位像素,使用调色板映射到其他模式。
RGB:3x8位像素,为真彩色。
RGBA:4x8位像素,有透明通道的真彩色。
CMYK:4x8位像素,颜色分离。
YCbCr:3x8位像素,彩色视频格式。
I:32位整型像素。
F:32位浮点型像素。
PIL也支持一些特殊的模式,包括RGBX(有padding的真彩色)和RGBa(有自左乘alpha的真彩色)。


可以通过mode属性读取图像的模式。其返回值是包括上述模式的字符串。

属性mode的使用如下:

from PIL import Image
im = Image.open("xiao.png")
print(im.mode)
输出:
'RGB'


通道
每张图片都是由一个或者多个数据通道构成。PIL允许在单张图片中合成相同维数和深度的多个通道。
以RGB图像为例,每张图片都是由三个数据通道构成,分别为R、G和B通道。而对于灰度图像,则只有一个通道。
对于一张图片的通道数量和名称,可以通过方法getbands()来获取。方法getbands()是Image模块的方法,它会返回一个字符串元组(tuple)。该元组将包括每一个通道的名称。



from PIL import Image
im = Image.open("code.jpg")
print(im.getbands())



输出:
('R', 'G', 'B')

尺寸
通过size属性可以获取图片的尺寸。这是一个二元组,包含水平
和垂直方向上的像素的个数。

属性size的使用如下:



from PIL import Image
im = Image.open("code.jpg")
print(im.size)



输出:
(240, 60)

坐标系统

PIL使用笛卡尔像素坐标系统,坐标(0,0)位于左上角。注意:坐标值表示像素的角;位于坐标(0,0)处的像素的中心实际上位于(0.5,0.5)。

坐标经常用于二元组(x,y)。长方形则表示为四元组,前面是左上角坐标。例如,一个覆盖800x600的像素图像的长方形表示为(0,0,800,600)。

调色板

调色板模式 ("P")使用一个颜色调色板为每个像素定义具体的颜色值

 信息

使用info属性可以为一张图片添加一些辅助信息。这个是字典对象。加载和保存图像文件时,多少信息需要处理取决于文件格式。

属性info的使用如下:



from PIL import Image
im = Image.open("xiao.png")
print(im.info)
输出:
{}



滤波器

对于将多个输入像素映射为一个输出像素的几何操作,PIL提供了4个不同的采样滤波器:

NEAREST:最近滤波。从输入图像中选取最近的像素作为输出像素。它忽略了所有其他的像素。
BILINEAR:双线性滤波。在输入图像的2x2矩阵上进行线性插值。注意:PIL的当前版本,做下采样时该滤波器使用了固定输入模板。
BICUBIC:双立方滤波。在输入图像的4x4矩阵上进行立方插值。注意:PIL的当前版本,做下采样时该滤波器使用了固定输入模板。
ANTIALIAS:平滑滤波。这是PIL 1.1.3版本中新的滤波器。对所有可以影响输出像素的输入像素进行高质量的重采样滤波,以计算输出像素值。在当前的PIL版本中,这个滤波器只用于改变尺寸和缩略图方法。
注意:在当前的PIL版本中,ANTIALIAS滤波器是下采样(例如,将一个大的图像转换为小图)时唯一正确的滤波器。BILIEAR和BICUBIC滤波器使用固定的输入模板,用于固定比例的几何变换和上采样是最好的。


Image模块中的方法resize()和thumbnail()用到了滤波器。

方法resize()的使用如下:



方法resize()的定义为:resize(size, filter=None)=> image
from PIL import Image
im = Image.open("xiao.png")
print(im.size)
im_resize = im.resize((256,256))
print(im_resize.size)
输出:
(670, 502)
(256,256)



对参数filter不赋值的话,方法resize()默认使用NEAREST滤波器。如果要使用其他滤波器可以通过下面的方法来实现:



from PIL import Image
im = Image.open("xiao.png")
print(im.size)
im_resize0 = im.resize((256,256), Image.BILINEAR)
print(im_resize0.size)
im_resize1 = im.resize((256,256), Image.BICUBIC)
print(im_resize1.size)
im_resize2 = im.resize((256,256), Image.ANTIALIAS)
print(im_resize2.size)
输出:
(670, 502)
(256,256)
(256,256)
(256,256)



类的函数

PIL 的主要功能定义在 Image 类当中,而 Image 类定义在同名的 Image 模块当中。使用 PIL 的功能,一般都是从新建一个 Image 类的实例开始。新建 Image 类的实例有多种方法。你可以用 Image 模块的 open() 函数打开已有的图片档案,也可以处理其它的实例,或者从零开始构建一个实例。

1、  New



定义:Image.new(mode,size) ⇒ image
     Image.new(mode, size, color) ⇒ image
含义:使用给定的变量mode和size生成新的图像。Size是给定的宽/高二元组,这是按照像素数来计算的。对于单通道图像,变量color只给定一个值;对于多通道图像,变量color给定一个元组(每个通道对应一个值)。
     在版本1.1.4及其之后,用户也可以用颜色的名称,比如给变量color赋值为“red”。如果没有对变量color赋值,图像内容将会被全部赋值为0(图像即为黑色)。如果变量color是空,图像将不会被初始化,即图像的内容全为0。
     这对向该图像复制或绘制某些内容是有用的。
例子:
from PIL import Image
im= Image.new("RGB", (128, 128), "#FF0000")
im.save("test1.png")   #图像im为128x128大小的红色图像。
im= Image.new("RGB", (128, 128))   #图像im为128x128大小的黑色图像,因为变量color不赋值的话,图像内容被设置为0,即黑色。
im.save("test2.png")
im= Image.new("RGB", (128, 128), "red")   #图像im为128x128大小的红色图像。
im.save("test3.png")



2、  Open

定义:Image.open(file) ⇒ image
     Image.open(file, mode) ⇒ image
含义:打开并确认给定的图像文件。这个是一个懒操作;该函数只会读文件头,而真实的图像数据直到试图处理该数据才会从文件读取(调用load()方法将强行加载图像数据)。如果变量mode被设置,那必须是“r”。
     用户可以使用一个字符串(表示文件名称的字符串)或者文件对象作为变量file的值。文件对象必须实现read(),seek()和tell()方法,并且以二进制模式打开。
例子:
from PIL import Image
im = Image.open("xiao.png")



3、  Blend

定义:Image.blend(image1,image2, alpha) ⇒ image
含义:使用给定的两张图像及透明度变量alpha,插值出一张新的图像。这两张图像必须有一样的尺寸和模式。
     合成公式为:out = image1 *(1.0 - alpha) + image2 * alpha
     如果变量alpha为0.0,将返回第一张图像的拷贝。如果变量alpha为1.0,将返回第二张图像的拷贝。对变量alpha的值没有限制。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = Image.open("wu.jpg")
im = Image.blend(im1,im2,0.5)
im.save("he.jpg")


4、  Composite

定义:Image.composite(image1,image2, mask) ⇒ image
含义:使用给定的两张图像及mask图像作为透明度,插值出一张新的图像。变量mask图像的模式可以为“1”,“L”或者“RGBA”。所有图像必须有相同的尺寸。
例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = Image.open("wu.jpg")
r,g,b = im1.split()
print(g.mode)
im = Image.composite(im1,im2,b)
im.save("he.jpg")
b.save("he1.jpg")


5、  Eval



定义:Image.eval(image,function) ⇒ image
含义:使用变量function对应的函数(该函数应该有一个参数)处理变量image所代表图像中的每一个像素点。如果变量image所代表图像有多个通道,那变量function对应的函数作用于每一个通道。
     注意:变量function对每个像素只处理一次,所以不能使用随机组件和其他生成器。
例子:

from PIL import Image
im = Image.open("jing.jpg")
def deffun(c):
    return c*0.89      #改变了亮度
im_eval = Image.eval(im,deffun)  
im_eval.save("gai.jpg")

注:图像im_eval与im01比较,其像素值均为im01的一半,则其亮度自然也会比im01暗一些。



6、  Frombuffer



定义:Image.frombuffer(mode,size, data) ⇒ image
     Image.frombuffer(mode, size,data, decoder, parameters) ⇒ image
含义:(New in PIL 1.1.4)使用标准的“raw”解码器,从字符串或者buffer对象中的像素数据产生一个图像存储。对于一些模式,这个图像存储与原始的buffer(这意味着对原始buffer对象的改变体现在图像本身)共享内存。
      并非所有的模式都可以共享内存;支持的模式有“L”,“RGBX”,“RGBA”和“CMYK”。对于其他模式,这个函数与fromstring()函数一致。
注意:版本1.1.6及其以下,这个函数的默认情况与函数fromstring()不同。这有可能在将来的版本中改变,所以为了最大的可移植性,当使用“raw”解码器时,推荐用户写出所有的参数,如下所示:
im =Image.frombuffer(mode, size, data, "raw", mode, 0, 1)
函数Image.frombuffer(mode,size, data, decoder, parameters)与函数fromstring()的调用一致。



7、  Fromstring

定义:Image.fromstring(mode,size, data) ⇒ image
     Image.fromstring(mode, size,data, decoder, parameters) ⇒ image
含义:函数Image.fromstring(mode,size, data),使用标准的“raw”解码器,从字符串中的像素数据产生一个图像存储。
函数Image.fromstring(mode,size, data, decoder, parameters)也一样,但是允许用户使用PIL支持的任何像素解码器。更多信息可以参考:Writing YourOwn File Decoder.
注意:这个函数只对像素数据进行解码,而不是整个图像。如果用户的字符串包含整个图像,可以将该字符串包裹在StringIO对象中,使用函数open()来加载。



8、  Merge

定义:Image.merge(mode,bands) ⇒ image
含义:使用一些单通道图像,创建一个新的图像。变量bands为一个图像的元组或者列表,每个通道的模式由变量mode描述。所有通道必须有相同的尺寸。
变量mode与变量bands的关系:
len(ImageMode.getmode(mode).bands)= len(bands)
例子:
from PIL import Image
im1 = Image.open("jing.jpg")
im2 = Image.open("wu.jpg")
r1,g1,b1 = im1.split()
r2,g2,b2 = im2.split()
imgs =[r1,g2,b2]
im_merge = Image.merge("RGB",imgs)
im_merge.save("he.jpg")



Image实例方法

convert

将当前图像转换为其他模式,并且返回新的图像。



from PIL import Image
im = Image.open('code.jpg')
print(im.mode)
im1 = im.convert('1')
print(im1.mode)



getbbox



定义:im.getbbox() ⇒ 4-tuple or None
含义:计算图像非零区域的包围盒。这个包围盒是一个4元组,定义了左、上、右和下像素坐标。如果图像是空的,这个方法将返回空。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.getbbox())
输出:
(0, 0, 650, 650)



Filter



定义:im.filter(filter) ⇒ image
含义:返回一个使用给定滤波器处理过的图像的拷贝。可用滤波器需要参考ImageFilter模块。

例子:
from PIL import Image,ImageFilter
im1 = Image.open("jing.jpg")
im_filter = im1.filter(ImageFilter.BLUR)
im_filter.save("he.jpg")
注:图像im_filter比im01变得有些模糊了。



getdata



定义:im.getdata() ⇒ sequence
含义:以包含像素值的sequence对象形式返回图像的内容。这个sequence对象是扁平的,以便第一行的值直接跟在第零行的值后面,等等。
注意:这个方法返回的sequence对象是PIL内部数据类型,它只支持某些sequence操作,包括迭代和基础sequence访问。使用list(im.getdata()),将它转换为普通的sequence。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
seq = im1.getdata()
print(seq[0])
seq0 = list(seq)
print(seq0[0])
print(len(seq0))
输出:
(41, 183, 197)
(41, 183, 197)
       #这个值是长和高之积

注:Sequence对象的每一个元素对应一个像素点的R、G和B三个值。



 

load



定义:im.load()
含义:为图像分配内存并从文件中加载它(或者从源图像,对于懒操作)。正常情况下,用户不需要调用这个方法,因为在第一次访问图像时,Image类会自动地加载打开的图像。

     (New in 1.1.6)在1.1.6及以后的版本,方法load()返回一个用于读取和修改像素的像素访问对象。这个访问对象像一个二维队列,如:
      pix = im.load()
      print pix[x, y]
      pix[x, y] =value
      通过这个对象访问比方法getpixel()和putpixel()快很多。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
lm_load = im1.load()
print(lm_load[649,649])
输出:
(236, 210, 153)



getpixel((x,y))

返回给定位置的像素值。如果图像为多通道,则返回一个元组。

注意:该方法执行比较慢;如果用户需要使用python处理图像中较大部分数据,可以使用像素访问对象(见load),或者方法getdata()。



from PIL import Image
im1 = Image.open("code.jpg")
print(im1.getpixel((1,1)))
print(im1.getpixel((6,6)))



paste



定义1:im.paste(image,box)
含义1:将一张图粘贴到另一张图像上。变量box或者是一个给定左上角的2元组,或者是定义了左,上,右和下像素坐标的4元组,或者为空(与(0,0)一样)。如果给定4元组,被粘贴的图像的尺寸必须与区域尺寸一样。
如果模式不匹配,被粘贴的图像将被转换为当前图像的模式。

例子1:
from PIL import Image
im1 = Image.open("jing.jpg")
box = [0,0,200,200]
im_crop = im1.crop(box)
im1.paste(im_crop,(200,200,400,400))  #等价于im1.paste(im_crop,(200,200))
im1.save("he.jpg")

定义2:im.paste(colour,box)
含义2:它与定义1一样,但是它使用同一种颜色填充变量box对应的区域。对于单通道图像,变量colour为单个颜色值;对于多通道,则为一个元组。

例子2:
from PIL import Image
im1 = Image.open("jing.jpg")
im1.paste((256,256,256),(200,100,500,200))
im1.save("he.jpg")
注:图像im1的(200,100)位置将出现一个300x100的白色方块,对于多通道的图像,如果变量colour只给定一个数值,将只会应用于图像的第一个通道。如果是“RGB”模式的图像,将应用于红色通道。

定义3:im.paste(image,box, mask)
含义3:与定义1一样,但是它使用变量mask对应的模板图像来填充所对应的区域。可以使用模式为“1”、“L”或者“RGBA”的图像作为模板图像。模板图像的尺寸必须与变量image对应的图像尺寸一致。
      如果变量mask对应图像的值为255,则模板图像的值直接被拷贝过来;如果变量mask对应图像的值为0,则保持当前图像的原始值。变量mask对应图像的其他值,将对两张图像的值进行透明融合。
注意:如果变量image对应的为“RGBA”图像,即粘贴的图像模式为“RGBA”,则alpha通道被忽略。用户可以使用同样的图像作为原图像和模板图像。

例子3:
from PIL import Image
im1 = Image.open("jing.jpg")
box = [100,100,200,200]
im_crop = im1.crop(box)
r,g,b = im_crop.split()
im1.paste(im_crop,(200,100,300,200),b)
im1.save("he.jpg")
注:在图像im1的(0,0)位置将出现一个半透明的100x100的方块。

定义4:im.paste(colour,box, mask)
含义4:与定义3一样,只是使用变量colour对应的单色来填充区域。

例子4:
from PIL import Image
im1 = Image.open("jing.jpg")
box = [100,100,200,200]
im_crop = im1.crop(box)
r,g,b = im_crop.split()
im1.paste((0,256,0),(200,100,300,200),b)
im1.save("he.jpg")
注:在图像im1的(0,0)位置将出现一个100x100的绿色方块。



 

crop()

图像中剪切出某个矩形大小的图像。它接收一个四元素的元组作为参数,各元素为(left, upper, right, lower),坐标系统的原点(0, 0)是左上角。



定义:im.crop(box) ⇒ image
含义:从当前的图像中返回一个矩形区域的拷贝。变量box是一个四元组,定义了左、上、右和下的像素坐标。
这是一个懒操作。对源图像的改变可能或者可能不体现在裁减下来的图像中。为了获取一个分离的拷贝,对裁剪的拷贝调用方法load()。

例子:
from PIL import Image
im1 = Image.open("jing.jpg")
print(im1.size)
box = [0,0,650,400]   #650(长)400(高)
im_crop = im1.crop(box)
im_crop.save("he.jpg")



简单验证码识别

知识点一: 灰度处理

灰度化处理的基本原理
灰度图像是R、G、B三个分量相同的一种特殊的彩色图像。
简单来讲就是R=G=B.



from PIL import Image
image = Image.open('code.jpg')
im = image.convert('L')



知识点二: 二值化

图像的二值化,就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。



def binarizing(img,threshold=127):
    """传入image对象进行灰度、二值处理"""
    img = img.convert("L") # 转灰度
    pixdata = img.load()
    w, h = img.size
    # 遍历所有像素,大于阈值的为黑色
    for y in range(h):
        for x in range(w):
            if pixdata[x, y] < threshold:
                pixdata[x, y] = 0
            else:
                pixdata[x, y] = 255
    return img



知识点三: 降噪

根据一个点A的RGB值,与周围的8个点的RBG值比较,设定一个值N(0 <N <8),当A的RGB值与周围8个点的RGB相等或者小于N时,此点为噪点 



def depoint(img):
    """传入二值化后的图片进行降噪"""
    pixdata = img.load()
    w,h = img.size
    for y in range(1,h-1):
        for x in range(1,w-1):
            count = 0
            if pixdata[x,y-1] > 245:#上
                count = count + 1
            if pixdata[x,y+1] > 245:#下
                count = count + 1
            if pixdata[x-1,y] > 245:#左
                count = count + 1
            if pixdata[x+1,y] > 245:#右
                count = count + 1
            if count > 4:
                pixdata[x,y] = 255
    return img



补充 pytesser3的使用
1.通过pip Instll pytesser3 下载包
2.下载Tesseract OCR 文件
3.修改pytesser3包下面__init__文件内tesseract_exe_name的值为你的tesseract.exe的路径

字符型验证码tensorflow端到端的图片说明 验证码字符是什么_验证码_02

 

 

字符型验证码tensorflow端到端的图片说明 验证码字符是什么_人工智能_03

 



1 import random
 2 from PIL import Image,ImageDraw,ImageFont
 3 from pytesser3 import image_to_string
 4 
 5 class GetCode():
 6     def __init__(self,n=4,size=(250,80)):
 7         self.n = n  # n代表验证码个数,默认四个
 8         self.width,self.height = size  # size代表验证码图片像素大小
 9 
10     def rndfont(self):
11         '''
12         先把大写字母,数字,小写字母的ascii值放在一个列表中,然后随机取一个转成对应的字符
13         '''
14         li = [x for x in range(65,91)]+[i for i in range(48,58)]+[j for j in range(97,123)]
15         return chr(random.choice(li))
16 
17     def rndbgcolor(self):
18         '''
19         生成随机验证码背景颜色,浅色调(150-250)
20         '''
21         return (random.randint(150,255),random.randint(150,255),random.randint(150,255))
22 
23     def rndfontcolor(self):
24         '''
25         随机生成验证码字符颜色,深色调(0-150)
26         '''
27         return (random.randint(0,150),random.randint(0,150),random.randint(0,150))
28 
29     def get_code(self):
30         image = Image.new('RGB',(self.width,self.height),(255,255,255)) # 生成一个image对象
31         font = ImageFont.truetype('ARIALUNI.TTF',30)  # 字符字体以及大小
32         draw = ImageDraw.Draw(image)  # 把image对象放到画布上
33         for x in range(self.width):
34             for y in range(self.height):
35                 draw.point((x,y),fill=self.rndbgcolor()) # 随机背景颜色填充image
36         for t in range(4):
37             draw.text((60*t+30,25),self.rndfont(),font=font,fill=self.rndfontcolor())  # 字体填充随机颜色
38         image.save('code.jpg')#保存图片
39 
40 
41 
42 class OcrCode():
43     def __init__(self,image):
44         self.image = Image.open(image)
45         self.image.show()
46         self.width, self.height =self.image.size
47 
48     def transfer_image(self):
49         self.image = self.image.convert('L') #灰度处理
50         pix = self.image.load()
51         threshold = 150
52         for x in range(self.width):
53             for y in range(self.height):
54                 pix[x,y] = 0 if pix[x,y] < threshold else 255  #二值化处理,把大于阀值的像素点直接设置为白色(255),
55                                                              # 小于阀值的设置为黑色(0)
56         self.image.show()
57         return self.image
58 
59 
60     def clear_noise(self):
61         '''
62         四邻去噪,遍历图片像素点,判断像素点是否孤立,如果像素点四周有超过3个点灰度值为白色,那么该点应该应该也设置为白色
63         '''
64         pix = self.transfer_image().load()
65         for x in range(1,self.width-1):
66             for y in range(1,self.height-1):
67                 count = 0
68                 if pix[x,y-1] > 245:
69                     count += 1
70                 if pix[x,y+1] > 245:
71                     count += 1
72                 if pix[x-1,y] > 245:
73                     count += 1
74                 if pix[x+1,y] > 245:
75                     count += 1
76                 if count > 3:
77                     pix[x,y] = 255
78         self.image.show()
79         return self.image
80 
81     def recognize_code(self):
82         textcode = image_to_string(self.clear_noise())
83         print('图片中的字符为:',textcode)
84 
85 
86 if __name__ == '__main__':
87     code = GetCode(4)
88     code.get_code()
89     ocrcode = OcrCode('code.jpg')
90     ocrcode.recognize_code()



 补充:验证码生成



def get_validCode(request):
    width = 120#指定图片的长高
    height = 40

    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))

    img = Image.new(mode="RGB", size=(width, height), color="white")
    draw = ImageDraw.Draw(img, "RGB")  # 实例画笔对象
    font = ImageFont.truetype("static/font/kumo.ttf", 25)  # 指定字体样式和大小
    valid_list = []  # 用于存储随机验证码
    for i in range(5):  # 生成5位数的随机字符
        random_num = str(random.randint(0, 9))  # 随机数字
        random_lower = chr(random.randint(65, 90))  # 随机小写字母
        random_upper = chr(random.randint(97, 122))  # 随机小写字母

        random_char = random.choice([random_num, random_lower, random_upper])  # 随机选择

        draw.text([5 + i * 24, 10], random_char, rndColor(), font=font)
        # 这里列表里的两个参数表示横纵坐标,在img里面的,第二个参数表示文本内容,第三个是颜色,第四个是字体大小
        valid_list.append(random_char)  # 将随机生成的验证码字符串添加到列表中
    for i in range(100):  # 加噪点
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
    for i in range(5):  # 加干扰线,这里通过添加多组横纵坐标实现
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)
        x3 = random.randint(0, width)
        y3 = random.randint(0, height)

        draw.line((x1, y1, x2, y2, x3, y3), fill=rndColor())
    f = BytesIO()  # 相当于获取文件句柄
    img.save(f, "png")  # 存入内存,以png的格式
    data = f.getvalue()  # 从内存中取出来
    return data