ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python CNN验证码识别全流程:从样本生成到模型部署的工程实践

Python CNN验证码识别全流程:从样本生成到模型部署的工程实践 简介基于Python与CNN神经网络的验证码识别项目完整提供源码与文档说明适合作为毕业设计、期末大作业或课程设计的高分参考。项目采用端到端卷积神经网络模型对纯数字验证码的识别准确率可达99.99%且代码包含详细注释覆盖验证码生成、图像预处理、one-hot编码、模型训练、预测与测试全流程可帮助开发者快速理解CNN在图像识别任务中的实际搭建与调参方法。资源压缩包共50个文件以40张png验证码样本、8个Python脚本为主体配套1份README说明文档与gitignore配置包体约592KB整体结构清晰。其中验证码生成与训练预测模块均可独立调用便于按数据集、模型、训练和预测等模块查阅使用。截至当前已有115人学习浏览该资源项目经过调试验证可运行既可作为论文写作前的实验原型也能直接用于答辩演示或期末项目展示。1. Python用CNN识别验证码不是贴个模型那么简单看到这个标题多数人的第一反应是“CNN识别验证码嘛网上开源项目一大把拉下来跑通就完事”。真正上手过的人知道验证码识别是一条流水线样本从哪来、预处理怎么统一、网络结构怎么搭、训练到什么程度算好、最后怎么接到自动化流程里每一步都有细节在等你。标题里的“项目源码文档说明”听起来像个开箱即用的包但把别人的源码跑通只是开始能把它改造成你手头数据可用的方案才是这个方向真正的价值所在。这篇文章适合三类人做自动化测试需要绕过图形验证码的测试工程师想用Python入门深度学习的同学验证码是一个样本量小、标注成本低、效果反馈快的练手场景以及要把OCR前置能力集成进爬虫或RPA流程的开发者。先给结论识别率卡在80%上不去的时候问题通常不在网络结构而在样本质量和预处理一致性上。这个判断后面每一步都能印证。2. 从样本到输入张量验证码数据集怎么造、怎么洗2.1 生成训练样本用Pillow批量造出带噪、带扭曲的验证码训练CNN第一步是攒数据。真实验证码标注成本高四种字符混合、上千张手工标注一下午就没了而且真实样本的版权和合规问题也麻烦。常见做法是用Pillow自己生成字体、尺寸、噪声、扭曲程度全部可控一套脚本几分钟就能造出上万张。下面这段是我常用的生成器# sample_gen.py —— 批量生成带干扰的 4 位验证码样本 import os import random from PIL import Image, ImageDraw, ImageFilter CHARS abcdefghijklmnopqrstuvwxyz0123456789 # 参与训练的字符表 OUT_DIR dataset/train os.makedirs(OUT_DIR, exist_okTrue) def make_sample(save_path: str, size(120, 40), font_size28): text .join(random.choices(CHARS, k4)) # 随机取 4 个字符 img Image.new(RGB, size, (255, 255, 255)) # 白底 draw ImageDraw.Draw(img) # 随机干扰线每条线两个随机端点 for _ in range(3): draw.line( [random.randint(0, size[0]), random.randint(0, size[1]), random.randint(0, size[0]), random.randint(0, size[1])], fill(random.randint(0, 150), random.randint(0, 150), random.randint(0, 150)), width1 ) # 逐字符绘制x 坐标递增y 坐标上下浮动 x random.randint(4, 8) for ch in text: y random.randint(2, 6) draw.text((x, y), ch, fill(30, 30, 30), fontNone) x random.randint(22, 26) # 控制字符间距 # 轻微扭曲 压缩噪点 img img.filter(ImageFilter.SMOOTH) img img.filter(ImageFilter.GaussianBlur(radius0.5)) img.save(save_path) with open(save_path.replace(.png, .txt), w) as f: f.write(text) for i in range(12000): make_sample(f{OUT_DIR}/sample_{i:05d}.png) if i % 1000 0: print(fgenerated {i})逻辑说明脚本每次随机生成4个字符写进图片同时把标签存成同名txt训练时读文件名或txt都能对齐。干扰线、高斯模糊、字符上下浮动模拟的是真实验证码最常见的三种干扰。关键参数是size(120, 40)、font_size28和字符间距22~26——这三个值决定了后面CNN输入张量的尺寸也决定了字符是否粘连。参数说明图片尺寸不宜过大120x40是速度和精度之间的折中再大就浪费算力字符间距22到26像素是模拟“轻微粘连但不完全重叠”的常见状态间距太小生成的样本难度过高模型会学得很痛苦。GaussianBlur(radius0.5)模拟的是图片压缩后的模糊感半径超过1.5会让字符边缘糊成一团慎调。如果你要复现先跑1000张打开图片看一遍再批量生成这是省时间的重点。2.2 预处理与两种识别路线整图直读 vs 单字符分割样本造好之后下一步是把图片转成CNN能吃的张量。OpenCV做这一步最顺手。以灰度图输入为例# preprocess.py —— 统一图片尺寸与灰度 import cv2 import imutils def load_and_preprocess(path: str, target_size(120, 40)) - numpy.ndarray: # 读成单通道灰度图 img cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: raise ValueError(fcannot read image: {path}) # 统一尺寸等比缩放后填充到目标尺寸避免字符变形 img imutils.resize(img, widthtarget_size[0]) h, w img.shape top max(0, (target_size[1] - h) // 2) bottom target_size[1] - h - top img cv2.copyMakeBorder(img, top, bottom, 0, 0, cv2.BORDER_CONSTANT, value255) # OTSU 二值化把前景字符和背景噪声分离 _, binary cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 开运算去掉孤立的盐噪声点 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) return binary.astype(float32) / 255.0逻辑说明这段预处理的核心是“消除输入分布差异”。不管图片来源是生成器还是真实请求都要落到相同的尺寸、相同的通道顺序、相同的像素取值范围。copyMakeBorder用白色填充而不是黑色是因为验证码背景是浅色填充黑色会把网络训练时关注的边界区域搞乱。OTSU阈值自动找分割点比固定127强尤其适合背景有渐变色的验证码。参数说明kernel(2, 2)的开运算核很有讲究。核太小去不掉噪点核太大比如3x3以上会把细字符笔画也腐蚀掉导致识别率下降。这类形态学操作的参数每次换数据集都要重新验证不要一套参数走天下。二值化后除以255归一化到0~1是CNN训练的基本操作不做这一步梯度下降会非常不稳定。预处理之后要选识别路线。定长4位验证码有两种主流做法路线输入输出形态优点缺点适用场景整图直读整张图进CNN输出4x36的分类无需分割管线简单对字符粘连、重叠敏感字符间距清晰的验证码单字符分割先把图切成4块逐块识别每个字符识别率稳定分割出错则全错定位算法要调字符分离、无粘连的验证码我一般优先做整图直读。原因是分割路线的“分割准确率”是乘法关系——即使单字识别率有98%四字全对也只有92%而整图直读让CNN自己学字符位置只要样本多样鲁棒性反而更高。分割路线只在字符严重粘连、整图识别率明显偏低时才回头考虑配合垂直投影找字符边界。2.3 样本质量决定了训练上限清洗与均衡的四个检查点很多项目死在“样本集看起来没问题”。生成完数据要做的第一件事不是训练而是检查分布。以下四个检查点是我踩过坑后才固定下来的流程字符均衡random.choices生成字符理论上分布均匀但量小的时候会有偏差。训练前统计一下各字符出现次数低于平均数的字符补样本。CNN对样本少的类别会无脑偏向高频类这是黑匣子里的常见翻车点。异常样本过滤生成过程中如果字体库缺失某些字符可能渲染成方框或空白。批量生成的脚本里加一步对每张图做连通域分析字符数不等于4就删掉。这个检查能帮你省掉大量“训练集loss极低但测试全灭”的排查时间。训练集和验证集同分布从同一个生成器出来的数据分train/val分布天然一致但这不代表真实场景。真实验证码如果带彩色噪点、环形干扰、字符旋转而你生成的样本没有验证集再高也没用。标签与图像严格对齐保存图片和标签时文件名对应关系一旦错位整个数据集就是毒药。建议生成时把标签写进文件名或txt并用脚本抽查20张图人工核对标签内容不要相信“生成逻辑这么简单所以不会错”。3. 搭建CNN识别模型网络结构、参数与训练入口3.1 选型理由为什么CNN而不是全连接或传统OCR传统OCR方案先做字符分割再对单个字符做模板匹配或特征提取。这套技术在印刷体扫描件上很成熟但对付扭曲、带干扰线、字符间距不定的验证码就力不从心——模板匹配的本质是找“相似区域”而验证码设计的初衷就是让你找不到稳定特征。Tesseract在这类图像上的表现基本是拆盲盒级别。CNN卷积神经网络的优势在于“局部平移不变性”。卷积核在整张图上滑动字符往左偏两个像素、上移三个像素对激活值的影响很小配合池化层的下采样网络学到的是“这里有一个圆圈、那里有一条斜线”这种局部结构而不是像素级的精确位置。这正是验证码识别最需要的性质干扰线随便画只要核心笔画结构在卷积核就能响应。全连接网络没有这个性质输入尺寸一变就失效参数量大且极易过拟合。所以这个场景下CNN是首选不是因为它新而是因为它和任务性质匹配。3.2 一套能跑的Keras模型Conv→BN→MaxPool结构模型结构不需要很复杂验证码不是ImageNet过深的网络反而在少量样本上过拟合。下面这套结构是验证码识别里比较经典的卷积堆叠方式# model.py —— 定义一个适用于 120x40 定长验证码的 CNN from tensorflow.keras import layers, models INPUT_SHAPE (40, 120, 1) # 注意通道维在最后与 TF 默认布局一致 NUM_CLASSES 36 # 单字符类别数26 字母 10 数字 CAPTCHA_LEN 4 # 验证码长度定长才有这个输出设计 def build_cnn(): model models.Sequential(namecaptcha_cnn) model.add(layers.Input(shapeINPUT_SHAPE)) # 第一组卷积提取低级边缘、笔画特征 model.add(layers.Conv2D(32, (3, 3), paddingsame, activationrelu)) model.add(layers.BatchNormalization()) model.add(layers.MaxPooling2D(pool_size(2, 2))) # 第二组卷积组合成局部结构特征 model.add(layers.Conv2D(64, (3, 3), paddingsame, activationrelu)) model.add(layers.BatchNormalization()) model.add(layers.MaxPooling2D(pool_size(2, 2))) # 第三组卷积抽象出字符部件 model.add(layers.Conv2D(128, (3, 3), paddingsame, activationrelu)) model.add(layers.BatchNormalization()) # 全局池化替代 Flatten减少参数量 model.add(layers.GlobalAveragePooling2D()) model.add(layers.Dense(128, activationrelu)) model.add(layers.Dropout(0.5)) # 输出层4 个字符 x 36 类 144用 softmax 展开成 (4, 36) model.add(layers.Dense(CAPTCHA_LEN * NUM_CLASSES, activationsoftmax)) return model model build_cnn() model.summary()逻辑说明三层卷积逐级抽象特征——底层看边缘中层看笔画组合高层看部件结构。每次卷积后都接BatchNormalization作用是让激活值分布稳定训练收敛快不少。池化层放在卷积后面下采样特征图从120x40缩到60x20再缩到30x10分辨率降低的同时感受野变大。最后用GlobalAveragePooling2D而不是Flatten把每个特征图压成一个数值参数量小一个量级过拟合风险明显降低。参数说明(3,3)卷积核是最稳的起点5x5感受野更大但细节保留差paddingsame保证特征图尺寸不缩水池化时才统一减半。Dropout 0.5放在全连接层前是防过拟合的关键位置训练集较小的时候不建议低于0.3。最后一层输出4 * 36 144维前4维代表第0个字符的36类概率依次类推。这个输出设计只适用于定长验证码变长场景要改用CTC损失做法会复杂很多。3.3 损失函数与输出层定长验证码的标签编码网络输出是144维向量标签不能直接用字符串丢给模型需要编码。常见做法是把每个字符映射成one-hot向量再把4个字符的one-hot拼接# encode.py —— 字符串标签转成模型输出格式 import numpy as np CHARS abcdefghijklmnopqrstuvwxyz0123456789 CHAR2IDX {ch: i for i, ch in enumerate(CHARS)} def encode_text(text: str) - np.ndarray: a1b2 - shape (4, 36) 的 one-hot 矩阵 vec np.zeros((CAPTCHA_LEN, len(CHARS)), dtypefloat32) for pos, ch in enumerate(text): vec[pos, CHAR2IDX[ch]] 1.0 return vec def decode_pred(pred: np.ndarray) - str: 把 (4, 36) 概率矩阵解码回字符串 idx pred.argmax(axis1) return .join(CHARS[i] for i in idx)逻辑说明encode_text做的是定长对齐不足4位的字符串在训练数据里本身就是脏数据应该直接过滤。decode_pred拿的是已经reshape成(4, 36)的概率矩阵逐位取最大概率的字符索引。这两个函数是训练和推理的数据桥方向相反必须保证字符表顺序完全一致。参数说明损失函数用categorical_crossentropy时y传给fit的shape必须是(batch, 144)而不是(batch, 4, 36)Keras会自动处理还是报错取决于后端版本我一般在训练脚本里显式reshape(-1, 144)。字符表里字母和数字的顺序无所谓但一旦定了就不要改否则训好的模型全部废掉——因为这个模型记住了“索引0是字符a”。4. 训练、评估与调参识别率从80%到98%的实际路径4.1 训练脚本最小实现数据加载、切分、Checkpoint数据集和模型就绪训练脚本要处理三件事把图片批量读成numpy数组、划分验证集、训练时自动保存最优权重。下面是一份可以直接落地的骨架# train.py —— 训练主流程 import os import glob import numpy as np from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint from tensorflow.keras.optimizers import Adam def load_dataset(img_dir: str): img_paths sorted(glob.glob(os.path.join(img_dir, *.png))) X, y [], [] for p in img_paths: arr load_and_preprocess(p) # 复用 2.2 的预处理函数 label os.path.basename(p).split(_)[-1].replace(.png, ) X.append(arr) y.append(encode_text(label).reshape(-1)) X np.stack(X)[..., np.newaxis] # (n, 40, 120, 1) y np.stack(y) # (n, 144) return X, y X, y load_dataset(dataset/train) perm np.random.permutation(len(X)) split int(len(X) * 0.9) X_train, X_val X[perm[:split]], X[perm[split:]] y_train, y_val y[perm[:split]], y[perm[split:]] model build_cnn() model.compile( optimizerAdam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy] ) callbacks [ ModelCheckpoint(best.h5, monitorval_loss, save_best_onlyTrue), EarlyStopping(monitorval_loss, patience8, restore_best_weightsTrue), ] model.fit( X_train, y_train, validation_data(X_val, y_val), batch_size64, epochs60, callbackscallbacks, verbose1 )逻辑说明load_dataset把每张图走一遍2.2的预处理管道同时从文件名解析标签。perm先打乱再切分避免生成器按顺序写入导致同字符连续出现。训练时ModelCheckpoint盯着验证集loss存最优权重EarlyStopping在验证集连续8个epoch不提升时自动停掉并恢复最优权重。这套组合保证你就算挂机去吃饭回来拿的也是训练过程中的最佳状态而不是最后一轮可能已经过拟合的状态。参数说明学习率1e-3配Adam是通用起点。验证集比例10%12000张样本留1200张做验证够用且不会太挤占训练数据。batch_size64在显存允许的情况下比32收敛更平滑但如果发现loss震荡厉害先降到32试试。patience8意味着模型在验证集上连续8轮无提升就停止过小容易在loss平台期提前结束过大则浪费时间。4.2 三个必调的参数batch size、学习率、数据增强这三个参数是识别率从80%往98%爬的关键。我调参的顺序是固定的学习率。1e-3跑不动loss停在2.0以上不降就降到5e-4或3e-4如果loss下降但验证集波动剧烈考虑在训练中途衰减。最简单的方式是配合ReduceLROnPlateau回调验证集不提升就自动乘以0.2。学习率是这套模型里最敏感的参数没有之一。batch size。64起步loss曲线震荡就回调32batch size加倍128能让训练加快但在小数据集上容易让梯度方向过于“平均”陷入局部最优。数据增强。训练集只有一万张时增强是防过拟合最有效的招。ImageDataGenerator里四个参数值得调from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range5, # 随机旋转正负5度再大字符语义会变 width_shift_range0.05, # 水平平移5% height_shift_range0.05, # 垂直平移5% zoom_range0.05, # 随机缩放5% brightness_range(0.9, 1.1), # 亮度轻微扰动模拟不同渲染背景 fill_modeconstant, # 平移后填充白色和验证码底色一致 cval255 ) train_flow datagen.flow(X_train, y_train, batch_size64) model.fit(train_flow, validation_data(X_val, y_val), epochs60, callbackscallbacks)逻辑说明增强的本质是“让模型见过更多变形”。rotation_range超过10度会让“6”看起来像“9”、“z”像“2”语义混淆得不偿失。平移和缩放模拟的是真实验证码字符位置随机浮动。brightness_range应对的是一些网站用浅灰字体渲染字符的情况。fill_modeconstant配合cval255让空白区域填充白色否则黑色填充会引入不存在的背景结构。这里有一个常见误用做增强时把原始X_train也喂给模型等于训练集和验证集分布不一致。增强只放在训练侧验证集永远用原始清洗后的数据。4.3 评估不能只看准确率按字符、按样本类型拆开看训练结束别急着部署。整体准确率是个黑匣子——它可能高达96%但“8”这个字符识别率只有60%刚好在生产流量里“8”出现频次最高你的模型在线上就会翻车。评估要拆开看# evaluate.py —— 逐字符准确率与失败样本可视化 from sklearn.metrics import confusion_matrix, classification_report pred model.predict(X_val, verbose0) # shape (n, 144) pred pred.reshape(-1, CAPTCHA_LEN, len(CHARS)) pred_texts [decode_pred(p) for p in pred] true_texts [decode_pred(y.reshape(-1, CAPTCHA_LEN, len(CHARS))[i]) for i in range(len(y))] # 逐字符对比统计每个字符的识别情况 char_true, char_pred [], [] for t, p in zip(true_texts, pred_texts): for ct, cp in zip(t, p): char_true.append(ct) char_pred.append(cp) print(classification_report(char_true, char_pred)) # 找出整图预测失败的前 20 张人工检查是预处理还是网络问题 miss [(i, t, p) for i, (t, p) in enumerate(zip(true_texts, pred_texts)) if t ! p] for i, t, p in miss[:20]: print(fidx{i} true{t} pred{p})逻辑说明classification_report直接给出每个字符的精确率、召回率、F1值。如果发现个别字符F1明显低于均值不用急着改网络先看失败图片——通常是该字符在字符表里和另一个字符形态相似或者训练样本里这个字符数量偏少。打印前20个失败样本时对比原始图片确认预处理有没有把它们搞坏。参数说明这里reshape(-1, 4, 36)必须和编码时完全对应写错一位得到的评估结果全是废数据。整图准确率和逐字符准确率是有区别的假设每字符识别率98%4位整图准确率理论值大约92%如果你的整图准确率远低于这个乘积估算说明错误集中出现在特定字符组合或特定样张类型上这才是要深挖的方向。5. 验证码识别避坑五个常见翻车现场与排查思路5.1 现象训练loss居高不下模型完全没学进去训练了20个epoch训练集loss还在2.5以上相当于随机猜36类准确率在10%到20%之间徘徊。先别调网络查三处第一标签和图片对不对得上——之前就见过有人脱敏时把文件名和标签对应关系弄乱模型看到的是“字符A的图配字符B的标签”神仙网络也学不出来第二预处理输出形状和INPUT_SHAPE是否一致——(120, 40, 1)和(40, 120, 1)差一个transpose模型能跑但学到的全是无效信息第三归一化是否完成——像素值没缩放到0~1之间梯度的量级就会异常。解决写一个20张样本的迷你测试集人工核对标签和输入shape确认无误再启动全量训练。5.2 现象训练集准确率99%验证集只有70%这就是过拟合的典型症状模型把训练样本的噪声细节背下来了。解决路径按优先级排列先加Dropout0.3提到0.5再加数据增强旋转5度、平移5%最后减小模型容量——把第三层卷积从128降到64。如果这三步做完验证集还是远低于训练集检查验证集本身是不是把同一个生成批次的数据切到了两边导致数据泄露。很多看起来“玄学”的过拟合根源是数据划分不随机。5.3 现象训练和验证都很好一上真实验证码全灭这种翻车最伤士气原因只有一个训练样本和线上样本分布不一致。真实验证码可能是彩底、带波纹、字符比例不同、字体跟你用的完全不一样。解决从线上真实采集500~1000张样本人工标注混进训练集再训练一次。这是从“能跑demo”到“能上线”必经的一步逃避不了。很多项目源码直接跑训练集是作者生成的数据换到你的目标站点就失效根因就在这。5.4 现象单张识别要200毫秒跑批量任务慢得离谱排查时先分清耗时在哪一段。常见原因有三个一是预处理里用了cv2.imread等待磁盘I/O批量时建议用cv2.imdecode直接读内存字节流二是模型权重太大把best.h5换成量化版或用TensorRT转换三是在每个请求里重复load_model——模型加载一次可能要几百毫秒放在服务启动时加载运行时只调predict。最容易被忽略的是预测时又做了一次OTSU和开运算而训练时预处理已经做过一遍再加上一次等于双重处理特征分布直接错掉。5.5 现象定长模型识别变长或含中文的验证码输出全是乱的标题里的模型结构是定长设计输出维度是“长度x类别数”遇到4位变5位、或混入中文的验证码结构本身就不适用。解决要么预处理阶段做长度归一化——把变长验证码裁成固定长度比如最多6位不足补白要么换模型——输出层改成序列模型或者直接用CTC loss做不定长识别。这两条路工作量差别很大如果你的目标验证码长度固定不要为了“可能的变长”提前上CTC先把定长做扎实。6. 把模型接进自动化流程预测脚本、批处理与验证方法训练和评估都过关之后最后一步是把模型集成到真实业务环境里。这里给出一份轻量级的预测服务片段重点在于“输入格式和训练时完全一致”# predictor.py —— 加载模型对外提供单张图片识别 import cv2 import numpy as np from tensorflow.keras.models import load_model MODEL_PATH best.h5 class CaptchaSolver: def __init__(self, model_path: str MODEL_PATH): self.model load_model(model_path) def predict_bytes(self, img_bytes: bytes) - str: # 直接从内存读取图片字节避免磁盘 I/O nparr np.frombuffer(img_bytes, np.uint8) img cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE) # 复用训练时完全相同的预处理逻辑 img resize_to_target(img, (120, 40)) _, binary cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))) x binary.astype(float32) / 255.0 x x[np.newaxis, ..., np.newaxis] # (1, 40, 120, 1) pred self.model.predict(x, verbose0).reshape(-1, 4, 36) return decode_pred(pred[0]) solver CaptchaSolver() print(solver.predict_bytes(open(real_sample.png, rb).read()))逻辑说明预测脚本的核心是“与训练时保持输入分布一致”——灰度、二值化、开运算、归一化、通道顺序每一步都不能省略少一步识别率就会跳水。类封装的好处是模型只加载一次后续每个请求只走前向推理。参数说明binary[np.newaxis, ..., np.newaxis]这个索引操作容易写错展开就是(1, 40, 120, 1)batch维放前面通道维放最后。如果你训练时用的是channels_first布局这里就要换成(1, 1, 40, 120)两个布局的模型权重不通用换布局等于重新训练。验证方法上我会固定保留50张线上真实样本从不参与训练每次改模型或改预处理后跑一遍记录“整图识别率”和“单字符识别率”两个指标。整图识别率低于80%的不允许进自动化流程因为批量任务里一次失败就要重试重试成本往往比想象高。另外建议记录每张预测的置信度——softmax输出的最大值。低于0.7的样本不要直接采用标记出来人工复核。最后说一个我的个人教训刚开始做验证码识别时我花了两周调网络结构把卷积层从3层加到6层识别率纹丝不动后来重新审视预处理发现真实数据和训练数据的二值化阈值算法不同真实图用固定127训练图用OTSU统一之后识别率直接涨了15个点。从那之后我再也不先动模型永远是先对齐样本和预处理再谈调参。这条路从样本生成、预处理、模型结构、训练评估到部署每一步都是可以量化验证的希望帮到你。本文还有配套的精品资源点击获取
返回列表