基于深度学习的滑动验证码缺口识别教程
简介
做爬虫或自动化测试的朋友,应该都被极验这类滑动验证码卡过——传统的边缘检测、轮廓匹配找缺口,要么死磕验证码的固定光照、边框样式,要么一换网站就歇菜。
为什么不用深度学习? 目标检测模型能自动“看”懂缺口的特征(不管什么背景、什么缺口形状),泛化能力比传统方法强太多。本文就用经典的 YOLOv3 手把手带你跑通全流程:从数据收集、标注到训练、部署,最后输出一个能直接用的缺口定位API。
准备工作
2.1 克隆代码仓库
本次直接用开源社区维护的 YOLOv3 适配版仓库,省得从零搭框架:
git clone https://github.com/Python3WebSpider/DeepLearningSlideCaptcha2.git
cd DeepLearningSlideCaptcha2
2.2 安装依赖
仓库的 requirements.txt 已经配好核心组件,但为了避免国内网络和版本冲突的坑,推荐先做这两步再装全量依赖:
- Python 环境:用 conda 建个 3.8-3.11 的虚拟环境(PyTorch 2.x 也兼容本仓库,但得改一下 train/detect 脚本的 PyTorch 版本相关代码,小白直接用 3.8+ PyTorch 1.8.x-1.13.x 就行)。
- 换 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
标注步骤要严格统一,否则模型训练会崩:
- 打开工具后,先切换到 PascalVOC 格式(保存XML)(后续会转 YOLO 格式,这样步骤清晰)。
- 设置默认标签:在工具的「Edit → Predefined Classes」里只加一行
target(所有缺口都叫这个)。
- 打开收集好的
data/captcha/images 目录,同时设置「Change Save Dir」到新建的 data/captcha/annotations 目录。
- 每张图用「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类,路径也配好了)。
然后直接运行微调脚本:
💡 训练参数说明(可以在 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,直接用仓库的检测脚本测试:
检测结果(带框的图片、缺口坐标的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 自带的交互式文档,直接上传图片测试接口!
优化建议
如果模型在你的目标网站上效果不好,可以试试这几个优化方向:
- 数据增强:在
data/captcha.yaml 里打开或增加数据增强(比如随机翻转、裁剪、亮度/对比度调整),或者自己写脚本生成更多样化的样本。
- 换更新的 YOLO 版本:比如 YOLOv5、YOLOv8,它们的准度和速度都比 YOLOv3 好,仓库架构也更友好。
- 部署加速:把模型转成 ONNX 或 TensorRT 格式,推理速度能提升几倍到几十倍。
- 主动学习:把模型预测置信度低的样本(比如conf_thres在0.5-0.7之间的)手动标注后加入训练集重新训练,效果会越来越好。
总结
本文用 YOLOv3 跑通了滑动验证码缺口识别的全流程:从数据收集、标注到训练、部署,最后输出了一个能直接用的 API。相比传统方法,深度学习方案的泛化能力强太多,换个网站只要重新收集/标注少量数据微调一下就能用。
完整代码可以直接看原仓库:DeepLearningSlideCaptcha2