基于 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. 识别验证码
- 点击「📂 打开图片」选择验证码图片
- 点击「🔍 识别」查看结果
- 点击「📦 批量识别」对文件夹中的所有图片进行批量测试
九、总结
本文实现了一个完整的验证码识别系统,主要贡献包括:
-
模型设计:在 ResNet-18 基础上引入通道注意力机制和双池化特征融合,在严格的 4 字符全对评估标准下达到 97.1% 的准确率。
-
自动化训练:编写
auto_train.py实现一键训练,包含数据加载、模型训练、自动评估、早停机制和最优模型保存。 -
交互界面:基于 PyQt5 开发了开箱即用的可视化识别工具,支持单张/批量识别、置信度可视化、Top-3 备选结果展示和实时日志输出。
-
工程实践:采用后台线程处理批量任务避免 UI 卡顿,颜色编码反馈识别置信度,错误样本自动标记便于分析。
该系统可直接应用于各类 4 位字符验证码的识别场景,也可作为学习 CNN 图像分类、注意力机制、PyQt5 界面开发的参考项目。
项目代码基于 PyTorch 框架,所有模型权重和训练数据均已包含在项目中。
代码下载路径
有训练代码和训练好的模型以及训练过程,提供数据,提供GUI界面代码
项目完整文件下载请见演示与介绍视频的简介处给出:➷➷➷
https://www.bilibili.com/video/BV1tsnG6TE4o/

浙公网安备 33010602011771号