验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computersand Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。
这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
对图像验证码来讲,这类验证码大多是数字、字母的组合,国内也有使用汉字的。
在这个基础上增加噪点、干扰线、变形、重叠、不同字体颜色等方法来增加识别难度。
相应的这种验证码的识别大概分为以下几个步骤:
1.灰度处理
2.二值化
3.降噪
4.识别
PIL (Python Image Library) 是 Python 平台处理图片的事实标准,兼具强大的功能和简洁的 API。
另外虽然介绍的是 PIL,但实际上安装的却是 Pillow。PIL 的更新速度很慢,而且存在一些难以配置的问题,不推荐使用;而 Pillow 库则是 PIL 的一个分支,维护和开发活跃,Pillow 兼容 PIL 的绝大多数语法,推荐使用。
安装:
sudo pip install pillow
安装好之后,打开 Python 解释器,输入 from PIL import Image 来测试是否安装成功
PIL中所涉及的基本概念有如下几个:通道(bands)、尺寸(size)、坐标系统(coordinate system)。
通道
每张图片都是由一个或者多个数据通道构成。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 的主要功能定义在 Image 类当中,而 Image 类定义在同名的 Image 模块当中。使用 PIL 的功能,一般都是从新建一个Image 类的实例开始。新建 Image 类的实例有多种方法。
你可以用 Image 模块的 open()函数打开已有的图片档案,也可以处理其它的实例,或者从零开始构建一个实例。
# 生成图片 Image.new(mode, size, color) ⇒ image
# 其中mode表示模式,size表示大小,color表示颜色
from PIL import Image
im= Image.new("RGB", (128, 128), "#FF0000")
im.save('red.jpg')
im = Image.open('red.jpg')
im.show()
im_l = im.convert('L') # R=G=B
im_l.show()
Image 类的实例有 4 个常用属性,分别是:
format: 以 string 返回图片档案的格式(JPG, PNG, BMP, None, etc),如果不是从打开文件得到的实例,则返回 None。
mode: 以 string 返回图片的模式(RGB, CMYK, etc)
size: 以二元 tuple 返回图片档案的尺寸 (width, height)
info: 以字典形式返回示例的信息
# Image实例方法:
# convert 将当前图像转换为其他模式,并且返回新的图像。
from PIL import Image
im = Image.open('code.jpg')
print(im.mode)
im1 = im.convert('L')
print(im1.mode)
print(im1.getpixel((1,1)))
print(im1.getpixel((20,6)))
# 218
# 204
# getpixel((x,y)) 返回给定位置的像素值。如果图像为多通道,则返回一个元组。
from PIL import Image
im1 = Image.open("code.jpg")
print(im1.getpixel((1,1)))
print(im1.getpixel((6,6)))
# (220, 220, 210)
# (204, 214, 205)
# crop()图像中剪切出某个矩形大小的图像。它接收一个四元素的元组作为参数,各元素为(left, upper, right, lower),坐标系统的原点(0, 0)是左上角。
from PIL import Image
im1 = Image.open("code.jpg")
im = im1.crop((0, 0, 100, 50)) # 需要四个坐标x1,y1,x2,y2
im.show()
使用滤镜
from PIL import Image,ImageFilter
# 打开一个jpg图像文件,注意是当前路径:
im = Image.open('code.jpg')
# 获得图像尺寸:
w, h = im.size
print('Original image size: %sx%s' % (w, h))
# 缩放到50%:
im.thumbnail((w//2, h//2))
print('Resize image to: %sx%s' % (w//2, h//2))
# 把缩放后的图像用jpeg格式保存:
im.save('thumbnail.jpg', 'jpeg')
# 应用模糊滤镜:
im2 = im.filter(ImageFilter.BLUR)
im2.save('blur.jpg', 'jpeg')
# 高斯模糊
im2.filter(ImageFilter.GaussianBlur).save('GaussianBlur.jpg')
# 普通模糊
im2.filter(ImageFilter.BLUR).save('BLUR.jpg')
# 边缘增强
im2.filter(ImageFilter.EDGE_ENHANCE).save('EDGE_ENHANCE.jpg')
# 找到边缘
im2.filter(ImageFilter.FIND_EDGES).save('FIND_EDGES.jpg')
# 浮雕
im2.filter(ImageFilter.EMBOSS).save('EMBOSS.jpg')
# 轮廓
im2.filter(ImageFilter.CONTOUR).save('CONTOUR.jpg')
# 锐化
im2.filter(ImageFilter.SHARPEN).save('SHARPEN.jpg')
# 平滑
im2.filter(ImageFilter.SMOOTH).save('SMOOTH.jpg')
# 细节
im2.filter(ImageFilter.DETAIL).save('DETAIL.jpg')
PIL的ImageDraw提供了一系列绘图方法,让我们可以直接绘图。比如要生成字母验证码图片
画验证码需要的一些基础
from PIL import Image, ImageFilter, ImageDraw, ImageFont
# 创建画笔,用于在图片上画任意内容
img = Image.new(mode='RGB', size=(120,50), color=(0, 0, 0)) # 黑色
draw = ImageDraw.Draw(img, mode='RGB')
img.show()
# 画点
img = Image.new(mode='RGB', size=(120, 60), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示坐标
# 第二个参数:表示颜色
draw.point([100, 100], fill="red")
draw.point([105, 100], fill="red")
draw.point([110, 100], fill="red")
draw.point([300, 300], fill=(0, 255, 255))
# img.show()
# 画线
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示起始坐标和结束坐标
# 第二个参数:表示颜色
draw.line((100,100,100,300), fill='red')
draw.line((100,100,300,100), fill=(255, 255, 255))
# 写文本
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示起始坐标
# 第二个参数:表示写入内容
# 第三个参数:表示颜色
draw.text([0,0],'python',"red")
img.show()
# 特殊字体文字
img = Image.new(mode='RGB', size=(180, 60), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
# 第一个参数:表示字体文件路径
# 第二个参数:表示字体大小
font = ImageFont.truetype('arial.ttf',36)
# 第一个参数:表示起始坐标
# 第二个参数:表示写入内容
# 第三个参数:表示颜色
# 第四个参数:表示颜色
draw.text([0, 0], 'python', "red", font=font)
img.show()
生成字母+数字图形验证码
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import string
def rndChar():
"""
生成随机字母+数字
:return:
"""
return random.choice(string.ascii_lowercase + string.digits)
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 130), random.randint(0, 130), random.randint(0, 130))
def check_code(width=180, height=60, char_length=5, font_file='micross.ttf', font_size=30):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
# 创建Font对象:
font = ImageFont.truetype(font_file, font_size)
# 创建Draw对象:
draw = ImageDraw.Draw(img, mode='RGB')
# # 填充每个像素:
# for x in range(width):
# for y in range(height):
# draw.point((x, y), fill=rndColor())
# 写文字
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(10, 20)
draw.text([i * 25+6, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(50):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 画干扰线
for i in range(3):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
# img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
if __name__ == '__main__':
# 1. 直接打开
img,code = check_code()
img.show()
# 2. 写入文件
# img, code = check_code()
# print(code)
# with open('code3.png','wb') as f:
# img.save(f, format='png')
# 3. 写入内存(Python3)
# from io import BytesIO
# stream = BytesIO()
# img.save(stream, 'png')
# stream.getvalue()
# print(stream.getvalue())
灰度化处理的基本原理
灰度图像是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
安装
pip Install pytesser3
下载Tesseract 文件
pytesser是谷歌OCR开源项目的一个模块,在python中导入这个模块即可将图片中的文字转换成文本。
链接:https://code.google.com/p/pytesser/
或者使用 http://yun.baidu.com/s/1jHJvNiI
安装路径
设置路径
验证码
实现代码
def depoint(image):# 像素 判断一个点周围情况 4,8邻域
"""
降噪
:param image:
:return:
"""
pixdata = image.load()
print(pixdata)
w, h = image.size
for y in range(1,h-1):
for x in range(1,w-1):
count = 0
if pixdata[x,y-1] > 245:
count +=1
if pixdata[x,y+1] > 245:
count += 1
if pixdata[x-1,y] > 245:
count += 1
if pixdata[x+1,y] > 245:
count += 1
if count > 3:
pixdata[x,y] = 255
return image
def binaring(image,threshold = 160):
"""
对传入的图像进行灰度,二值化处理
:param image:
:param threshold:
:return:
"""
image = image.convert('L')
image.show()
pixdata = image.load()
# print(pixdata)
w, h = image.size
for y in range(h):
for x in range(w):
# print(pixdata[x,y])
pix_l.append(pixdata[x,y])
if pixdata[x, y] < threshold:
pixdata[x, y] = 0
else:
pixdata[x, y] = 255
return image
from pytesser3 import image_to_string
# from pytesser3 import image_file_to_string
from PIL import Image
image = Image.open('code3.png')
pix_l =[]
# image.show()
# pix_l_set = sorted(list(set(pix_l)))
# print(pix_l_set[:len(pix_l_set)//2]) # 求平均数的值
image2 = binaring(image) # 二值化
image3 = depoint(image2) # 降噪
image3.show()
# 识别文字
print('code: ',image_to_string(image3))
二值化
降噪