预期效果:
我们要使用 Python下的Pillow实现以下功能
介绍一些 图片验证码:
图片验证码是比较传统的验证码形式,图片中除了经过平移、旋转、错切、缩放等基本变换的字母和数字之外,加一些随机线条或其他 干扰因素。
安装 pillow:
pip install pillow
开工:
- 既然要用到验证码,那就先准备好 随机选取的模块
- 导入 字符和数字的 集合(不是数据类型的集合)
- 准备图片需要的用到的工具、ImageDraw 和 ImageFont
- 将选取好的字符串返回
from random import choice, randint, randrange # randrange和randint的区别就是 前者左闭右开
import string
from PIL import Image, ImageDraw, ImageFont
# 验证码图片文字的字符集
characters = string.ascii_letters + string.digits # string下面的ascii_letters 字符和 digits 数字供咱们选择
def selectedCharacters(length):
'''返回length个字符串'''
result = ''.join(choice(characters) for _ in range(length)) # 这里用到的是 生成器表达式来进行随机选取 和 封装字符串、
return result
- 准备好颜色返回
def getColor():
'''准备好 文字干扰线要用到的颜色'''
r = randint(0, 255) # 随机选取颜色
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
- 定义主函数,生成图片,函数参数:图片大小,字符数量,背景颜色
- 创建白色底图,和生成在图片上绘画的对象
- 将生成的字符串计算大小(宽高),否则图片装不下
- 判断 空白图像是否能装得下 字符
def main(size=(200, 100), characterNumber=6, bgcolor=(255, 255, 255)):
'''创建空白图像和绘图对象'''
imageTemp = Image.new('RGB', size, bgcolor)
draw01 = ImageDraw.Draw(imageTemp) # 创建可绘图的 draw 对象
# 生成并计算随机字符串的宽度和高度
text = selectedCharacters(characterNumber) # 生成随机的字符
font = ImageFont.truetype(C:/Windows/Fonts/Dengl.ttf', 48) # 使用系统的文字
width, height = draw01.textsize(text, font) # 计算图片中字符集的,宽高是多少
if width + 2 * characterNumber > size[0] or height > size[1]:
'''(如果生成字符的宽度 + 字符数量 x 2 > 空白图片的宽度) or (字符的高度 > 空白图片的高度)'''
print('尺寸不合法')
return
- 生成字符在图片中的坐标位置,求出每个字符的长度
- 对总字符数量遍历,让每个字符都有自己的坐标位置 x,y
- 开始在 图上绘画 字符
# 绘制随机字符串中的字符
startX = 0 # x坐标
widthEachCharater = width // characterNumber # 字符总宽 // 数量 = 每一个字符的宽度
for i in range(characterNumber): # 遍历 字符串数量,循环这么多次
startX += widthEachCharater + 1 # 计算 每个字符 X轴的 坐标点
'''让图片的Y轴 上下不定,Y轴 = ((图片的高度-字符的高度) // 2) 计算出能够活动的空间 + +- 10的这个区间,这里要注意 自己设定的区间不要超过图片的活动范围'''
position = (startX, (size[1] - height) // 2 + randint(-10, 10)) # x,y 轴的坐标都有,那么他就可以代表每个 字符的位置
draw01.text(xy=position, text=text[i], font=font, fill=getColor()) # 在坐标上xy,使用 fill 颜色,font大小,来生成 text 文字
- 对生成的字符实现扭曲的效果,就是 向两边偏移一点点像素
- 加载 生成新图像(和之前图像一样大) 和 之前创建的白板图像
- 遍历图像的宽高,将像素位置向做偏移一些
- 最后在 for 的作用域内,老图像将x,y 一点点的赋值给 新图像
# 对像素位置进行微调,实现扭曲的效果
imageFinal = Image.new('RGB', size, bgcolor)
pixelsFinal = imageFinal.load() # 加载新图像
pixelsTemp = imageTemp.load() # 加载老图像
for y in range(size[1]): # 遍历y轴 也就是图像的高度,从上到下
offset = randint(-1, 0) # 生成偏移的规格 -1到0,代表着向左
for x in range(size[0]): # 遍历x轴 也就是图像的宽度,从左到右
newx = x + offset # 在x轴的基础项 加上 刚才设置的偏移量,结果就是 不想左偏移像素 就是 原地不动
if newx >= size[0]: # 如果偏移后的值 大于 原图像的宽度了
newx = size[0] - 1 # 就让他 -1个
elif newx < 0: # 小于 0,
newx = 0 # 就让他等于零
pixelsFinal[newx, y] = pixelsTemp[x, y] # 将原图像的 宽高 赋值给 新图像
'''这个的操作就相当于,将之前图像的宽高,通过for 一点点的遍历给 新图像'''
- 制作三中 随机的干扰线,彩色的小点,弧线,直线
- 将刚才扭曲的新图像 Draw,成为可绘画图片
- point 点,line 直线, arc 弧线
# 绘制随机颜色随机位置的干扰像素
draw02 = ImageDraw.Draw(imageFinal)
for i in range(int(size[0] * size[1] * 0.07)):
'''遍历次数 宽度x高度x0.07,这个0.07值得是密度,7%的干扰像素,设置多了就看不到内容了'''
draw02.point((randrange(0, size[0]), randrange(0, size[1])), fill=getColor())
'''point 画点。参数 x,y,颜色。画点的x,y轴都是取决于 图片的大小,然后randrange取随机。颜色也是取随机'''
# 绘制 8条随机干扰直线
for i in range(8):
'''随机直线 值得就是 两端的位置不固定,但是 y轴的 长度都是一样的'''
start = (0, randrange(size[1])) # 开始随机位置 x=0,y=随机
end = (size[0], randrange(size[1])) # 结束随机位置
draw02.line([start + end], fill = getColor(), width=1) # 开始画线,x,y位置,随机的颜色,线条的宽度。
# 绘制 8条随机弧线
for i in range(8):
start = (-50, -50) # start为 x,y 的-50坐标
end = (size[0] + 10, randint(0, size[1] + 10)) # end是 宽度 + 随机一个区间
'''start + end,代表 end计算的值 分别 -50,来代表弧线的 x,y 轴'''
draw02.arc(start + end, 0, 360, fill=getColor()) # 0起始位置,360结束位置
- 保存图片 并 展示
- 将主函数启动
# 保存并显示图片
imageFinal.save("result.jpg")
imageFinal.show()
if __name__ == '__main__':
main((200, 100), 6, (255, 255, 255))
效果展示
最后全部代码
from random import choice, randint, randrange # randrange和randint的区别就是 前者左闭右开
import string
from PIL import Image, ImageDraw, ImageFont
# 验证码图片文字的字符集
characters = string.ascii_letters + string.digits # string下面的ascii_letters 字符和 digits 数字供咱们选择
def selectedCharacters(length):
'''返回length个字符串'''
result = ''.join(choice(characters) for _ in range(length)) # 这里用到的是 生成器表达式来进行随机选取 和 封装字符串、
return result
def getColor():
'''准备好 文字干扰线要用到的颜色'''
r = randint(0, 255) # 随机选取颜色
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
def main(size=(200, 100), characterNumber=6, bgcolor=(255, 255, 255)):
'''创建空白图像和绘图对象'''
imageTemp = Image.new('RGB', size, bgcolor)
draw01 = ImageDraw.Draw(imageTemp) # 创建可绘图的 draw 对象
# 生成并计算随机字符串的宽度和高度
text = selectedCharacters(characterNumber) # 生成随机的字符
font = ImageFont.truetype('C:/Windows/Fonts/Dengl.ttf', 48) # 使用系统的文字
width, height = draw01.textsize(text, font) # 计算图片中字符集的,宽高是多少
if width + 2 * characterNumber > size[0] or height > size[1]:
'''(如果生成字符的宽度 + 字符数量 x 2 > 空白图片的宽度) or (字符的高度 > 空白图片的高度)'''
print('尺寸不合法')
return
# 绘制随机字符串中的字符
startX = 0 # x坐标
widthEachCharater = width // characterNumber # 字符总宽 // 数量 = 每一个字符的宽度
for i in range(characterNumber): # 遍历 字符串数量,循环这么多次
startX += widthEachCharater + 1 # 计算 每个字符 X轴的 坐标点
'''让图片的Y轴 上下不定,Y轴 = ((图片的高度-字符的高度) // 2) 计算出能够活动的空间 + +- 10的这个区间,这里要注意 自己设定的区间不要超过图片的活动范围'''
position = (startX, (size[1] - height) // 2 + randint(-10, 10)) # x,y 轴的坐标都有,那么他就可以代表每个 字符的位置
draw01.text(xy=position, text=text[i], font=font, fill=getColor()) # 在坐标上xy,使用 fill 颜色,font大小,来生成 text 文字
# 对像素位置进行微调,实现扭曲的效果
imageFinal = Image.new('RGB', size, bgcolor)
pixelsFinal = imageFinal.load() # 加载新图像
pixelsTemp = imageTemp.load() # 加载老图像
for y in range(size[1]): # 遍历y轴 也就是图像的高度,从上到下
offset = randint(-1, 0) # 生成偏移的规格 -1到0,代表着向左
for x in range(size[0]): # 遍历x轴 也就是图像的宽度,从左到右
newx = x + offset # 在x轴的基础项 加上 刚才设置的偏移量,结果就是 不想左偏移像素 就是 原地不动
if newx >= size[0]: # 如果偏移后的值 大于 原图像的宽度了
newx = size[0] - 1 # 就让他 -1个
elif newx < 0: # 小于 0,
newx = 0 # 就让他等于零
pixelsFinal[newx, y] = pixelsTemp[x, y] # 将原图像的 宽高 赋值给 新图像
'''这个的操作就相当于,将之前图像的宽高,通过for 一点点的遍历给 新图像'''
# 绘制随机颜色随机位置的干扰像素
draw02 = ImageDraw.Draw(imageFinal)
for i in range(int(size[0] * size[1] * 0.07)):
'''遍历次数 宽度x高度x0.07,这个0.07值得是密度,7%的干扰像素,设置多了就看不到内容了'''
draw02.point((randrange(0, size[0]), randrange(0, size[1])), fill=getColor())
'''point 画点。参数 x,y,颜色。画点的x,y轴都是取决于 图片的大小,然后randrange取随机。颜色也是取随机'''
# 绘制 8条随机干扰直线
for i in range(8):
'''随机直线 值得就是 两端的位置不固定,但是 y轴的 长度都是一样的'''
start = (0, randrange(size[1])) # 开始随机位置 x=0,y=随机
end = (size[0], randrange(size[1])) # 结束随机位置
draw02.line(start + end, fill = getColor(), width=1) # 开始画线,x,y位置,随机的颜色,线条的宽度。
# 绘制 8条随机弧线
for i in range(8):
start = (-50, -50) # start为 x,y 的-50坐标
end = (size[0] + 10, randint(0, size[1] + 10)) # end是 宽度 + 随机一个区间
'''start + end,代表 end计算的值 分别 -50,来代表弧线的 x,y 轴'''
draw02.arc(start + end, 0, 360, fill=getColor()) # 0起始位置,360结束位置
# 保存并显示图片
imageFinal.save("result.jpg")
imageFinal.show()
if __name__ == '__main__':
main((200, 100), 6, (255, 255, 255))
- 如果觉得 注释太多,影响学习
注释简易版本
from random import choice, randint, randrange
import string
from PIL import Image, ImageDraw, ImageFont
# 验证码图片文字的字符集
characters = string.ascii_letters + string.digits
def selectedCharacters(length):
result = ''.join(choice(characters) for _ in range(length))
return result
def getColor():
r = randint(0, 255)
g = randint(0, 255)
b = randint(0, 255)
return (r, g, b)
def main(size=(200, 100), characterNumber=6, bgcolor=(255, 255, 255)):
# 创建空白图像和绘图对象
imageTemp = Image.new('RGB', size, bgcolor)
draw01 = ImageDraw.Draw(imageTemp)
# 生成并计算随机字符串的宽度和高度
text = selectedCharacters(characterNumber)
font = ImageFont.truetype('C:/Windows/Fonts/Dengl.ttf', 48)
width, height = draw01.textsize(text, font)
if width + 2 * characterNumber > size[0] or height > size[1]:
print('尺寸不合法')
return
# 绘制随机字符串中的字符
startX = 0
widthEachCharater = width // characterNumber
for i in range(characterNumber):
startX += widthEachCharater + 1
position = (startX, (size[1] - height) // 2 + randint(-10, 10))
draw01.text(xy=position, text=text[i], font=font, fill=getColor())
# 对像素位置进行微调,实现扭曲的效果
imageFinal = Image.new('RGB', size, bgcolor)
pixelsFinal = imageFinal.load()
pixelsTemp = imageTemp.load()
for y in range(size[1]):
offset = randint(-1, 0)
for x in range(size[0]):
newx = x + offset
if newx >= size[0]:
newx = size[0] - 1
elif newx < 0:
newx = 0
pixelsFinal[newx, y] = pixelsTemp[x, y]
# 绘制随机颜色随机位置的干扰像素
draw02 = ImageDraw.Draw(imageFinal)
for i in range(int(size[0] * size[1] * 0.07)):
draw02.point((randrange(0, size[0]), randrange(0, size[1])), fill=getColor())
# 绘制8条随机干扰直线
for i in range(8):
start = (0, randrange(size[1]))
end = (size[0], randrange(size[1]))
draw02.line(start + end, fill = getColor(), width=1)
# 绘制8条随机弧线
for i in range(8):
start = (-50, -50)
end = (size[0] + 10, randint(0, size[1] + 10))
draw02.arc(start + end, 0, 360, fill=getColor())
# 保存并显示图片
imageFinal.save("result.jpg")
imageFinal.show()
if __name__ == '__main__':
main((200, 100), 6, (255, 255, 255))