基于 PyTorch + ResNet-18 的验证码交互式识别系统

基于 PyTorch + ResNet-18 的验证码交互式识别系统

从模型训练到 PyQt5 可视化界面,实现开箱即用的 4 位验证码自动识别

在这里插入图片描述

一、项目背景

验证码(CAPTCHA)是互联网中最常见的安全机制之一。随着深度学习的快速发展,基于卷积神经网络的验证码识别已经成为一个经典的研究方向。本文介绍一个完整的验证码识别系统,涵盖数据准备、模型设计、自动化训练、PyQt5 交互界面四大模块,最终实现开箱即用的验证码识别工具。

系统特性

  • 字符集:数字 0-9、小写字母 a-z、大写字母 A-Z,共 62 类
  • 验证码长度:4 位字符
  • 模型架构:ResNet-18 + 通道注意力机制 + 双池化特征融合
  • 测试集准确率:97.1%
  • 推理速度:GPU 上单张推理 < 10ms
  • 交互界面:PyQt5 实现,支持单张/批量识别、置信度可视化

二、数据集

数据规模

数据集 训练集 增强训练集 测试集 总计
图片数量 3,988 15,952 1,000 20,940

数据生成

验证码图片通过 Python captcha 库自动生成,每张包含 4 个随机字符,字符从 62 类字符集中均匀采样。图片尺寸在 100~140(宽)× 45~65(高)之间随机扰动,模拟真实场景中的尺寸变化。

数据增强

使用 Augmentor 库对训练集进行离线增强,包括:

  • 随机畸变:网格宽度 6、高度 2、幅度 3
  • 倾斜变换:左右倾斜、上下倾斜、角点倾斜
  • 随机旋转:±1° 范围内的随机旋转

增强后训练数据量扩大至原始的 4 倍,有效提升模型泛化能力。

标签编码

0-9  → 类别 0~9
a-z  → 类别 10~35
A-Z  → 类别 36~61

三、模型架构

整体设计

本项目实验了 8 种不同的 CNN 架构,最终选用 ResNet-18 + 通道注意力 + 双池化融合 作为最优方案。

输入图片 (3×40×120)
    │
    ▼
ResNet-18 Backbone (截取至 layer3, 输出 256 通道特征图)
    │
    ▼
通道注意力模块 (Channel Attention)
    │
    ▼
双池化融合 (AvgPool + MaxPool → Concat → 1×1 Conv)
    │
    ▼
4 个独立分类头 (每个输出 62 维向量)

通道注意力机制

借鉴 CBAM(Convolutional Block Attention Module)中的通道注意力思想,对特征图的每个通道计算注意力权重:

# 分别对特征图做全局平均池化和全局最大池化
avg_out = FC2(ReLU(FC1(AvgPool(x))))   # 平均路径
max_out = FC2(ReLU(FC1(MaxPool(x))))   # 最大路径
# 两条路径结果相加后通过 Sigmoid 得到通道权重
channel_attention = Sigmoid(avg_out + max_out)
# 用注意力权重对原始特征图进行逐通道加权
x = x * channel_attention

其中 FC1 将通道数压缩至 1/8(256→32),FC2 再恢复至原始通道数(32→256),使用 1×1 卷积实现。

双池化特征融合

# 分别做全局平均池化和全局最大池化
avg_feat = AdaptiveAvgPool2d(1)(x)   # 形状: [B, 256, 1, 1]
max_feat = AdaptiveMaxPool2d(1)(x)   # 形状: [B, 256, 1, 1]
# 在通道维度拼接后通过 1×1 卷积降维
fused = Conv1x1(Cat([avg_feat, max_feat], dim=1))  # [B, 256, 1, 1]

平均池化保留全局语义信息,最大池化保留最显著的局部特征,两者互补融合后通过 1×1 卷积将 512 维降至 256 维。

多任务分类头

验证码的 4 个字符相互独立,因此采用 4 个独立的分类头,每个头接收相同的 256 维特征向量,输出 62 维的字符概率分布:

fc1 = Sequential(Dropout(0.5), Linear(256, 62))  # 第1个字符
fc2 = Sequential(Dropout(0.5), Linear(256, 62))  # 第2个字符
fc3 = Sequential(Dropout(0.5), Linear(256, 62))  # 第3个字符
fc4 = Sequential(Dropout(0.5), Linear(256, 62))  # 第4个字符

四、训练策略

超参数配置

参数 值
优化器 Adam (lr=1e-3, weight_decay=1e-4)
学习率调度 CosineAnnealingLR (T_max=80, eta_min=1e-6)
批大小 64
最大 Epoch 80
早停策略 连续 20 个 epoch 无提升则停止
损失函数 4 路 CrossEntropyLoss 求和
硬件 NVIDIA GeForce RTX 4060 Laptop GPU

训练过程

训练采用原始数据 + 增强数据交替训练的策略:每个 epoch 先遍历原始训练集,再遍历增强训练集,充分利用两种数据分布。

训练曲线如下:

Epoch Loss 测试准确率 备注
1 16.47 0.00% 随机初始化
3 4.55 47.80% 快速收敛
5 0.99 81.50%
10 0.39 92.20%
20 0.21 94.70%
28 0.14 95.40%
35 0.09 96.20%
47 0.04 97.10% 最佳模型

模型在 47 个 epoch 达到最佳准确率 97.1%,随后触发早停机制自动终止训练,总耗时约 200 分钟。

评估指标

测试集上的准确率定义为 4 个字符全部识别正确 才算一次正确预测,这是一个非常严格的评估标准。97.1% 的准确率意味着在 1000 张测试图片中,有 971 张被完全正确地识别。


五、PyQt5 交互界面

界面布局

系统采用左右分栏布局:

左侧面板:

  • 模型状态区:显示当前加载的模型架构、权重文件、运行设备
  • 图片显示区:预览待识别的验证码图片
  • 操作按钮:打开图片、识别、批量识别

右侧面板:

  • 识别结果区:大字体显示预测的 4 位验证码
  • 置信度详情:每个字符的 Top-3 预测及概率
  • 运行日志:深色终端风格的实时日志输出

在这里插入图片描述

核心功能

1. 自动加载模型

程序启动后自动从 ./weights/best_model.pth 加载训练好的最优模型,无需手动配置:

QTimer.singleShot(300, self.auto_load_model)

2. 单张识别

用户选择一张验证码图片后,点击「识别」按钮即可得到预测结果。系统会显示:

  • 4 位验证码文字(大字体)
  • 每个字符的预测置信度
  • 每个字符的 Top-3 备选结果

颜色反馈机制:

  • 🟢 绿色:平均置信度 > 80%(高可信)
  • 橙色:平均置信度 50%~80%(中等可信)
  • 🔴 红色:平均置信度 < 50%(低可信,需人工复核)

3. 批量识别

选择包含多张验证码的文件夹,系统在后台线程中批量处理:

  • 实时进度条显示处理进度
  • 完成后显示总体准确率
  • 自动列出所有识别错误的样本(真实标签 vs 预测结果)

4. 后台线程

批量识别使用 QThread 在后台执行,避免阻塞 UI 主线程:

class BatchPredictWorker(QThread):
    progress = pyqtSignal(int, int)  # 当前进度, 总数
    result = pyqtSignal(list)         # 识别结果列表
    finished = pyqtSignal()           # 完成信号

推理流程

# 1. 图像预处理
img_tensor = transform(pil_image).unsqueeze(0).to(device)
#    Resize(40×120) → ToTensor → Normalize(0.5, 0.5)

# 2. 模型推理
with torch.no_grad():
    y1, y2, y3, y4 = model(img_tensor)

# 3. 取每个字符的最高概率类别
preds = [y1.argmax(1).item(), y2.argmax(1).item(),
         y3.argmax(1).item(), y4.argmax(1).item()]

# 4. 解码为字符串
result = LabeltoStr(preds)  # 例如: "0bZ7"

六、关键代码实现

模型定义

class Res18Captcha(nn.Module):
    """ResNet-18 + 通道注意力 + 双池化融合"""
    def __init__(self, class_num=62):
        super().__init__()
        # 使用 torchvision 的 ResNet-18,截取至 layer3
        model_ft = ResNet(BasicBlock, [2, 2, 2, 2])
        self.base_model = nn.Sequential(
            *list(model_ft.children())[:-3]
        )

        # 通道注意力模块
        self.avgpool = nn.AdaptiveAvgPool2d(1)
        self.maxpool = nn.AdaptiveMaxPool2d(1)
        self.sign = nn.Sigmoid()
        self.a_fc1 = nn.Conv2d(256, 32, 1, bias=False)
        self.a_relu = nn.ReLU()
        self.a_fc2 = nn.Conv2d(32, 256, 1, bias=False)

        # 双池化融合
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)
        self.reduce_layer = nn.Conv2d(512, 256, 1)

        # 4 个分类头
        self.fc1 = nn.Sequential(nn.Dropout(0.5), nn.Linear(256, class_num))
        self.fc2 = nn.Sequential(nn.Dropout(0.5), nn.Linear(256, class_num))
        self.fc3 = nn.Sequential(nn.Dropout(0.5), nn.Linear(256, class_num))
        self.fc4 = nn.Sequential(nn.Dropout(0.5), nn.Linear(256, class_num))

    def forward(self, x):
        bs = x.shape[0]
        x = self.base_model(x)

        # 通道注意力
        avg_out = self.a_fc2(self.a_relu(self.a_fc1(self.avgpool(x))))
        max_out = self.a_fc2(self.a_relu(self.a_fc1(self.maxpool(x))))
        ca = self.sign(avg_out + max_out)
        x = x * ca.expand_as(x)

        # 双池化融合
        xx1 = self.avg_pool(x)
        xx2 = self.max_pool(x)
        x = torch.cat([xx1, xx2], dim=1)
        x = self.reduce_layer(x).view(bs, -1)

        return self.fc1(x), self.fc2(x), self.fc3(x), self.fc4(x)

自动化训练脚本

# 训练循环核心逻辑
for epoch in range(total_epochs):
    model.train()
    for loader in [train_loader, auged_loader]:
        for imgs, labels in loader:
            y1, y2, y3, y4 = model(imgs)
            loss = (criterion(y1, labels[:, 0]) +
                    criterion(y2, labels[:, 1]) +
                    criterion(y3, labels[:, 2]) +
                    criterion(y4, labels[:, 3]))
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

    scheduler.step()
    acc = evaluate(model, test_loader)

    if acc > best_acc:
        best_acc = acc
        torch.save(model.state_dict(), "best_model.pth")

七、项目结构

captchaPytorch/
├── auto_train.py          # 自动化训练脚本
├── gui.py                 # PyQt5 交互式界面
├── config/
│   └── parameters.py      # 全局超参数配置
── model/
│   ├── model.py           # 基础 ResNet 实现
│   ├── res18.py           # ResNet-18 + 注意力
│   ├── captchaNet.py      # 简单 CNN 基线
│   ├── dense.py           # DenseNet-121
│   ├── senet.py           # SE-ResNet
│   ├── dualpooling.py     # 双池化 ResNet
│   ├── BNNeck.py          # BNNeck 模型
│   └── IBN.py             # IBN-Net
├── lib/
│   ├── dataset.py         # 数据集加载
│   ├── generate_captcha.py# 验证码生成
│   ├── optimizer.py       # 自定义优化器
│   ├── scheduler.py       # 学习率调度器
│   ── center_loss.py     # Center Loss
├── utils/
│   ├── dataAug.py         # 数据增强管线
│   ├── Visdom.py          # 可视化封装
│   ── ...
├── weights/
│   └── best_model.pth     # 最优模型权重 (97.1%)
└── data/dataset6/
    ├── train/             # 3,988 张训练图片
    ├── auged_train/       # 15,952 张增强图片
    ── test/              # 1,000 张测试图片

八、使用方法

环境要求

Python 3.7+
PyTorch 1.x
torchvision
PyQt5
Pillow
numpy

快速开始

1. 训练模型

python auto_train.py

脚本会自动加载 dataset6 数据集,训练 ResNet-18 模型,最佳权重保存至 ./weights/best_model.pth。

2. 启动界面

python gui.py

界面启动后自动加载模型,无需额外配置。

3. 识别验证码

  • 点击「📂 打开图片」选择验证码图片
  • 点击「🔍 识别」查看结果
  • 点击「📦 批量识别」对文件夹中的所有图片进行批量测试

九、总结

本文实现了一个完整的验证码识别系统,主要贡献包括:

  1. 模型设计:在 ResNet-18 基础上引入通道注意力机制和双池化特征融合,在严格的 4 字符全对评估标准下达到 97.1% 的准确率。

  2. 自动化训练:编写 auto_train.py 实现一键训练,包含数据加载、模型训练、自动评估、早停机制和最优模型保存。

  3. 交互界面:基于 PyQt5 开发了开箱即用的可视化识别工具,支持单张/批量识别、置信度可视化、Top-3 备选结果展示和实时日志输出。

  4. 工程实践:采用后台线程处理批量任务避免 UI 卡顿,颜色编码反馈识别置信度,错误样本自动标记便于分析。

该系统可直接应用于各类 4 位字符验证码的识别场景,也可作为学习 CNN 图像分类、注意力机制、PyQt5 界面开发的参考项目。


项目代码基于 PyTorch 框架,所有模型权重和训练数据均已包含在项目中。

代码下载路径

有训练代码和训练好的模型以及训练过程,提供数据,提供GUI界面代码
项目完整文件下载请见演示与介绍视频的简介处给出:➷➷➷
https://www.bilibili.com/video/BV1tsnG6TE4o/
在这里插入图片描述

导航