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

news2024/11/26 21:27:21

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

一、背景知识

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项目源码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1897576.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++入门 容器适配器 / stack queue模拟实现

目录 容器适配器 deque的原理介绍 stack模拟实现 queue模拟实现 priority_queue模拟实现 仿函数 容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结)&#xff0c;该种模式是将一个类的接口转换成客户希望…

Rufus 制作启动盘 | 便携的工作空间

唠唠闲话 最近服务器硬盘故障多&#xff0c;在修复过程中&#xff0c;学习了一些操作&#xff0c;这里做个记录。本期主要介绍 U盘启动盘的制作&#xff0c;以及持久化存储。 U 盘启动盘 镜像选择 Ubuntu 的版本命名遵循 “Adjective Animal” 的模式&#xff0c;即 “形容…

数据驱动制造业升级,免费可视化工具成关键

制造业作为国民经济的支柱产业&#xff0c;正经历着前所未有的变革。数据&#xff0c;作为这场变革的核心驱动力&#xff0c;其重要性不言而喻。然而&#xff0c;面对海量且复杂的数据&#xff0c;如何高效、直观地将其转化为有价值的洞察&#xff0c;成为了众多制造企业亟待解…

C++ 栈-队列-优先级队列

目录 1 栈 2 队列 3 deque 介绍 4 优先级队列 5 反向迭代器 栈也是我们在C语言就模拟实现过的一种数据结构&#xff0c;在C中&#xff0c;栈其实和我们前面模拟实现过的string、vector等容器有一点区别&#xff0c;站起是不是容器&#xff0c;而是一种容器适配器&#xff0c;我…

【前端项目笔记】9 数据报表

数据报表 效果展示&#xff1a; 在开发代码之前新建分支 git checkout -b report 新建分支report git branch 查看分支 git push -u origin report 将本地report分支推送到云端origin并命名为report 通过路由的形式将数据报表加载到页面中 渲染数据报表基本布局 面包屑导航…

总结一下 .NET FrameWork 和 .NET Core 创建的项目的不同点

前言 从 Visual Studio 2022 开始&#xff0c;微软开始淡化 .NET Framework 的概念&#xff0c;在项目向导中&#xff0c;只有使用带 “.NET Framework” 的项目模板创建的才是 .NET Framework 项目&#xff0c;使用其他模板创建的都是 .NET Core 项目 比如&#xff0c;如果你…

【数据结构】06.栈队列

一、栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out)的原则。 压栈&#…

数据结构--单链表实现

欢迎光顾我的homepage 前言 链表和顺序表都是线性表的一种&#xff0c;但是顺序表在物理结构和逻辑结构上都是连续的&#xff0c;但链表在逻辑结构上是连续的&#xff0c;而在物理结构上不一定连续&#xff1b;来看以下图片来认识链表与顺序表的差别 这里以动态顺序表…

《梦醒蝶飞:释放Excel函数与公式的力量》8.8 STDEVP函数

8.8 STDEVP函数 STDEVP函数是Excel中用于计算总体数据的标准偏差的函数。标准偏差是统计学中的一个重要指标&#xff0c;用于衡量数据集中各数值偏离平均值的程度。总体标准偏差考虑了整个数据集&#xff0c;而不是样本。 8.8.1 函数简介 STDEVP函数用于返回总体数据的标准偏…

Java实现日志全链路追踪.精确到一次请求的全部流程

广大程序员在排除线上问题时,会经常遇见各种BUG.处理这些BUG的时候日志就格外的重要.只有完善的日志才能快速有效的定位问题.为了提高BUG处理效率.我决定在日志上面优化.实现每次请求有统一的id.通过id能获取当前接口的全链路流程走向. 实现效果如下: 一次查询即可找到所有关…

前端面试题13(API请求方法)

在前端JavaScript中&#xff0c;进行API请求主要可以通过几种方式来实现&#xff0c;最常见的是使用XMLHttpRequest&#xff08;较旧的方法&#xff09;、fetch&#xff08;现代浏览器推荐方法&#xff09;以及使用第三方库如axios或jQuery.ajax等。 1. XMLHttpRequest 这是最…

MPI hello world SSH 免密互联

目标&#xff1a; 我们想实现2台主机免密互联&#xff0c;将MPI Hello World跑起来 假设hostname是node01,node02,&#xff08;Linux shell窗口一般是UserNameHostName&#xff0c;node1和node2一定要和HostName一样&#xff09; hostname是/etc/hosts中的配置&#xff0c;如下…

工地/矿区/电力/工厂/环卫视频智能安全监控反光衣AI检测算法的原理及场景应用

一、引言 随着科技的快速发展&#xff0c;特别是在智能交通和安全生产领域&#xff0c;对于夜间或弱光环境下的人员识别和安全监控需求日益凸显。反光衣作为一种重要的安全装备&#xff0c;被广泛应用于道路施工、工地作业、夜间巡逻、安全生产等场景&#xff0c;旨在提高人员的…

【CUDA】 矩阵乘法 matMatMul

矩阵乘法 matMatMul 矩阵乘法是基本线性代数子程序&#xff08;BLAS&#xff09;的重要组成部分&#xff0c;而且线性代数中许多其他操作以此为基础。 图1是两个矩阵的乘法。 基础方法&#xff0c;正方形tile和长方形tile 基础方法 执行矩阵乘法的基础方法是使用单个线程执…

2021-06-15 protues(ISIS)脉冲发生器仿真仪表使用

缘由这个脉冲发生器怎么连线_编程语言-CSDN问答

STM32智能交通管理系统教程

目录 引言环境准备智能交通管理系统基础代码实现&#xff1a;实现智能交通管理系统 4.1 数据采集模块 4.2 数据处理与分析 4.3 控制系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;交通管理与优化问题解决方案与优化收尾与总结 1. 引言 智能交通管理系统利用STM32嵌…

【对顶堆 优先队列】295. 数据流的中位数

本文涉及知识点 对顶堆 优先队列 LeetCode295. 数据流的中位数 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 …

网络漏洞挖掘实测报告

关于作者&#xff1a;个人主页 网络漏洞挖掘实测报告 一、前言 网络漏洞挖掘是信息安全领域中至关重要的一环。通过挖掘和修复漏洞&#xff0c;可以有效地保护系统免受潜在的攻击和破坏。本报告旨在记录一次完整的网络漏洞挖掘实测过程&#xff0c;包括实施方法、过程、结果以…

免杀笔记 ----> ShellCode Loader !!!

学了那么久的前置知识&#xff0c;终于到了能上线的地方了&#xff01;&#xff01;&#xff01; 不过这里还没到免杀的部分&#xff0c;距离bypass一众的杀毒软件还有很长的路要走&#xff01;&#xff01; 目录 1.ShellCode 2.ShellCode Loader的概念 3.可读可写可…

Android实现获取本机手机号码

和上次获取设备序列号一样&#xff0c;仍然是通过无障碍服务实现&#xff0c;在之前的代码基础上做了更新。代码和demo如下&#xff1a; package com.zwxuf.lib.devicehelper;import android.accessibilityservice.AccessibilityService; import android.app.Activity; import…