滑块验证码是一种常见的验证码形式,通过拖动滑块与背景图像中的缺口进行匹配,验证用户是否为真人。本文将详细介绍基于图像处理的滑块验证码匹配技术,并提供优化代码以提高滑块位置偏移量的准确度,尤其是在背景图滑块阴影较浅的情况下。
一、背景知识
1.1 图像处理概述
图像处理是指对图像进行分析和操作,以达到增强图像、提取特征、识别模式等目的。常用的图像处理技术包括高斯模糊、Canny 边缘检测、轮廓提取等。
1.2 滑块验证码的原理
滑块验证码通过用户拖动滑块,使滑块图像与背景图像中的缺口对齐,从而验证用户的操作。实现滑块验证码匹配的关键在于精确检测背景图像中缺口的位置。
二、技术实现
2.1 代码实现
import base64
import os
from datetime import datetime
from typing import Union, Optional
import cv2
import numpy as np
class SliderCaptchaMatch:
def __init__(self,
gaussian_blur_kernel_size=(5, 5),
gaussian_blur_sigma_x=0,
canny_threshold1=200,
canny_threshold2=450,
save_images=False,
output_path=""):
"""
初始化SlideMatch类
:param gaussian_blur_kernel_size: 高斯模糊核大小,默认(5, 5)
:param gaussian_blur_sigma_x: 高斯模糊SigmaX,默认0
:param canny_threshold1: Canny边缘检测阈值1,默认200
:param canny_threshold2: Canny边缘检测阈值2,默认450
:param save_images: 是否保存过程图片,默认False
:param output_path: 生成图片保存路径,默认当前目录
"""
self.GAUSSIAN_BLUR_KERNEL_SIZE = gaussian_blur_kernel_size
self.GAUSSIAN_BLUR_SIGMA_X = gaussian_blur_sigma_x
self.CANNY_THRESHOLD1 = canny_threshold1
self.CANNY_THRESHOLD2 = canny_threshold2
self.save_images = save_images
self.output_path = output_path
def _remove_alpha_channel(self, image):
"""
移除图像的alpha通道
:param image: 输入图像
:return: 移除alpha通道后的图像
"""
if image.shape[2] == 4: # 如果图像有alpha通道
alpha_channel = image[:, :, 3]
rgb_channels = image[:, :, :3]
# 创建一个白色背景
white_background = np.ones_like(rgb_channels, dtype=np.uint8) * 255
# 使用alpha混合图像与白色背景
alpha_factor = alpha_channel[:, :, np.newaxis] / 255.0
image_no_alpha = rgb_channels * alpha_factor + white_background * (1 - alpha_factor)
return image_no_alpha.astype(np.uint8)
else:
return image
def _get_gaussian_blur_image(self, image):
"""
对图像进行高斯模糊处理
:param image: 输入图像
:return: 高斯模糊处理后的图像
"""
return cv2.GaussianBlur(image, self.GAUSSIAN_BLUR_KERNEL_SIZE, self.GAUSSIAN_BLUR_SIGMA_X)
def _get_canny_image(self, image):
"""
对图像进行Canny边缘检测
:param image: 输入图像
:return: Canny边缘检测后的图像
"""
return cv2.Canny(image, self.CANNY_THRESHOLD1, self.CANNY_THRESHOLD2)
def _get_contours(self, image):
"""
获取图像的轮廓
:param image: 输入图像
:return: 轮廓列表
"""
contours, _ = cv2.findContours(image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
return contours
def _get_contour_area_threshold(self, image_width, image_height):
"""
计算轮廓面积阈值
:param image_width: 图像宽度
:param image_height: 图像高度
:return: 最小和最大轮廓面积阈值
"""
contour_area_min = (image_width * 0.15) * (image_height * 0.25) * 0.8
contour_area_max = (image_width * 0.15) * (image_height * 0.25) * 1.2
return contour_area_min, contour_area_max
def _get_arc_length_threshold(self, image_width, image_height):
"""
计算轮廓弧长阈值
:param image_width: 图像宽度
:param image_height: 图像高度
:return: 最小和最大弧长阈值
"""
arc_length_min = ((image_width * 0.15) + (image_height * 0.25)) * 2 * 0.8
arc_length_max = ((image_width * 0.15) + (image_height * 0.25)) * 2 * 1.2
return arc_length_min, arc_length_max
def _get_offset_threshold(self, image_width):
"""
计算偏移量阈值
:param image_width: 图像宽度
:return: 最小和最大偏移量阈值
"""
offset_min = 0.2 * image_width
offset_max = 0.85 * image_width
return offset_min, offset_max
def _is_image_file(self, file_path: str) -> bool:
"""
检查字符串是否是有效的图像文件路径
"""
valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff')
return os.path.isfile(file_path) and file_path.lower().endswith(valid_extensions)
def _is_base64(self, s: str) -> bool:
"""
检查字符串是否是有效的 base64 编码
"""
try:
if isinstance(s, str):
# Strip out data URI scheme if present
if "data:" in s and ";" in s:
s = s.split(",")[1]
base64.b64decode(s)
return True
return False
except Exception:
return False
def _read_image(self, image_source: Union[str, bytes], imread_flag: Optional[int] = None) -> np.ndarray:
"""
读取图像
:param image_source: 图像路径或base64编码
:param imread_flag: cv2.imread 和 cv2.imdecode 的标志参数 (默认: None)
:return: 读取的图像
"""
if isinstance(image_source, str):
if self._is_image_file(image_source): # 如果是文件路径
if imread_flag is not None:
return cv2.imread(image_source, imread_flag)
else:
return cv2.imread(image_source)
elif self._is_base64(image_source): # 如果是 base64 编码
# 剥离数据URI方案(如果存在)
if "data:" in image_source and ";" in image_source:
image_source = image_source.split(",")[1]
img_data = base64.b64decode(image_source)
img_array = np.frombuffer(img_data, np.uint8)
if imread_flag is not None:
image = cv2.imdecode(img_array, imread_flag)
else:
image = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED)
if image is None:
raise ValueError("Failed to decode base64 image")
return image
else:
raise ValueError("The provided string is neither a valid file path nor a valid base64 string")
else:
raise ValueError("image_source must be a file path or base64 encoded string")
def get_slider_offset(self, background_source: Union[str, bytes], slider_source: Union[str, bytes],
out_file_name: str = None) -> int:
"""
获取滑块的偏移量
:param background_source: 背景图像路径或base64编码
:param slider_source: 滑块图像路径或base64编码
:param out_file_name: 输出图片的文件名: 默认为当前时间戳
:return: 滑块的偏移量
"""
background_image = self._read_image(background_source)
slider_image = self._read_image(slider_source, cv2.IMREAD_UNCHANGED)
out_file_name = out_file_name if out_file_name else datetime.now().strftime('%Y%m%d%H%M%S.%f')[:-3]
if background_image is None:
raise ValueError("Failed to read background image")
if slider_image is None:
raise ValueError("Failed to read slider image")
slider_image_no_alpha = self._remove_alpha_channel(slider_image)
image_height, image_width, _ = background_image.shape
image_gaussian_blur = self._get_gaussian_blur_image(background_image)
image_canny = self._get_canny_image(image_gaussian_blur)
contours = self._get_contours(image_canny)
if self.save_images:
# 创建输出目录
if not os.path.exists(self.output_path):
os.makedirs(self.output_path)
cv2.imwrite(os.path.join(self.output_path, f'{out_file_name}_image_canny.png'), image_canny)
cv2.imwrite(os.path.join(self.output_path, f'{out_file_name}_image_gaussian_blur.png'), image_gaussian_blur)
contour_area_min, contour_area_max = self._get_contour_area_threshold(image_width, image_height)
arc_length_min, arc_length_max = self._get_arc_length_threshold(image_width, image_height)
offset_min, offset_max = self._get_offset_threshold(image_width)
offset = None
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
if contour_area_min < cv2.contourArea(contour) < contour_area_max and \
arc_length_min < cv2.arcLength(contour, True) < arc_length_max and \
offset_min < x < offset_max:
cv2.rectangle(background_image, (x, y), (x + w, y + h), (0, 0, 255), 2)
offset = x
# 匹配滑块模板在背景中的位置
result = cv2.matchTemplate(background_image, slider_image_no_alpha, cv2.TM_CCOEFF_NORMED)
_, _, _, max_loc = cv2.minMaxLoc(result)
slider_x, slider_y = max_loc
offset = slider_x
cv2.rectangle(background_image, (slider_x, slider_y),
(slider_x + slider_image_no_alpha.shape[1], slider_y + slider_image_no_alpha.shape[0]),
(255, 0, 0), 2)
if self.save_images:
cv2.imwrite(os.path.join(self.output_path, f'{out_file_name}_image_label.png'), background_image)
return offset
2.2 代码说明
- 图像预处理:通过高斯模糊和Canny边缘检测增强图像的对比度和亮度,提高滑块识别率。
- 多图像融合:通过多次处理图像并融合结果,以减小噪声对检测结果的影响。
- 动态调整阈值:根据图像的直方图动态调整Canny边缘检测的阈值,提高对不同图像的适应性。
- 轮廓检测:通过
_get_contours
函数获取图像的轮廓,并根据轮廓面积和弧长进行筛选。 - 滑块匹配:通过模板匹配方法
cv2.matchTemplate
匹配滑块在背景图中的位置。
2.3 优化策略
- 对比度和亮度增强:通过提高图像的对比度和亮度,使得滑块和背景的区别更加明显,增强滑块匹配的准确度。
- 多图像融合:融合多张处理后的图像,减小单张图像中的噪声对结果的影响。
- 动态调整参数:根据图像内容动态调整Canny边缘检测的阈值,使得算法对不同类型的图像都有较好的适应性。
2.4 安装依赖
要运行上述代码,需要安装以下 Python 库:
pip install numpy opencv-python slider_captcha_match
2.5 使用方法
在安装完所需库后,您可以按照以下步骤使用滑块验证码匹配功能:
- 初始化SliderCaptchaMatch类:配置高斯模糊、Canny边缘检测等参数。
- 读取背景图像和滑块图像:可以是文件路径或base64编码。
- 获取滑块偏移量:调用
get_slider_offset
函数,返回滑块的准确偏移量。
from slider_captcha_match import SliderCaptchaMatch
from datetime import datetime
import cv2
# 初始化 SliderCaptchaMatch 类
slider_captcha_match = SliderCaptchaMatch(save_images=True,output_path="output")
# 读取背景图像和滑块图像
background_source = "path_to_background_image.jpg"
slider_source = "path_to_slider_image.png"
# 获取滑块偏移量
offset = slider_captcha_match.get_slider_offset(background_source, slider_source)
print(f"滑块偏移量: {offset}")
# 输出结果保存路径
out_file_name = datetime.now().strftime('%Y%m%d%H%M%S.%f')[:-3]
print(f"结果图像保存路径: output/{out_file_name}_image_label.png")
三、测试与验证
为了验证优化后的滑块验证码匹配技术,进行多次测试,比较不同情况下的滑块偏移量检测结果,并记录背景图、滑块图、中间预处理图和代码标注的滑块位置的图,以及缺口坐标位置偏移量计算。
Response for row 1: offset(手动标注)=155;缺口坐标(代码计算)=155.0
Response for row 2: offset(手动标注)=119;缺口坐标(代码计算)=118.5
Response for row 2: offset(手动标注)=223;缺口坐标(代码计算)=224.0
四、总结
本文介绍了基于图像处理的滑块验证码匹配技术,并通过多种优化策略提高了滑块位置偏移量的检测准确度。通过对图像进行预处理、融合多张图像、动态调整阈值等方法,可以有效提高滑块验证码在不同背景下的识别率。希望这篇文章能够对从事图像处理和验证码研究的读者有所帮助。
参考资料
- OpenCV 官方文档
- NumPy 官方文档
- 本Github项目源码地址