基于深度学习的滑动验证码缺口识别教程

简介

做爬虫或自动化测试的朋友,应该都被极验这类滑动验证码卡过——传统的边缘检测、轮廓匹配找缺口,要么死磕验证码的固定光照、边框样式,要么一换网站就歇菜。

为什么不用深度学习? 目标检测模型能自动“看”懂缺口的特征(不管什么背景、什么缺口形状),泛化能力比传统方法强太多。本文就用经典的 YOLOv3 手把手带你跑通全流程:从数据收集、标注到训练、部署,最后输出一个能直接用的缺口定位API。

准备工作

2.1 克隆代码仓库

本次直接用开源社区维护的 YOLOv3 适配版仓库,省得从零搭框架:

git clone https://github.com/Python3WebSpider/DeepLearningSlideCaptcha2.git
cd DeepLearningSlideCaptcha2

2.2 安装依赖

仓库的 requirements.txt 已经配好核心组件,但为了避免国内网络和版本冲突的坑,推荐先做这两步再装全量依赖

  1. Python 环境:用 conda 建个 3.8-3.11 的虚拟环境(PyTorch 2.x 也兼容本仓库,但得改一下 train/detect 脚本的 PyTorch 版本相关代码,小白直接用 3.8+ PyTorch 1.8.x-1.13.x 就行)。
  2. 换 PyPI 镜像:永久换源或临时加 -i https://pypi.tuna.tsinghua.edu.cn/simple

正式安装:

pip install -r requirements.txt

目标检测基础:为什么选 YOLOv3?

滑动验证码缺口识别,本质是「单目标检测」任务——找一张图片里唯一的“缺口矩形框”。

目前主流目标检测算法分两类:

  • Two-Stage(两步走):比如 R-CNN 系列,先“撒网”提几千个可能有物体的候选框,再逐个分类、修正位置。优点是准,但速度太慢,不适合自动化场景的高频请求。
  • One-Stage(一步走):比如 YOLO、SSD,直接把图片切成网格,每个网格预测几个框和置信度,最后过滤得到最优解。优点是飞一样快准度稍差但足够用在缺口识别这种场景

本教程选 YOLOv3——它在 YOLO 系列里属于“万金油”:速度比 v1/v2 稳,准度比 v1 高一大截,部署也比较成熟。

数据准备:深度学习的“燃料”

4.1 自动收集验证码图片

找缺口的第一步,是有足够多、足够杂的验证码图。仓库附了用 Selenium 爬取极验公开演示站(https://captcha1.scrape.center/)的脚本 collect.py

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import os

# 自定义配置:收集数量、演示站地址、保存路径
COUNT = 100  # 建议至少200-500张,准度才稳
URL = 'https://captcha1.scrape.center/'
OUTPUT_DIR = 'data/captcha/images'

if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)

for i in range(COUNT):
    # 每次循环开/关浏览器,避免会话污染导致验证码不刷新
    driver = webdriver.Chrome()
    try:
        driver.get(URL)
        wait = WebDriverWait(driver, 10)
        # 先点登录触发验证码
        login_btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-primary')))
        login_btn.click()
        # 等待背景图完全加载
        captcha_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_slicebg')))
        time.sleep(2)  # 极验有时候会延迟加载,多等1-2秒
        # 只截图背景图部分,减少无关干扰
        captcha_bg.screenshot(f'{OUTPUT_DIR}/captcha_{i}.png')
    finally:
        driver.quit()

💡 小提示:如果爬取自己的目标网站,一定要修改 CSS 选择器到对应的背景图元素,收集数量越多、缺口/背景越多样(不同光照、颜色、缺口形状),模型泛化能力越好。

4.2 手动标注缺口位置

YOLOv3 需要“有标注的图片”才能训练——也就是告诉模型“哪张图里的哪个矩形框是缺口”。推荐用轻量开源的 labelImg 工具:

pip install labelImg
labelImg

标注步骤要严格统一,否则模型训练会崩:

  1. 打开工具后,先切换到 PascalVOC 格式(保存XML)(后续会转 YOLO 格式,这样步骤清晰)。
  2. 设置默认标签:在工具的「Edit → Predefined Classes」里只加一行 target(所有缺口都叫这个)。
  3. 打开收集好的 data/captcha/images 目录,同时设置「Change Save Dir」到新建的 data/captcha/annotations 目录。
  4. 每张图用「Create RectBox」工具精准框选缺口的四个角,标签选默认的 target,保存后直接按「D」跳到下一张。

4.3 转成 YOLOv3 需要的格式

PascalVOC 的 XML 格式存的是绝对坐标的左上角(xmin, ymin)和右下角(xmax, ymax),但 YOLOv3 需要的是:

  • 类别ID(这里只有1类,所以ID是0)
  • 相对于整张图的归一化中心点坐标(x_center, y_center)
  • 相对于整张图的归一化宽高(box_width, box_height)

仓库也附了转换脚本 convert.py

import xmltodict
import os

# 目录配置
XML_DIR = 'data/captcha/annotations'
LABEL_DIR = 'data/captcha/labels'
if not os.path.exists(LABEL_DIR):
    os.makedirs(LABEL_DIR)

def parse_single_xml(xml_path):
    with open(xml_path, 'r', encoding='utf-8') as f:
        xml_dict = xmltodict.parse(f.read())
    
    # 取图片尺寸(用于归一化)
    img_w = int(xml_dict['annotation']['size']['width'])
    img_h = int(xml_dict['annotation']['size']['height'])
    
    # 取缺口的绝对坐标
    bndbox = xml_dict['annotation']['object']['bndbox']
    xmin = int(bndbox['xmin'])
    ymin = int(bndbox['ymin'])
    xmax = int(bndbox['xmax'])
    ymax = int(bndbox['ymax'])
    
    # 转 YOLO 归一化格式
    x_center = ((xmin + xmax) / 2) / img_w
    y_center = ((ymin + ymax) / 2) / img_h
    box_w = (xmax - xmin) / img_w
    box_h = (ymax - ymin) / img_h
    
    return [x_center, y_center, box_w, box_h]

# 批量转换所有XML
for filename in os.listdir(XML_DIR):
    if not filename.endswith('.xml'):
        continue
    # 转换
    yolo_data = parse_single_xml(os.path.join(XML_DIR, filename))
    # 保存为同名txt
    with open(os.path.join(LABEL_DIR, filename.replace('.xml', '.txt')), 'w', encoding='utf-8') as f:
        f.write(f'0 {" ".join(map(str, yolo_data))}')

模型训练

5.1 下载预训练权重

从零训练 YOLOv3 需要几百万张图和几周时间,我们用 COCO 数据集预训练的权重(已经学会识别常见物体的基础特征),再用自己的验证码数据“微调”,快的话几十分钟就能出效果。

仓库附了下载脚本(Linux/Mac 用 bash scripts/download_pretrained.sh,Windows 可以直接去 YOLOv3 官方权重下载页 下,放到 weights 目录下)。

5.2 开始微调训练

训练前,先确认一下 data/captcha.yaml 文件里的类别数(nc)、图片路径、标签路径、类别名称 对不对(默认是1类,路径也配好了)。

然后直接运行微调脚本:

bash scripts/train.sh

💡 训练参数说明(可以在 scripts/train.sh 里改):

  • --img-size:输入图片的尺寸(默认640,显存不够可以改成416)。
  • --batch-size:每次训练用的图片数(显存越大,越大越好,默认8)。
  • --epochs:训练轮数(默认100,验证集损失不再下降时可以提前停止)。
  • --data:刚才确认的 captcha.yaml 路径。
  • --weights:预训练权重路径。

5.3 用 TensorBoard 看训练效果

训练过程中,脚本会自动把损失、准确率等指标存到 logs 目录,用 TensorBoard 可视化很方便:

tensorboard --logdir=logs

打开浏览器访问 http://localhost:6006,重点看这两个曲线:

  • val/box_loss:验证集的框损失(越低越好,降到0.05-0.1左右基本就够用了)。
  • val/mAP_0.5:验证集的IoU=0.5时的平均精度(越高越好,单目标检测下到0.9以上就很稳)。

模型测试

6.1 准备测试数据

把没用来训练的验证码图片(比如收集的最后20%的图)放到 data/captcha/test 目录。

6.2 运行检测脚本

训练好的最优模型会存到 checkpoints/best.pt,直接用仓库的检测脚本测试:

bash scripts/detect.sh

检测结果(带框的图片、缺口坐标的txt文件)会自动存到 data/captcha/result 目录。

模型部署:做一个能用的API

光有检测脚本不够,实际爬虫/自动化测试里,我们需要一个能接收图片、返回缺口左上角x坐标(滑动距离主要看x) 的HTTP API。这里用轻量的 FastAPI 快速实现:

from fastapi import FastAPI, UploadFile, File, HTTPException
from PIL import Image
import io
import torch
from models.yolo import Model
from utils.general import non_max_suppression, scale_coords
from utils.augmentations import letterbox
import numpy as np

app = FastAPI(title="滑动验证码缺口识别API", version="1.0")

# ------------------ 全局加载模型(只加载一次,避免每次请求都加载) ------------------
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 1. 加载模型结构
model = Model(cfg='models/yolov3.yaml', ch=3, nc=1).to(DEVICE)
# 2. 加载最优权重
checkpoint = torch.load('checkpoints/best.pt', map_location=DEVICE)
model.load_state_dict(checkpoint['model'])
# 3. 切换到评估模式(关闭dropout、batchnorm的训练特性)
model.eval()

def preprocess(image: Image.Image, img_size: int = 640):
    """YOLOv3 标准预处理:letterbox缩放 + 转tensor + 归一化"""
    # 1. 转RGB(防止上传的是灰度图或RGBA图)
    img = image.convert('RGB')
    # 2. letterbox缩放:保持长宽比,填充黑边到指定尺寸
    img = letterbox(img, new_shape=img_size, auto=False)[0]
    # 3. 转 numpy -> (H, W, C) -> (C, H, W) -> 转 torch tensor
    img = np.asarray(img).transpose(2, 0, 1)
    img = torch.from_numpy(img).float().to(DEVICE)
    # 4. 归一化到0-1
    img /= 255.0
    # 5. 加一个batch维度(YOLOv3要求输入是batch的形式)
    if img.ndimension() == 3:
        img = img.unsqueeze(0)
    return img, image.size

@app.post("/predict", summary="识别滑动验证码缺口")
async def predict(file: UploadFile = File(...)):
    # 1. 读取上传的图片
    try:
        image_bytes = await file.read()
        image = Image.open(io.BytesIO(image_bytes))
    except Exception:
        raise HTTPException(status_code=400, detail="上传的不是有效图片")
    
    # 2. 预处理
    img_tensor, original_size = preprocess(image)
    
    # 3. 预测(不计算梯度,加速推理)
    with torch.no_grad():
        pred = model(img_tensor)[0]  # 取第一个输出(检测结果)
        # 4. 非极大值抑制(NMS):过滤重叠的框,只留置信度最高的
        pred = non_max_suppression(pred, conf_thres=0.5, iou_thres=0.5)[0]
    
    # 5. 后处理:把letterbox缩放后的框坐标还原成原图的
    if pred is not None and len(pred):
        pred = scale_coords(img_tensor.shape[2:], pred[:, :4], original_size).round()
        # 取第一个(也是唯一的)框的坐标
        x1, y1, x2, y2 = pred[0].tolist()
        return {
            "success": True,
            "data": {
                "x1": int(x1),  # 缺口左上角x坐标(滑动距离就是这个值!)
                "y1": int(y1),
                "x2": int(x2),
                "y2": int(y2),
                "width": int(x2 - x1),
                "height": int(y2 - y1)
            }
        }
    else:
        raise HTTPException(status_code=404, detail="未检测到缺口")

启动API(用uvicorn,FastAPI官方推荐的ASGI服务器):

uvicorn deploy:app --reload --host 0.0.0.0 --port 8000

启动后可以访问 http://localhost:8000/docs 打开 FastAPI 自带的交互式文档,直接上传图片测试接口!

优化建议

如果模型在你的目标网站上效果不好,可以试试这几个优化方向:

  1. 数据增强:在 data/captcha.yaml 里打开或增加数据增强(比如随机翻转、裁剪、亮度/对比度调整),或者自己写脚本生成更多样化的样本。
  2. 换更新的 YOLO 版本:比如 YOLOv5、YOLOv8,它们的准度和速度都比 YOLOv3 好,仓库架构也更友好。
  3. 部署加速:把模型转成 ONNX 或 TensorRT 格式,推理速度能提升几倍到几十倍。
  4. 主动学习:把模型预测置信度低的样本(比如conf_thres在0.5-0.7之间的)手动标注后加入训练集重新训练,效果会越来越好。

总结

本文用 YOLOv3 跑通了滑动验证码缺口识别的全流程:从数据收集、标注到训练、部署,最后输出了一个能直接用的 API。相比传统方法,深度学习方案的泛化能力强太多,换个网站只要重新收集/标注少量数据微调一下就能用。

完整代码可以直接看原仓库:DeepLearningSlideCaptcha2