自动化测试时,验证码是个讨厌的存在,尤其现在存在的各种高级验证码方式。本篇只从图片验证码入手,探讨如何准确识别。

因为不同的产品获取图片验证码的接口不同,本片不探讨如何获取图片验证码,默认能准确获取。

识别前处理图片

图片验证码一般包含许多颜色和噪点,用于防止恶意程序对系统进行自动化攻击(貌似我们探讨的就是,哈哈),我们首先需要处理他们,增加 ocr 识别成功率。

最通用的处理流程是:灰度化—>二值化—>降噪

灰度化

什么是灰度化?我们的代码都涉及到图片邻域了,应该了解一下相关的知识。

我们常见的图片基本都是 RGB 图像,RGB 图像中的每个像素由三个值组成,分别表示红色、绿色和蓝色的强度。这些值的范围通常是0到255。例如,纯红色的像素表示为(255, 0, 0),纯绿色为(0, 255, 0),纯蓝色为(0, 0, 255)。当三个通道的值相同时,图像会呈现灰色,例如(100, 100, 100)表示灰度值为100。

灰度图像中的每个像素只有一个值,表示亮度。这个值的范围也是0到255,其中0表示黑色,255表示白色,0-255之间的值表示不同程度的灰色。

那么如何将 RGB 图像转换为灰度图像呢?既然灰度图像每个像素只有一个值,将 RGB 图像每个像素的三个值转换为一个值,是不是就可以了呢?

答案是可以的,可以通过算法实现:

算法名称 算法公式 算法特点
加权平均法 gray = 0.299 * R + 0.587 * G + 0.114 * B 考虑人眼对不同颜色的敏感度,效果自然,也最常用
平均法 gray = (R + G + B) / 3 简单快速,但视觉效果一般
最大值法 gray = max(R, G, B) 保留最亮的颜色分量,常用于特殊效果
单通道提取法 gray = G 直接提取绿色,快速但信息丢失多,适合特定场景
单通道提取法 abs(R - G) + abs(G - B) + abs(B - R) 强调差异,用于边缘检测
单通道提取法 取亮度通道(L或V)作为灰度值 更符合色彩模型,适用于高级处理

比较推荐加权平均法,也引出来 opencv-python 库(支持以上算法)。

# 如果使用 PyChar 编辑器,错误提示时,可点击下载 opencv-python 库。
import cv2

# 读取彩色图像
img = cv2.imread('input.jpg')

# 转换为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 保存结果,查看图片变化
cv2.imwrite('output_gray.jpg', gray_img)

二值化

二值图像的每一个像素只有两个值0和1,其中0表示黑色,1表示白色。

二值化的方法就是设置一个阈值(比如127),将灰度图像每一个像素大于或等于阈值的值设置为1,小于阈值的值设置为0。这样就得到了二值图像。

随之而来的是另一个问题,阈值要怎么设置呢?对于 ocr 识别需要的图片,可以使用手动设置(推荐127,但可以试试100、150等比较)、Otsu 自动阈值、局部自适应阈值。幸运的是 opencv-python 库支持这三种方法。

# 如果使用 PyChar 编辑器,错误提示时,可点击下载 opencv-python 库。
import cv2

# 读取彩色图像
img = cv2.imread('input.jpg')

# 转换为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 手动设置阈值
_, binary_manual = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY)

# 保存结果,查看图片变化
cv2.imwrite('output_manual.jpg', binary_manual)

# Otsu 自动阈值
_, binary_otsu = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 保存结果,查看图片变化
cv2.imwrite('output_otsu.jpg', binary_otsu)

# 局部自适应阈值
binary_adaptive = cv2.adaptiveThreshold(
    gray_img,
    255,
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
    cv2.THRESH_BINARY,
    blockSize=15,
    C=2
)

# 保存结果,查看图片变化
cv2.imwrite('output_adaptive.jpg', binary_adaptive)

这三种方案可以根据具体图片选择使用。

降噪

降噪的方法如下:

方法 是否保留边缘 适用噪声类型 速度 推荐用途
均值滤波 一般噪声 简单图像处理
高斯滤波 部分保留 高斯噪声 OCR 图像预处理
中值滤波 椒盐噪声 较快 OCR、文字识别常用
双边滤波 多种噪声 高质量图像修复
非局部均值去噪 多种噪声、纹理干扰 很慢 老文档、扫描件修复
形态学操作 局部 小噪点、孔洞 极快 二值图后处理、OCR专用

你没有想错,opencv-python 库支持以上所有方法对图片降噪。推荐使用如下流程:

# 如果使用 PyChar 编辑器,错误提示时,可点击下载 opencv-python 库。
import cv2
import numpy as np

# 读取图像
img = cv2.imread('input.jpg')

# 转为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 灰度图降噪(推荐中值滤波)
denoised = cv2.medianBlur(gray_img, 3)

# 手动设置阈值
_, binary = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY)

# 形态学开运算(去噪点)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 保存结果
cv2.imwrite('cleaned_for_ocr.jpg', cleaned)

识别图片验证码

对图片进行预处理后,就可以进行 ocr 识别了。基本上有四种方式可以识别图片验证码:

  1. pytesseract:谷歌开源的OCR识别程序,经测试只能识别很简单的图片验证码,不推荐(可以试试)
  2. ddddocr:本篇要使用的 OCR 库,比 pytesseract 要好的多
  3. 云服务厂商:我认为是最牛逼的,大厂训练资源质量高、数量多。唯一缺点是需要钱?
  4. 自己训练:使用深度学习框架(如 PyTorch / TensorFlow)训练专用模型,是难度最大的

不考虑那么多,直接 ddddocr 走起:

# 如果使用 PyChar 编辑器,错误提示时,可点击下载 cv2 库。ddddocr同理
import cv2
import ddddocr

# 读取图像
img = cv2.imread('test.jpg')

# 转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 灰度图降噪(推荐中值滤波)
denoised = cv2.medianBlur(gray, 3)

# 手动设置阈值
_, binary = cv2.threshold(denoised, 127, 255, cv2.THRESH_BINARY)

# 形态学开运算(去噪点)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 保存结果
cv2.imwrite('cleaned_for_ocr.jpg', cleaned)

# 实例化
ocr = ddddocr.DdddOcr()

# 读取图像字节流
with open('cleaned_for_ocr.jpg', 'rb') as f:
    img_bytes = f.read()

# 获取识别结果
result = ocr.classification(img_bytes)
print(result)

另外可能会遇到这样的报错:

Traceback (most recent call last):
  File "D:\Projects\Python\TestOcr\app.py", line 32, in <module>
    result = ocr.classification(img_bytes)
  File "D:\Projects\Python\TestOcr\.venv\Lib\site-packages\ddddocr\__init__.py", line 466, in classification
    image = image.resize((int(image.size[0] * (64 / image.size[1])), 64), Image.ANTIALIAS).convert('L')
                                                                          ^^^^^^^^^^^^^^^
AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'

这是因为从 Pillow 10.0.0 版本开始 ,Image.ANTIALIAS 被弃用并移除了,取而代之的是 Image.Resampling.LANCZOS。而在 ddddocr 的源码中(具体是 init.py 中的 classification 方法),使用了旧版本 Pillow 的常量:image.resize((width, height), Image.ANTIALIAS)

解决办法是修改 ddddocr 源代码:

  1. 在PyCharm的运行输出框内,找到 **.venv\Lib\site-packages\ddddocr\__init__.py
  2. ctrl+鼠标左键点击 **.venv\Lib\site-packages\ddddocr\__init__.py,跳转到对应源码
  3. 将源码 image = image.resize((int(image.size[0] * (64 / image.size[1])), 64), Image.ANTIALIAS).convert('L') 替换为 image = image.resize((int(image.size[0] * (64 / image.size[1])), 64), Image.Resampling.LANCZOS).convert('L')

THEEND



© 转载需要保留原始链接,未经明确许可,禁止商业使用。CC BY-NC-ND 4.0