基于图像处理的滑块验证码匹配技术

3 篇文章 0 订阅
1 篇文章 0 订阅

滑块验证码是一种常见的验证码形式,通过拖动滑块与背景图像中的缺口进行匹配,验证用户是否为真人。本文将详细介绍基于图像处理的滑块验证码匹配技术,并提供优化代码以提高滑块位置偏移量的准确度,尤其是在背景图滑块阴影较浅的情况下。

一、背景知识

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 使用方法

在安装完所需库后,您可以按照以下步骤使用滑块验证码匹配功能:

  1. 初始化SliderCaptchaMatch类:配置高斯模糊、Canny边缘检测等参数。
  2. 读取背景图像和滑块图像:可以是文件路径或base64编码。
  3. 获取滑块偏移量:调用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

四、总结

本文介绍了基于图像处理的滑块验证码匹配技术,并通过多种优化策略提高了滑块位置偏移量的检测准确度。通过对图像进行预处理、融合多张图像、动态调整阈值等方法,可以有效提高滑块验证码在不同背景下的识别率。希望这篇文章能够对从事图像处理和验证码研究的读者有所帮助。

参考资料

  1. OpenCV 官方文档
  2. NumPy 官方文档
  3. 本Github项目源码地址
  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值