百趣云 百趣云的博客

滑块验证码对抗(上):缺口识别的 OpenCV 实战

滑块验证的两个子问题

滑块验证码(极验、顶象、各厂自研)的对抗拆成两个独立子问题:缺口识别(算出滑块要拖多少像素)和轨迹生成(怎么拖得像人)。本文讲第一个。

缺口识别的主流方案对比

方案准确率成本适用
模板匹配85-95%零成本缺口是固定形状贴图
边缘检测+轮廓90%+零成本缺口有明显边缘
深度学习(YOLO等)98%+要训练干扰多、缺口形态多变
打码平台95%+按次付费不想维护任何代码

工程上先上模板匹配,准确率不够再升级——大多数滑块的缺口就是一张半透明贴图,模板匹配足够。

模板匹配:标准实现

滑块验证给两张图:背景图(有缺口)和滑块图(缺口贴图)。核心思路是在背景图上滑动滑块图,找匹配度最高的位置

import cv2
import numpy as np

def find_gap(bg_path, slider_path):
    bg = cv2.imread(bg_path)
    slider = cv2.imread(slider_path)

    # 1. 滑块图处理:提取缺口轮廓(滑块图通常带透明通道或纯色背景)
    # 边缘检测强化轮廓
    bg_edge = cv2.Canny(bg, 100, 200)
    slider_edge = cv2.Canny(slider, 100, 200)

    # 2. 模板匹配
    result = cv2.matchTemplate(bg_edge, slider_edge, cv2.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv2.minMaxLoc(result)

    # max_loc[0] 就是缺口左上角 x 坐标
    return max_loc[0], max_val

x, confidence = find_gap('bg.png', 'slider.png')
print(f'缺口 x=✅, 置信度={confidence}')

精髓一:五个提升准确率的细节

  1. 先边缘检测再匹配。直接在原图上匹配会被背景纹理干扰(比如背景图里恰好有相似色块),Canny 边缘化之后匹配的是"形状",抗干扰能力强一个量级
  2. 滑块图要去背景。滑块贴图常带白色/透明底,直接匹配会把底色也算进去。用透明通道做 mask,或阈值化抠出滑块本体:
# 有 alpha 通道时
if slider.shape[2] == 4:
    mask = slider[:, :, 3]
    result = cv2.matchTemplate(bg_edge, slider_edge, cv2.TM_CCORR_NORMED, mask=mask)
  1. 缩放对齐。页面显示的图和原图尺寸可能不同(响应式缩放),识别前确认图片实际像素与显示像素的比例,最终位移量要按比例换算
  2. 起始偏移。滑块初始位置不一定在 x=0(有的滑块条有左边距),最终位移 = 缺口x - 滑块初始x,别直接拿缺口 x 当拖动距离
  3. 置信度兜底。max_val 低于阈值(比如 0.5)说明匹配失败,触发重试(刷新验证码再来),别把错误结果提交上去浪费验证次数

精髓二:没有滑块图的变种怎么破

有些滑块只给一张背景图(缺口直接挖在背景里),没有独立滑块图。这时用缺口特征检测

def find_gap_no_template(bg_path):
    bg = cv2.imread(bg_path)
    gray = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
    edge = cv2.Canny(gray, 100, 200)

    # 找轮廓,筛选"像缺口"的:面积适中、位置在右半区、长宽比接近1
    contours, _ = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    for c in contours:
        x, y, w, h = cv2.boundingRect(c)
        area = cv2.contourArea(c)
        if 800 < area < 5000 and x > bg.shape[1] * 0.3 and 0.5 < w/h < 2:
            return x
    return None

更稳的方案是双图对比:抓"无缺口的原始背景图"和"有缺口的背景图"(很多滑块服务会返回两张),像素级 diff,差异区域就是缺口:

diff = cv2.absdiff(bg_with_gap, bg_original)
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
# thresh 里最大的白色区域即缺口

精髓三:识别只是入场券

把缺口识别做到 99% 准确率,提交后还是可能失败——因为滑块验证的核心校验在轨迹不在结果

  • 匀速直线拖动 = 机器,必挂
  • 瞬间到位(耗时 50ms)= 机器,必挂
  • 轨迹点间隔完全均匀 = 机器,必挂

识别对了只是"答案对了",轨迹要像人才能"交卷"。轨迹生成是下一个子问题(下篇详述)。

总结

  1. 缺口识别先上模板匹配:Canny 边缘化 + matchTemplate
  2. 五个细节:边缘化、去背景、缩放对齐、起始偏移、置信度兜底
  3. 无滑块图变种用轮廓筛选或双图 diff
  4. 识别只是入场券,轨迹生成才是核心校验

交流微信:run1255

By 百趣云 阅读量:8 On