针对滑动验证码,如果我们想用爬虫自动化完成这一流程,关键步骤有两个:识别目标缺口的位置以及将滑块拖到缺口位置。
具体步骤为:
1、对验证码图片进行高斯模糊滤波处理,消除部分噪声干扰;
2、利用边缘检测算法,通过调整相应阈值识别出验证码图片中滑块的边缘;
3、基于上一步得到的各个边缘轮廓信息,对比面积、位置、周长等特征,筛选出最可能的轮廓,得到缺口位置。
一、准备工作
安装opencv-python库
pip install opencv-python
二、基础知识
1、高斯滤波
高斯滤波用来去除图片中的一些噪声,减少噪声干扰,其实就是把一张图片模糊化,为下一步的边缘检测做好铺垫。
OpenCV提供了一个用于实现高斯模糊的方法,叫作GaussianBlur,其声明如下:
def GaussianBlur(src,ksize,sigmax,dst=None,sigmaY=None,borderType=None)
- src:需要处理的图片。
- ksize:高斯滤波处理所用的高斯内核大小,需要传入一个元组,包含x和y两个元素,必须是奇数元祖。比如(3,3)、(5,5)、(7,7)等,通常使用正方形核,如(5,5),但也可以使用矩形核心,如(5,3),这个参数的设置直接影响图像的模糊效果,轻微去噪建议选择(3,3),可以保留更多细节,常规去噪推荐(5,5),强去噪推荐(7,7),但是画面就更模糊了。
- sigmaX:高斯内核函数在X方向上的标准偏差,
- sigmaY:高斯内核函数在Y方向上的标准偏差。若sigmaY为0,就将它设为sigmax;;若sigmaX和sigmaY都是0,就通过ksize计算出sigmaX和sigmaY。
sigmaX和sigmaY直接影响模糊的强度和方向性,值越大,模糊效果越强,sigmaX > sigmaY
:水平方向更模糊(适合处理垂直纹理);sigmaX < sigmaY
:垂直方向更模糊(适合处理水平纹理);sigmaX = sigmaY
:各向同性模糊(默认)。如果=0则表示根据ksize自动计算
轻微去噪想要保留文字边缘的话,或者保留锐利边缘,ksize建议设置(3,3),sigmaX和sigmaY都设置成0.5-1.5即可
这里ksize和sigmaX是必传参数。
2、边缘检测
由于验证码图片里的目标缺口通常具有比较明显的边缘,所以借助一些边缘检测算法,再加上调整阈值是可以找出缺口位置的。目前应用比较广泛的边缘检测算法是Canny,OpenCV也实现了算法,方法名就叫Canny,其声明如下:
def Canny(image,threshold1,threshold2,edges=None,apertureSize=3,L2gradient=False)
其中比较重要的参数如下:
- image:需要处理的图片。
- threshold1、threshold2:两个阈值,分别是最小判定临界点和最大判定临界点。
- apertureSize:用于查找图片渐变的索贝尔内核的大小,奇数。
- L2gradient:用于查找梯度幅度的等式,True表示使用更精确的L2梯度计算。
通常来说,只需要设置threshold1和threshold2的值即可,其数值大小需要视具体图片而定。经过边缘检测算法的处理后,会保留下一些比较明显的边缘信息。threshold1建议是50-100低阈值,threshold2建议是150-200高阈值
3、轮廓提取
下一步可以利用OpenCV技术提取出这些边缘的轮廓,这需要用到findContours方法,其声明如下:
def findContours(image,mode,method,contours=None,hierarchy=None,offset=None)
其中比较重要的参数如下:
- image:需要处理的图片。
- mode:用于定义轮廓的检索模式,RETR_EXTERNAL适合滑块/缺口检测,RETR_LIST需要分析所有结构,RETR_TREE分析嵌套轮廓(如复杂验证码),RETR_CCOMP建立两层级轮廓特性,适用于需要区分物体外廓和内部空洞的场景,比如带空洞的滑块。详情见OpenCV官方文档中对RetrievalModes的介绍。
- method:用于定义轮廓的近似方法,CHAIN_APPROX_NONE保留所有点,存储效率低,CHAIN_APPROX_SIMPLE压缩冗余点,存储效率高。详情见OpenCV官方文档中对ContourApproximationModes的介绍。
具体的选择标准可以参考OpenCV官方文档的介绍。
4、外接矩形
提取到边缘轮廓后,可以计算出轮廓的外接矩形,以便我们根据面积和周长等参数判断提取到的轮廓是不是目标缺口的轮廓。计算外接矩形使用的方法是boundingRect,其声明如下:
def boundingRect(array)
这个方法只有一个参数,就是array,它可以是一个灰度图或者2D点集,这里传入轮廓信息。返回该矩形的坐标和尺寸信息,具体来说,它返回一个包含四个整数的元组(x, y, w, h)
,其中:x
:矩形左上角的横坐标;y
:矩形左上角的纵坐标;w
:矩形的宽度;h
:矩形的高度
5、轮廓面积
获取了各个轮廓的外接矩形后,我们可以根据面积和周长等筛选缺口所在的位置,于是需要用到计算面积的方法contourArea,其定义如下:
def contourArea(contour,oriented=None)
其中各参数的介绍如下
- contour:轮廓信息。
- oriented:方向标识符,默认值为False。若取True,则该方法会返回一个带符号的面积值,正负取决于轮廓的方向(顺时针还是逆时针)。若取False,则面积值以绝对值形式返回。
返回值就是轮廓的面积。
6、轮廓周长
同理,周长也有对应的计算方法,叫作arcLength,其定义如下:
def arcLength(curve,closed)
其中各参数的介绍如下。
- curve:轮廓信息。
- closed:轮廓是否封闭。
返回值就是轮廓的周长。
三、缺口识别
接下来,我们开始真正的实现缺口识别算法。
首先,定义实现高斯滤波、边缘检测和轮廓提取的三个方法:
python
import cv2
# 高斯模糊的大小
GAUSSIAN_BLUR_KSZIZE = (5,5)
# 高斯模糊的标准差,0表示根据ksize自动计算
GAUSSIAN_BLUR_SIGMAX = 0
# Canny边缘检测的第一个阈值
CANNY_THRESHOLD1 = 100
# Canny边缘检测的第二个阈值
CANNY_THRESHOLD2 = 200
# 高斯模糊处理函数
def get_gaussian_blur_image(image):
"""
对图像进行高斯模糊处理,去除噪点
:param image: 输入图像
:return: 处理后的图像
"""
# 使用OpenCV的高斯模糊函数进行处理
image = cv2.GaussianBlur(image, GAUSSIAN_BLUR_KSZIZE, GAUSSIAN_BLUR_SIGMAX)
return image
# Canny边缘检测处理函数
def get_canny_image(image):
"""
对图像进行Canny边缘检测
:param image: 输入图像
:return: 处理后的图像
"""
# 使用OpenCV的Canny边缘检测函数进行处理
image = cv2.Canny(image, CANNY_THRESHOLD1, CANNY_THRESHOLD2)
return image
# 获取轮廓函数
def get_contours(image):
"""
获取图像的轮廓
:param image: 输入图像
:return: 轮廓列表
"""
# 使用OpenCV的findContours函数获取图像的轮廓
contours, _ = cv2.findContours(image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
return contours
读取待识别的验证码图片,并分别调用以上方法对此图片做处理:
ini
# 读取验证码图片
image_raw = cv2.imread('captcha.png')
# 获取宽高等信息
image_height, image_width, _ = image_raw.shape
# 高斯模糊处理
image_gaussian_blur = get_gaussian_blur_image(image_raw)
# 边缘检测处理
image_canny = get_canny_image(image_gaussian_blur)
# 得到轮廓信息
contours = get_contours(image_canny)
代码运行后,我们得到很多个轮廓信息。需要根据这些轮廓的外接矩形的面积和周长筛选我们想要的结果了。那么我们就需要大概估算缺口的高度、缺口的宽度,然后在允许误差20%的情况下,再根据验证码的宽和高信息大约计算出外接矩形的面积和周长的取值范围,如果是浏览器,那么我们可以直接通过F12找资源,得到验证码缺口的长宽信息,我们这里是图片,直接用截图然后框住缺口来移动,大致算出比例,我们算出来的比例是高大约是验证码高度的0.25,而宽大概是验证码宽度的0.15,因此这里我们可以算出缺口的面积阈值。另外还有一个偏移量,也就是缺口距离左边的距离肯定是大于两倍缺口的宽度,也就是大于0.3,这些综合起来就可以得到缺口的位置了。代码如下:
ini
# 缺口的面积最大阈值和最小阈值,我们算出来的缺口比例是高大约是验证码高度的0.25,而宽大概是验证码宽度的0.15,误差20%的情况下
MIN_AREA_THRESHOLD = (image_height*0.25)*(image_width*0.15)*0.8
MAX_AREA_THRESHOLD = (image_height*0.25)*(image_width*0.15)*1.2
# 偏移量,缺口距离左边的距离肯定是大于两倍缺口的宽度,也就是大于0.3验证码宽度
OFFSET_THRESHOLD = image_width * 0.3
# 遍历所有轮廓
for cnt in contours:
# 计算轮廓的面积
area = cv2.contourArea(cnt)
# 在面积范围内的轮廓
if MIN_AREA_THRESHOLD < area < MAX_AREA_THRESHOLD:
# 计算轮廓的外接矩形
x, y, w, h = cv2.boundingRect(cnt)
# 再通过外接矩形与验证码的宽高比来判断是否是缺口
if 0.25*0.8 < h / image_height < 0.25*1.2 and 0.15*0.8 < w / image_width < 0.15*1.2 and x > OFFSET_THRESHOLD:
print(f"缺口尺寸: {w}x{h}, 缺口位置为:({x},{y})")
# 对目标缺口的外接矩形做标注,在图片上绘制矩形,(x,y)是矩形左上角坐标,(x+w,y+h)是矩形右下角坐标,(0,0,255)表示颜色,这个是红色,2表示线条粗细
cv2.rectangle(image_raw,(x,y),(x+w,y+h),(0,0,255),2)
break
# 保存为图片
cv2.imwrite("image_lable.png", image_raw)