使用OCR技术识别图形验证码

首先来看最简单的一种验证码——图形验证码,这种验证码最早出现,现在也依然很常见,一般由 4 位左右的字母或者数字组成。

例如在案例网站 https://captcha7.scrape.center/ 就可以看到类似的验证码,如图 8-1 所示。

图8-1 由字母或数字组成的图形验证码

这类验证码整体比较规整,没有过多的干扰线和干扰点,文字也没有大幅度的变形和旋转。对于这类验证码,可以使用 OCR 技术识别。

OCR技术

OCR,即 Optical Character Recognition,中文叫作光学字符识别,是指使用电子设备(例如扫描仪或数码相机)检查打印在纸上的字符,通过检查暗、亮的模式确定字符形状,然后使用字符识别方法将形状转换成计算机文字。现在 OCR 已经广泛应用于生产生活中,如文档识别、证件识别、字幕识别、文档检索等。当然,用来识别本节所述的图形验证码也没有问题。

本节中我们会以当前案例网站的验证码为例,讲解利用 OCR 识别图形验证码的流程,实现输入 验证码的图片,输出识别结果。

准备工作

在本节的学习过程中需要导人 tesserocr 库,这个库的安装相对来说没有那么简单,可以参考 https://setup.scrape.center/tesserocr。

另外,还需要安装 Selenium、Pillow、NumPy 和 retrying 库,用来模拟登录、处理图像和重试操作。可以使用 pip3 工具安装这些库:

pip3 install selenium pillow numpy retrying

如果安装过程中遇到了问题,可以参考如下链接。

安装好这些库之后,就可以开始学习了。

保存验证码图片

为了便于操作,先将验证码的图片保存到本地。在浏览器中打开案例网站,然后右击验证码图片,将其保存为 captcha.png 文件即可,示例如图 8-2 所示。

图 8-2 图片 captcha.png

识别测试

现在新建一个项目,将验证码图片放到项目的根目录下,然后利用 tesserocr 库识别该验证码,代码如下:

import tesserocr
from PIL import Image

image = Image.open('captcha.png')
result = tesserocr.image_to_text(image)
print(result)

这里先新建了一个图片对象,然后调用 tesserocr 里的 image_to_text 方法将图片转换为了文本。

实现过程非常简单,运行结果如下:

d241

tesserocr 还提供了一个更加方便的方法,可以直接将图片文件转化为字符串,代码如下:

import tesserocr
print(tesserocr.file_to_text('captcha.png'))

输出结果同样是 d241。

可以看到,通过 OCR 技术便能成功识别图形验证码的内容。

处理验证码

换一个验证码,将其保存为 captcha2.png 文件,如图 8-3 所示。

重新执行下面的代码做测试:

import tesserocr
from PIL import Image
image = Image.open('captcha2.png')
result = tesserocr.image_to_text(image)
print(result)

输出结果如下: -b32d

这次的识别结果和实际结果有偏差,多了一个 “-”,这是因为图片里多余的点对识别造成了干扰对于这种情况,需要做一些额外的处理,把干扰信息去掉。仔细观察可以发现,图片里那些造成干扰的点,其颜色大多比文本的颜色更浅,因此可以通过颜色将造成干扰的点排除掉。

首先将保存的验证码图片转化为数组,看一下维度:

import tesserocr
from PIL import Image
import numpy as np

image = Image.open('captcha2.png')
print(np.array(image).shape)
print(image.mode)

运行结果如下:

从结果可以看出,这个图片其实是一个三维数组,38 和 112 代表图片的高和宽,4 则是每个像素点的表示向量。为什么是 4 呢?因为最后一维是一个长度为 4 的数组,分别代表 R(红色)、G(绿色) B(蓝色)、A(透明度),即一个像素点由 4 个数字表示。那为什么是 R、G、B、A,而不是 R、G、B 或其他呢?因为 image.mode 是 RGBA,即有透明通道的真彩色,运行结果的第二行也可以印证这一点。

mode 属性定义了图片的类型和像素的位宽,一共有 9 种类型。

  • 1:像素用 1 位表示,Python 中表示为 True 或 False,即二值化。

  • L:像素用 8 位表示,取值 0~255,表示灰度图像,数字越小,颜色越黑。

  • P:像素用 8 位表示,即调色板数据。

  • RGB:像素用 3×8 位表示,即真彩色。

  • RGBA:像素用 4×8 位表示,即有透明通道的真彩色。

  • CMYK:像素用 4×8 位表示,即印刷四色模式。

  • YCbCr:像素用 3x8 位表示,即彩色视频格式。

  • I:像素用 32 位整型表示。

  • F:像素用 32 位浮点型表示。

为了方便处理,可以把 RGBA 转为更简单的 L,即把图片转化为灰度图像。往图片对象的 convert 方法中传入 L 即可,代码如下所示:

image = image.convert('L')
image.show()

也可以往 convert 方法中传入 1,即把图片二值化处理,代码如下所示:

image = image.convert('1')
image.show()

我们选择把图片转化为灰度图像,然后根据阈值删除图片中的干扰点,代码如下:

from PIL import Image import numpy as np

image = Image.open('captcha2.png') image = image.convert('L') threshold = 50 array = np.array(image) array = np.where(array > threshold, 255, 0) image = Image.fromarray(array.astype('uint8')) image.show()

这里先将变量 threshold 赋值为 50,它代表灰度的阈值。接着将图片转化为 NumPy 数组,利用 NumPy 的 where 方法对数组进行筛选和处理,其中指定将灰度大于阈值的图片的像素设置为 255,表示白色,否则设置为 0,表示黑色。

最后看一下处理完的图片长什么样,如图 8-4 所示。

可以看到原来图片中的干扰点已经不见了,整个图片变得黑白分明。此时重新识别验证码,代码如下:

import tesserocr
from PIL import Image
import numpy as np

image = Image.open('captcha2.png')
image = image.convert('L')
threshold = 50
array = np.array(image)
array = np.where(array > threshold, 255, 0)
image = Image.fromarray(array.astype('uint8'))
print(tesserocr.image_to_text(image))

运行结果如下: b32d

这次的结果是正确的。所以,针对一些有干扰的图片,可以做去噪处理,这能提高验证码识别的正确率。

识别实战

现在,我们可以尝试使用自动化的方式识别案例中的验证码,这里使用 Selenium 完成这个操作,代码如下:

import time
import re
import tesserocr
from selenium import webdriver
from io import BytesI0
from PIL import Image
from retrying import retry
from selenium.webdriver.support.wait import WebDriverwait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
import numpy as np

def preprocess(image):
    image = image.convert('L')
    array = np.array(image)
    array = np.where(array > 50, 255, 0)
    image = Image.fromarray(array.astype('uint8'))
    return image

@retry(stop_max_attempt_number=10,retry_on_result=lambdax:x is False)
def login():
    browser.get("https://captcha7.scrape.center/")
    browser.find_element_by_css_selector('.username input[type="text"]').send_keys('admin')
    browser.find_element_by_css_selector('.passwordinput[type="password"]').send_keys('admin')
    captcha = browser.find_element_by_css_selector('#captcha')
    image = Image.open(BytesIO(captcha.screenshot_as_png))
    image = preprocess(image)
    captcha = tesserocr.image_to_text(image)
    captcha = re.sub('[^A-Za-z0-9]', "", captcha)
    browser.find_element_by_css_selector('.captchainput[type="text"]').send_keys(captcha)
    browser.find_element_by_css_selector('.login').click()

    try:
        WebDriverWait(browser, 10).until(EC.presence_of_element_located((By.XPATH, '//h2[contains(.,"登录成功")')))
        time.sleep(10)
        browser.close()
        return True
    except TimeoutException:
        return False
if __name__ == '__main__':
    browser = webdriver.Chrome()
    login()

我们首先定义了一个 preprocess 方法,用于对验证码图片做去噪处理,逻辑和前面是一样的。

接着定义了一个 login 方法,其执行逻辑是:

  1. 打开案例网站;

  2. 找到用户名输入框,输入用户名;

  3. 找到密码输入框,输入密码;

  4. 找到并截取验证码图片,转化为图片对象;

  5. 预处理验证码,去除噪声;

  6. 识别验证码,得到识别结果;

  7. 去除识别结果中的一些非字母字符和数字字符;

  8. 找到验证码输入框,输入验证码结果;

  9. 点击 “登录” 按钮;

  10. 等待 “登录成功” 的字样出现,如果出现就证明验证码识别正确,否则重复以上步骤重试。

其中我们用到了 retrying 来指定重试条件和重试次数,以保证在识别出错的情况下能够反复重试,增加整体的成功概率。

运行代码,会弹出浏览器,我们按照以上流程输入相应内容,可能重试几次,就成功登录了网站。浏览器页面如图8-5所示。

图8-5 浏览器页面

登录成功的页面如图 8-6 所示。

图8-6 登录成功的页面

至此,我们已经能通过 OCR 技术成功识别图片验证码,并将其应用到模拟登录的实战中。

总结

本节中我们了解了利用 tesserocr 识别图片验证码的过程,并将其应用于实战案例,实现了模拟登录。为了提高 tesserocr 的识别正确率,可以对验证码图片做去噪预处理。但利用 tesserocr 识别验证码的正确率整体并不高,下一节我们介绍其他方案。