
简介本资源是一套基于深度学习实现的验证码识别OCRPython 源码项目采用 ResNet 与 DenseNet 两种经典卷积网络算法面向计算机、人工智能、信息安全等专业的在校学生、教师及企业开发者可用于课程设计、毕业设计、大作业或入门级项目立项演示帮助理解图像分类与字符识别的基本流程。压缩包共 1084 个文件以 1071 个 png 验证码样本图片为主另含 5 个 xml 标注文件、4 个 py 源码脚本、1 个 ttf 字体及 iml、name、md 等工程配置文件整体约 8.84MB结构清晰便于直接运行与二次开发。项目代码均经过测试运行成功读者可据此掌握数据标注、模型搭建、训练调参与识别推理的完整链路并在此基础上修改网络结构或扩充数据集实现其他字符识别功能。目前已有 423 人学习关注适合希望快速上手深度学习 OCR 实践的读者参考借鉴。1. 验证码识别模型实战ResNetDenseNet 双骨干的 Python 源码拆解验证码识别这个方向做课程设计的人几乎绕不开。你手上如果有一个基于深度学习实现的验证码识别模型(OCR)python源码(ResNetDenseNet算法).zip里面躺着Captcha.iml、README.md、.name和几张样本图wmpmp.png、pe4xn.png、ydd3g.png、m4g8g.png、24f6w.png、2yggg.png、gc83b.png那它本质上是一套「多字符分类 卷积特征提取」的完整工程骨架而不是一个调包脚本。它解决的是给定一张 5 位左右的字符验证码输出对应字符串。适合计算机、信息安全、人工智能方向的在校学生做课程设计或毕设起步也适合想从 MNIST 过渡到真实 OCR 任务的工程师练手。下面我按「资源是什么 → 怎么跑起来 → 坑在哪 → 怎么改」的顺序把这份源码拆开讲。2. 从样本图到标签验证码识别的数据管线怎么搭2.1 为什么验证码识别不能直接套通用 OCR通用 OCR比如 Tesseract、PaddleOCR在印刷体文档上表现很好但验证码天生是「反 OCR」的字符有旋转、粘连、干扰线、背景噪点字符间距还不固定。你直接拿 Tesseract 去识别wmpmp.png这种图大概率输出一堆乱码。所以这份源码走的是「自训练 CNN 分类器」路线把验证码识别拆成固定长度多字符分类问题——假设验证码固定 5 位就输出 5 个 softmax 分支每个分支对应一个字符位。这个思路的好处是不需要 CTC 解码不需要检测框网络结构简单训练收敛快。代价是验证码长度必须固定字符集必须提前确定。源码里用 ResNet 和 DenseNet 两种骨干做对比本质是在「深层残差连接」和「密集特征复用」之间做选型实验。2.2 数据管线的三个关键参数拿到wmpmp.png这类样本后第一步是构建「图片 → 标签向量」的映射。常见做法是文件名即标签比如wmpmp.png对应字符串wmpmp。然后建立字符表import os import numpy as np from PIL import Image # 字符集根据你的验证码实际出现的字符来定 CHARS abcdefghijklmnopqrstuvwxyz0123456789 CHAR2IDX {c: i for i, c in enumerate(CHARS)} CAPTCHA_LEN 5 # 验证码固定长度必须和样本一致 def encode_label(text): 把 wmpmp 编码成 [22, 12, 15, 12, 15] 这样的索引列表 assert len(text) CAPTCHA_LEN, f标签长度不符: {text} return [CHAR2IDX[c] for c in text] def load_image(path, size(128, 48)): 统一尺寸 灰度化 归一化验证码不需要彩色信息 img Image.open(path).convert(L).resize(size) arr np.array(img, dtypenp.float32) / 255.0 return arr[np.newaxis, :, :] # 增加 channel 维度这里三个参数最容易被忽略size决定输入分辨率验证码字符小的话 128×48 够用字符粘连严重就上 160×60CAPTCHA_LEN必须和实际验证码位数一致写错一位整个训练全废CHARS字符集如果包含了大写字母但样本里没有模型会学出一个永远不激活的类别浪费容量。2.3 数据集划分与增强策略样本图只有 7 张显然不够训练实际项目里你需要自己批量生成或采集几千张。划分比例常见 8:1:1。验证码增强不要用随机裁剪会切掉字符推荐随机旋转 ±10 度随机亮度/对比度扰动轻微高斯噪声水平平移 ±2 像素这些增强模拟的是验证码生成时的随机扰动而不是通用图像增强那套。源码里如果没写增强你自己补上否则小数据集上 ResNet 直接过拟合。3. ResNet 与 DenseNet 双骨干网络结构选型与训练脚本3.1 两种骨干的核心差异ResNet 用残差连接解决深层网络梯度消失公式是H(x) F(x) xDenseNet 把每一层都连到后面所有层公式是x_l H_l([x_0, x_1, ..., x_{l-1}])。放到验证码识别场景对比项ResNetDenseNet参数量中等同深度下更少特征复用局部全局密集训练速度快稍慢拼接操作多小数据集表现容易过拟合相对更稳显存占用低高特征图拼接我的经验是验证码字符集小于 36、样本少于 1 万张时DenseNet 的密集连接反而更容易训出稳定结果样本上到几万张后 ResNet 的性价比更高。源码同时给两个骨干就是让你做这个对比实验。3.2 多字符分类头怎么写不管用哪个骨干最后都要接一个「长度 × 字符集大小」的输出头import torch import torch.nn as nn import torchvision.models as models class CaptchaModel(nn.Module): def __init__(self, backboneresnet18, num_chars36, captcha_len5): super().__init__() if backbone resnet18: net models.resnet18(weightsNone) # 验证码是灰度图第一层改成单通道 net.conv1 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse) feat_dim net.fc.in_features net.fc nn.Identity() elif backbone densenet121: net models.densenet121(weightsNone) net.features.conv0 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse) feat_dim net.classifier.in_features net.classifier nn.Identity() self.backbone net self.head nn.Linear(feat_dim, num_chars * captcha_len) self.num_chars num_chars self.captcha_len captcha_len def forward(self, x): feat self.backbone(x) logits self.head(feat) # [B, num_chars * captcha_len] return logits.view(-1, self.captcha_len, self.num_chars)关键点net.conv1改成单通道是因为验证码灰度化后只有 1 个 channel不改会直接报维度错误nn.Identity()把原分类头摘掉换成我们自己的多字符头view操作把扁平输出 reshape 成[B, 5, 36]方便对每一位单独算交叉熵。3.3 训练循环与损失函数损失用「逐位交叉熵求和」而不是整体一个 softmaxdef train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 for imgs, labels in loader: imgs imgs.to(device) labels labels.to(device) # [B, 5] optimizer.zero_grad() logits model(imgs) # [B, 5, 36] loss 0 for i in range(model.captcha_len): loss criterion(logits[:, i, :], labels[:, i]) loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader)参数说明criterion用nn.CrossEntropyLoss()优化器我一般用Adam(lr1e-3)起步20 个 epoch 后切1e-4batch size 在 8G 显存上 ResNet18 可以到 64DenseNet121 建议降到 32。训练时盯两个指标逐位准确率和整串准确率。整串准确率才是最终指标逐位高但整串低说明模型在某些字符位上混淆严重。4. 避坑与排查验证码识别训练里最常见的五个翻车点4.1 损失降不下去准确率卡在 5% 左右现象训练几个 epochloss 从 3.5 降到 3.2 就不动了准确率约等于随机猜。原因标签编码和字符集对不上比如文件名是大写但CHARS里只有小写CHAR2IDX[c]直接 KeyError 或者被静默跳过。解决在encode_label里加断言训练前先跑一遍全量标签校验把非法字符打印出来。4.2 训练集准确率 99%验证集只有 30%现象典型过拟合。原因样本量太少几百张增强又没开ResNet 参数量远大于数据量。解决先上 DenseNet 换掉 ResNet再开旋转和平移增强加 dropoutnn.Dropout(0.3)放在 head 前必要时冻结骨干前几层只训后段。4.3 推理时单张图预测结果和批量预测不一致现象model(img)和model(batch)对同一张图输出不同。原因忘了model.eval()BatchNorm 在训练模式用 batch 统计量单张图时统计量就是它自己导致输出漂移。解决推理前强制model.eval()并用torch.no_grad()包住。4.4 验证码字符粘连模型总把两个字符识别成一个现象wmpmp被识别成wmpm或wmpmp但中间字符错位。原因固定长度分类假设字符等宽但实际验证码字符间距不均。解决预处理阶段做垂直投影切分或者改用 CTC 损失 序列模型。源码走的是固定长度路线遇到强粘连验证码要自己升级。4.5 GPU 显存溢出OOM现象DenseNet121 训练到第二个 epoch 报 CUDA out of memory。原因DenseNet 特征图拼接导致显存占用随深度增长batch size 设太大。解决batch size 减半开torch.cuda.amp混合精度或者把输入尺寸从 160×60 降到 128×48。5. 进阶技巧把整串准确率从 85% 推到 95% 以上5.1 学习率调度与 warmup固定学习率训到后期 loss 会震荡。我一般用余弦退火 前 3 个 epoch warmupfrom torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR optimizer torch.optim.Adam(model.parameters(), lr1e-3) warmup LinearLR(optimizer, start_factor0.1, total_iters3) cosine CosineAnnealingLR(optimizer, T_max50, eta_min1e-5) scheduler SequentialLR(optimizer, schedulers[warmup, cosine], milestones[3])warmup 防止初期大梯度破坏预训练特征余弦退火让后期学习率平滑下降。这套组合在验证码任务上通常能涨 3~5 个点。5.2 测试时增强TTA推理时对同一张图做多次轻微变换取平均 logitsdef predict_with_tta(model, img, n5): model.eval() logits_list [] with torch.no_grad(): for _ in range(n): aug random_shift(img, max_shift2) # 随机平移 logits_list.append(model(aug)) avg_logits torch.stack(logits_list).mean(0) return avg_logits.argmax(-1)TTA 对验证码这种字符位置敏感的任务提升明显尤其是字符边缘模糊的样本。代价是推理时间乘以 n线上服务要权衡。5.3 用混淆矩阵定位弱字符整串准确率上不去往往是某几个字符互相混淆。打印混淆矩阵from sklearn.metrics import confusion_matrix import seaborn as sns # 收集所有验证集的逐位预测 all_preds [] # shape [N, 5] all_labels [] # ... 推理填充 ... for i in range(5): cm confusion_matrix(all_labels[:, i], all_preds[:, i]) sns.heatmap(cm, annotFalse) plt.title(fPosition {i}) plt.savefig(fcm_pos_{i}.png)看哪个位置的混淆最严重针对性补样本。比如0和O、1和l混淆就在数据生成时减少这类易混组合或者对混淆字符加类别权重。5.4 模型集成ResNet 和 DenseNet 各训一个推理时 logits 相加final_logits 0.5 * resnet_logits 0.5 * densenet_logits两个骨干的归纳偏置不同集成后整串准确率通常比单模型高 2~4 个点。源码同时给了两个骨干集成是最自然的进阶用法。从那以后我每次训验证码模型都强制先跑一遍标签校验脚本再开增强最后才动网络结构——顺序反了就是白跑几小时。希望帮到你。本文还有配套的精品资源点击获取