新闻详情

新闻详情

首页 / 资讯中心 / 详情

本地部署英文数字验证码识别服务:从模板匹配到CNN的完整实现

发布时间:2026/10/2 9:51:31来源:尧图网络
本地部署英文数字验证码识别服务:从模板匹配到CNN的完整实现
简介这是一套本地部署的英文数字混合验证码识别插件面向按键精灵、触摸精灵、触动精灵、Python等所有脚本与开发环境解决自动化流程中验证码识别准确率低、依赖外部接口的痛点。作者已将深度学习运行环境完整封包生成可直接双击运行的exe启动程序无需手动安装Python或配置依赖开启服务后提供标准Web API脚本只需将验证码图片转为Base64编码后POST到接口即可获得识别结果支持局域网、互联网及完全离线运行。压缩包共186个文件大小约27.43MB核心文件涵盖51个pyd编译模块、49个dll动态库、onnx推理模型、exe服务启动器以及多份txt调用说明另有少量python源码与证书文件整体目录结构清晰便于按需调用与二次集成。目前已有2724人学习使用适合需要高识别率、低接入成本且不依赖云服务的自动化技术用户快速落地。1. 本地部署的英文数字验证码识别插件为什么脚本圈都在找它我第一次看到“本地部署的英文数字验证码识别插件”这个需求是在帮一个做按键精灵脚本的朋友排查登录流程时。他当时卡在一个四位的英文数字验证码上截图截得动就是把字认不出来。市面上的云端识别要联网、按次计费还有延迟最关键的是他处理的是一套内网系统的测试账号图根本不想出网。于是“本地部署”就成了唯一的舒服解法。这个标题真正指的东西并不神秘一个跑在你本机或内网服务器上的识别服务输入验证码图片输出文本结果再配上方便按键精灵、触摸精灵、触动精灵、Python甚至PHP调的接口。它解决的是自动化脚本里最后那段“人眼要做的事”也就是把验证码变成可用的字符串。适合的人群很明确写脚本做自动化测试的、给自研系统做批量录入的、以及想要把识别能力集成进自己工具链的开发者。前提只有一个你只对自有或已授权系统做自动化别拿它去撞别人的风控。2. 英文数字验证码识别为什么通用 OCR 在这里会翻车2.1 验证码不是普通文字四种常见干扰验证码和扫描文档里的印刷体是两个物种。就算限定在英文数字你打开一张图也会同时看到四种干扰字符旋转随机角度从负十几度到正十几度整体波浪扭曲字符不再横平竖直干扰线从字符上横穿过去背景噪点和字符粘连。字体、颜色、位置每次生成都可能变只有“还是那几个字母数字”这个事实不变。通用 OCR比如 Tesseract是为文档设计的它依赖词库做语言模型纠错。而验证码往往是 4 到 6 位随机组合没有词义越纠错越离谱。在我做过的测试里通用 OCR 直接跑在英文数字验证码上识别率大概只有 30% 到 60%碰上扭曲厉害的样本还会更低。标题里说的“识别效果很牛”指的从来不是通用 OCR 那种什么都认而是在特定这一类专业验证码类别里做到九成五以上的正确率。这是“专用识别”和“通用识别”的差别提前搞清楚才不会选错工具。2.2 对接方式选型为什么 HTTP 是最大公约数要让按键精灵、触摸精灵、触动精灵、Python 都能对接我一般会先列一个表把常见对接方式摆出来比一比对接方式按键精灵触摸精灵 / 触动精灵Python跨平台性DLL/COM要注册或免注册调用32/64位兼容麻烦iOS 平台无法加载 DLL需要 ctypes 封装差命令行可以 Shell 调用但图片传递和结果解析繁琐受限subprocess 能调中本地 HTTP 服务POST 插件 / WinHttp 请求平台内置 HTTP 函数requests 一行调用最好按键精灵生态里很多人习惯用大漠插件绑定窗口先把图截下来再用 POST 插件把图片交给本地服务触摸精灵、触动精灵也都有自己的 HTTP 请求能力Python 更不用说。不同平台唯一都具备的流量出口就是 HTTP。所以我最终把服务定成“监听一个本地端口、接收 multipart 图片、返回 JSON”而不是搞共享内存或私有 TCP 协议。脚本平台层已经够折腾了别再让对接协议增加变量。2.3 识别管线预处理、分割、识别、置信度一个能用的本地识别服务内部管线是固定的四步。第一步预处理把彩色图做灰度化、二值化、去噪点干扰线第二步分割把一张图切成 N 个单字符小块第三步识别对每个小块做分类得到候选字符第四步后处理过滤低置信度结果。置信度这个字段很容易被忽略。它不参与“返回文本”这件事但对脚本流程很重要当识别结果低于某个阈值时脚本应该重新截图再识别一次而不是把错的结果继续往上抛。我一般在接口里返回两个主要字段code作为识别文本confidence作为置信度脚本端拿到后自己定策略。模板匹配适合字体固定、没有严重扭曲的验证码零训练成本十分钟能跑通。CNN 适合旋转、扭曲、粘连的验证码识别效果更好但要准备数据和训练时间。第三章先把模板匹配的服务框架搭起来让接口先通第四章再换成 CNN。这样做的好处是接口契约不变按键精灵那边一行不用改只有服务内部换识别核心。“本地部署”这个词常被误解成“加密保护”或“离线安装包”。它真正的价值只有一个图片不出内网、识别延迟受控、调用量不按次计费。部署形态上我一般分两种一种是开发机常驻服务方便调试另一种是打包成可执行程序放到目标机器上作为开机启动任务。按键精灵用户更倾向后者因为脚本换台电脑跑服务也得跟着走。第五章会把开机自启的坑单独列出来。3. 用 Python 在本机跑通验证码识别 HTTP 服务3.1 环境准备与最小安装Python 3.9 以上就行依赖只需要四个不需要任何 AI 框架pip install flask opencv-python numpy requestsflask 用来提供 HTTP 接口opencv-python 负责图像预处理和模板匹配numpy 做矩阵运算requests 只在测试客户端脚本里用到。如果你的目标机器不能联网常见做法是提前下载 whl 离线包或者用 PyInstaller 把服务打包成可执行文件。装完顺手验证一下环境python -c import cv2, flask; print(cv2.__version__)能打印 OpenCV 版本号就说明环境没问题。这里不要急着装 TensorFlow 或 PyTorch先把 v1 模板匹配跑通后面升级 CNN 再加 onnxruntime 也不迟。3.2 预处理模块让字符从背景里站出来验证码识别的第一个杠杆是预处理。字符都没和背景分开后面的分割和识别全是空中楼阁。import cv2 import numpy as np def preprocess(image_bytes): # 把上传的图片字节流直接解码省掉临时文件 img cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 自适应阈值二值化验证码背景不均匀时比固定阈值稳 binary cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 31, 15 ) # 开运算去掉细小的孤立噪点 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations1) return binary逻辑说明imdecode把客户端上传的 PNG 或 JPG 字节流直接解码成 OpenCV 矩阵不写临时文件服务端干净很多。adaptiveThreshold用局部像素计算阈值比写死127这种固定阈值更能应对渐变背景。THRESH_BINARY_INV把字符变白、背景变黑因为后面用findContours查轮廓时默认把白色前景当作要分割的对象。参数说明里最需要你手动调的是31和15分别代表局部窗口大小和均值减去的常量。窗口太小会把字符内部的笔画噪声也当成背景太大则二值化效果接近固定阈值。如果你发现字符断笔画看我说的把kernel改成(1, 1)或者iterations改成 0先别动窗口参数。如果发现背景有大片黑斑才增大窗口。3.3 字符分割与模板匹配识别核心模板匹配需要先准备一个字符模板库我通常是建一个templates目录放 0-9 和 A-Z 共 36 张 PNG每张是一个干净的字符图文件名就是字符本身。目录结构如下templates/ ├── 0.png ├── 1.png ├── A.png ├── B.png └── ...import os import cv2 def load_templates(template_dirtemplates): templates {} for fname in os.listdir(template_dir): if not fname.endswith(.png): continue char fname.split(.)[0] tpl cv2.imread(os.path.join(template_dir, fname), cv2.IMREAD_GRAYSCALE) # 模板统一缩放到 20x40避免字体大小不一致 tpl cv2.resize(tpl, (20, 40), interpolationcv2.INTER_AREA) # 模板也做一次二值化保证和预处理后的图处于同一灰度域 _, tpl cv2.threshold(tpl, 128, 255, cv2.THRESH_BINARY) templates[char] tpl return templates def split_and_match(binary, templates, min_area50): # 通过连通域找到每个字符的外轮廓 contours, _ cv2.findContours( binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) chars [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) if w 5 or h 10 or w * h min_area: continue # 太小的区域基本是噪点 # 裁剪字符四周多取两像素避免边缘被切掉 roi binary[max(0, y-2):yh2, max(0, x-2):xw2] roi cv2.resize(roi, (20, 40), interpolationcv2.INTER_AREA) # 和模板库逐字符匹配取分数最高者 best_char, best_score None, -1 for ch, tpl in templates.items(): result cv2.matchTemplate(roi, tpl, cv2.TM_CCOEFF_NORMED) _, score, _, _ cv2.minMaxLoc(result) if score best_score: best_score, best_char score, ch chars.append((best_char, float(best_score))) # 按 x 坐标排序保证“AB12”不会变成“A21B” chars.sort(keylambda item: item[1]) # 其实是按分数这里要注意 return chars注意代码里有一个容易写错的地方排序必须按字符块的 x 坐标而不是按分数、也不是按 y 坐标。上面那个示例代码里我故意留了一个不合理的排序键实际应该用boundingRect返回的x。修正写法是# 分割时把 (char, score, x) 一起存下来 chars.sort(keylambda t: t[2])因为findContours返回的顺序不保证从左到右只有显式按 x 坐标排序才可靠。逻辑说明RETR_EXTERNAL只取外轮廓避免字符内部的孔洞比如 8、9、B 这种带闭合区域的字母被当成多个对象。裁剪时四周扩出两像素是给matchTemplate留一点边缘上下文。TM_CCOEFF_NORMED对灰度值整体偏移有归一化适合二值图匹配分数越接近 1 说明越像。3.4 用 Flask 封装 /recognize 接口识别核心好了现在把它变成可调用的服务。这里通信协议要提前定清楚后面所有脚本都遵循它。from flask import Flask, request, jsonify import time app Flask(__name__) templates load_templates(templates) ALLOWED_EXTS {png, jpg, jpeg, bmp} app.route(/recognize, methods[POST]) def recognize(): # 客户端上传的字段名必须是 image file request.files.get(image) if file is None: return jsonify({error: no image field}), 400 fname file.filename or ext fname.rsplit(., 1)[-1].lower() if . in fname else png if ext not in ALLOWED_EXTS: return jsonify({error: unsupported format, ext: ext}), 400 data file.read() t0 time.time() binary preprocess(data) chars split_and_match(binary, templates) # 对每个字符置信度取平均作为整张图的置信度 if chars: code .join(ch for ch, _ in chars) conf sum(s for _, s in chars) / len(chars) else: code, conf , 0.0 cost_ms (time.time() - t0) * 1000 return jsonify({ code: code, confidence: round(conf, 4), cost_ms: round(cost_ms, 1), chars: [{char: ch, score: round(score, 4)} for ch, score in chars] }) if __name__ __main__: app.run(host127.0.0.1, port8899, threadedTrue)逻辑说明request.files.get(image)里“image”就是和各脚本端约定好的字段名按键精灵里拼 multipart 时字段名写错是新手最常见的报错。ALLOWED_EXTS做个白名单过滤防止一些奇怪格式让imdecode解出空矩阵。threadedTrue允许并发处理多个截图请求但对 CPU 密集的识别任务来说并发只是表面文章真正的瓶颈在后面。提示host127.0.0.1表示只能本机访问。如果按键精灵和识服务在同一台机器这个配置没问题如果服务跑在局域网另一台服务器需要改成0.0.0.0并且自己配置防火墙白名单。3.5 用 Python 客户端调用并验证服务跑起来后另开一个终端验证import requests import json resp requests.post( http://127.0.0.1:8899/recognize, files{image: (captcha.png, open(captcha.png, rb), image/png)}, timeout5, ) print(json.dumps(resp.json(), ensure_asciiFalse, indent2))files元组里三个元素分别是文件名、字节流、MIME 类型。很多新手把 MIME 省略requests 会自动补一个服务端往往也还能解析但按键精灵的 POST 插件如果不拼Content-Disposition里的 filename 和字段名服务端就会拿不到文件。Python 这边先确认通了再去调按键精灵。正常返回长这样{ code: AB12, confidence: 0.9842, cost_ms: 28.6, chars: [ {char: A, score: 0.9912}, {char: B, score: 0.9821}, {char: 1, score: 0.9734}, {char: 2, score: 0.9902} ] }3.6 按键精灵、触摸精灵、触动精灵的对接写法Python 通了其他平台只是换一个 HTTP 客户端的问题。按键精灵的常见做法是调用按键精灵的 POST 插件或者 WinHttp。它没有统一官方 HTTP 库生态里经常用“按键精灵post插件”或者是大漠插件自带的 HTTP 方法。核心要点是body 必须是符合 multipart 格式的完整字节串字段名必须是image不能只把图片路径发给服务端。如果你用的按键精灵版本不方便拼字节可以在按键精灵里先把图片转成 base64POST 一个 JSON 过去服务端再解码。我在实际项目中更推荐后者因为避开 multipart 各种编码坑。触摸精灵和触动精灵比按键精灵省心这两个环境里都内置 HTTP 相关函数具体函数名在各版本略有差异但语义都一样把图片字节放到 POST body字段名image解析返回的 JSON 取code。如果平台不给你直接拿图片字节的接口同样先转 base64 再 POST。“所有语言和平台都可方便对接”这句话落到实现上就一句话只要你会发 multipart POST就能对接。PHP 用 curlC# 用 HttpClient按键精灵用 POST 插件没有例外。接口返回 JSON 里最有用的就是code和confidence脚本端每个人取自己需要的字段就行。4. 把识别效果从“能用”升级到“很牛”CNN 与 ONNX Runtime4.1 什么时候必须放弃模板匹配模板匹配的瓶颈不是速度而是它对“没见过”的字体无能为力。如果目标验证码字体固定、间距均匀模板匹配很容易跑到 95% 以上。一旦字符开始旋转、扭曲、深浅变化或者两两粘连matchTemplate就开始频繁认错表现为 A 经常认成 4、V 和 Y 分不清、C 和 G 混淆。判断标准不要靠感觉我一般看分割后single字符置信度的均值如果有三成以上的字符置信度低于 0.85模板匹配的优化空间就很有限了。与其继续堆模板不如训练一个专用小 CNN。CNN 直接输入字符图块输出 36 个类别的概率对轻微旋转、噪声、笔画粗细变化有天然鲁棒性CPU 上推理一个字符也就两三毫秒。这也是“识别效果很牛”的真正来源模型不大但任务匹配度极高。4.2 数据准备生成 真实样本混合文件名即标签CNN 效果八成由数据决定。两个数据来源一是拿到目标系统真实验证码样本做人工标注二是用 Python 按目标风格生成大量样本。我一般混合真实样本保证分布一致生成样本把数量放大到几万张。标注格式最省事的就是文件名即答案。import random import string from PIL import Image, ImageDraw, ImageFont def generate_one(font_path, save_dirgen_data): # 去掉易混淆的 O/0、I/1训练能更快收敛 alphabet ABCDEFGHJKLMNPQRSTUVWXYZ23456789 chars .join(random.choices(alphabet, krandom.randint(4, 6))) img Image.new(L, (160, 60), color255) draw ImageDraw.Draw(img) font ImageFont.truetype(font_path, 32) x 10 for ch in chars: # 每个字符做上下浮动模拟真实验证码 draw.text((x, random.randint(5, 20)), ch, fontfont, fillrandom.randint(0, 120)) x random.randint(24, 30) # 随机点噪声 for _ in range(random.randint(100, 300)): px random.randint(0, 159) py random.randint(0, 59) img.putpixel((px, py), random.choice([0, 60, 120])) img.save(f{save_dir}/{chars}.png)逻辑说明字符集里去掉 O、0、I、1这是业界很常见的做法能直接砍掉最难分的几对。训练时的模型就只会输出 36 类里的剩下字符但真实识别时仍可能遇到原系统里的 O 和 0所以处理真实样本时要注意“输出空间要不要扩展到全部字符”。生成样本还是只做初筛最终要以真实样本回归结果为准。数据增强建议加上随机旋转正负 15 度、随机缩放和透视变换。这些增强能直接提升在真实样本上抗扭曲的能力。增强不是越多越好要先保证生成样本风格和目标系统接近否则训练出来的是一个“认得清假图认不得真图”的模型。4.3 训练一个轻量 CNN 并导出 ONNX模型层数不用太多三层卷积加一个全连接CPU 上跑起来足够快。用 PyTorch 写一个最小可跑的框架import torch import torch.nn as nn class CaptchaNet(nn.Module): def __init__(self, n_classes36): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2, 2), # 输入高32宽64 - 16x32 nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2, 2), # 8x16 nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d((4, 8)) # 固定输出尺寸 ) self.classifier nn.Linear(128 * 4 * 8, n_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)输入统一成高 32、宽 64 的灰度图输出层是 36 类。训练循环就是常规的交叉熵加 Adambatch_size64、初始学习率0.001五十个 epoch 以内基本收敛。训练完成后导出 ONNXpip install onnx onnxruntime torch.onnx.export(model, dummy_input, captcha_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}})参数说明dynamic_axes把 batch 维度设为动态这样导出文件既能一次识别一张也能一次识别一批。导出后先检查输入输出 shape输入应该是[1,1,32,64]输出是[1,36]。发现 shape 不对基本是训练时输入尺寸和导出时 dummy_input 不一致。4.4 在 HTTP 服务里无缝替换识别核心服务端只改split_and_match为cnn_predict接口和字段完全不变按键精灵端一行代码都不用动。这就是我把接口边界设计得很干净的好处。import onnxruntime as ort session ort.InferenceSession(captcha_cnn.onnx, providers[CPUExecutionProvider]) # 类别索引到字符的映射训练时保存成 json idx2char {0: A, 1: B, 2: C, ...} def cnn_predict(binary_img): # 缩放到与训练时一致的尺寸宽64、高32 roi cv2.resize(binary_img, (64, 32), interpolationcv2.INTER_AREA) blob roi.astype(np.float32) / 255.0 blob blob[None, None, :, :] # shape: (1, 1, 32, 64) logits session.run([output], {input: blob})[0] # 简单 softmax exp np.exp(logits - np.max(logits, axis1, keepdimsTrue)) prob exp / np.sum(exp, axis1, keepdimsTrue) class_id int(np.argmax(prob, axis1)[0]) return idx2char[class_id], float(np.max(prob))逻辑说明推理前预处理必须和训练时完全一致。训练时用灰度图除以 255推理也这么做训练时先做了二值化推理就不要突然换成别的预处理。这个一致性是“翻车”重灾区很多人模型训练得很好部署时识别率掉一半查到最后就是预处理不一致。字符级 CNN 加独立分割可以解决大部分旋转扭曲问题但字符一旦粘连分割这关还是会失败。更难的情况要做端到端序列识别也就是 CNN 加循环层加 CTC 损失直接对整张验证码图输出变长文本。落地方式仍然是导出 ONNX 后在服务里调用但实现和调参复杂度高一个量级。我一般只在字符级 CNN 也扛不住、粘连错误率超过 5% 的时候才上 CTC普通项目到字符级 CNN 就够用了。5. 避坑本地部署验证码识别插件的七个常见问题5.1 按键精灵 POST 图片过来服务端拿到空文件现象Flask 日志显示请求进来了但request.files是空的或者文件名为空。 原因按键精灵的 POST 插件拼 multipart 包时没有带正确的Content-Disposition或者字段名写成了file而服务端只认image。 解决先别动服务端用 Python requests 或 Postman 发一次同样的请求确认服务端正常。然后再检查按键精灵端 HTTP 头里的boundary是否和 body 一致。如果按键精灵拼字节实在不方便我强烈建议服务端同时支持 base64 模式按键精灵把图片转成 base64 后 POST JSON。这个方案看着绕实际能避开一堆 multipart 相关的玄学问题。5.2 触摸精灵/触动精灵传的是图片路径服务端收到的是路径字符串现象脚本端明明写了“图片地址”但服务端收到的是C:\xxx\1.png这种字符串识别接口直接抛错。 原因平台 HTTP 函数返回体里传的是字符串字段你得先把图片内容读进变量再放进 POST body。平台不会替你做“路径转文件”这件事。 解决检查平台是否提供图片转 base64 的命令有就直接在脚本端转好再发。对应的服务端加一个解析 base64 的分支。我习惯把两个入口都留着image字段收原始文件image_base64字段收字符串两边互不干扰。5.3 识别慢到脚本超时并发截图一多单线程服务直接拖垮现象单张识别 30ms脚本一开多线程识别耗时飙到 500ms 甚至超时。 原因Flask 内置服务器即使开了threadedTruePython 的 GIL 也扛不住多个请求同时做 CPU 密集的预处理和模板匹配。 解决第一步先量化瓶颈看返回的cost_ms是预处理慢还是匹配慢第二步给 OpenCV 限制线程数在导入 cv2 之前设置cv2.setNumThreads(2)第三步如果并发仍不够用 gunicorn 起多 worker或者干脆让脚本端把截图请求排成串行队列。验证码识别场景并发通常不大串行反而是最稳的策略。5.4 字符粘连导致分割错位识别结果多一位或少一位现象识别结果长度不对比如“AB2”或“AB12C”但单独看每个字符又都对。 原因两个字符粘在一起findContours把两个字符当成了一个整体匹配后只输出一个字符。 解决先试在分割前做一次形态学腐蚀把粘连处断开核大小从(2,2)开始试但腐蚀会让笔画变细容易破坏字符结构。再不行换等宽分割先找到整行验证码的左右边界估算字符数后按宽度均分。注意验证码的字符间距通常不均匀均分也只是治标。如果以上都失败说明这个验证码天然适合走 CNN/CTC 路线模板匹配可以放弃了。5.5 本地服务被防火墙或安全软件拦截现象本机能访问局域网其他机器访问不了或者按键精灵偶发连接失败。 原因Windows 防火墙默认拦截了 Python 进程的入站连接安全软件也可能把python.exe当未知程序处理。 解决开发阶段 host 绑定0.0.0.0手动在 Windows 防火墙放行对应端口比如8899。如果是打包成 exe 部署第一次启动会弹防火墙确认框需要用管理员权限运行并勾选允许。验证端口通不通用telnet 192.168.x.x 8899或者Test-NetConnection别只在浏览器里试。5.6 电脑重启后识别服务没跟着起来现象脚本白天跑得好好的第二天一开机就报连接被拒绝。 原因Flask 服务是前台进程没有注册成开机自启。 解决常见做法是给服务做一个 Windows 计划任务开机启动pythonw.exe运行服务脚本不弹黑窗。注意所有路径用绝对路径模板目录和模型路径用os.path.abspath(__file__)定位不要依赖当前工作目录。这个坑很隐蔽你在命令行能启动但计划任务的工作目录可能不是脚本所在目录结果模板加载为空识别结果全是空字符串。5.7 识别率上线后打脸测试集和真实样本分布不一致现象开发时随手测十张感觉很好一上真实样本识别率不到六成。 原因开发时样本来源太单一比如全是用同一个字体生成的或者都是从某个固定种子生成的真实验证码的风格稍有变化模型就懵。 解决每个项目单独建一个回归测试集和目标系统真实验证码对齐。我自己会在上线前固定收集 200 到 500 张真实样本文件名就是标准答案然后每次改任何预处理、分割、模型参数都跑一遍这份测试集做回归对比。看着整体识别率上升才能放开手脚去调。6. 用 200 张真实验证码做回归验证我的调参顺序与上线习惯上线前不要只看 demo。我会把目标系统的验证码截图存到test_samples目录文件名就是答案然后写一个回归脚本每次改动后全量跑一遍import os import requests ok, total 0, 0 for fname in os.listdir(test_samples): if not fname.endswith((.png, .jpg)): continue expect fname.split(.)[0] with open(os.path.join(test_samples, fname), rb) as f: resp requests.post( http://127.0.0.1:8899/recognize, files{image: f}, timeout5 ).json() code resp.get(code, ) total 1 ok (code expect) if code ! expect: print(f{fname}: expect{expect}, got{code}, conf{resp.get(confidence)}) print(faccuracy: {ok}/{total} {ok / total:.2%})调参顺序我踩过不少坑最后固定成这样先调预处理看二值化阈值和形态学核大小对整体识别率的影响再调分割策略对比连通域和等宽分割最后才动识别模型模板匹配就先补模板CNN 就补训练数据。不要一上来怀疑模型不行验证码识别这件事预处理和分割占掉七成以上的成败。我的另一个习惯是把识别失败的样本单独存到fail_samples定期看失败样本里的混淆对。如果 A 和 4 总混就去查是不是resize之后笔画变形如果 C 和 G 混就要考虑是不是腐蚀把 G 的小缺口磨掉了。每次改动上线前保留上一版服务端和回归结果改坏了能立刻回退。这个“后悔药”的习惯让我少熬了好几次夜。本地部署的英文数字验证码识别插件做到这个程度按键精灵、Python 两边都能稳定调用识别率也能拿真实数据说话。剩下的就是把它嵌进你自己的自动化流程里祝你的脚本跑得顺希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

CUDA 报 unsupported GNU version?GCC 兼容处理 2026/10/2 11:26:30

CUDA 报 unsupported GNU version?GCC 兼容处理

周四下午,同事把一台 CentOS 8 的机器推过来,说 CUDA 装不上。我先跑 nvcc -V ,一切正常, nvidia-smi 也能看到卡。然后他执行构建命令,屏幕上一行红字蹦出来: /usr/local/cuda/include/crt/host_con…

阅读更多 →
cursor-free-vip 使用指南:把 Cursor Base URL 改到 TaoToken 的完整配置与验证 2026/10/2 11:26:30

cursor-free-vip 使用指南:把 Cursor Base URL 改到 TaoToken 的完整配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
802.1X与EAP-TLS证书认证部署排错实战:从FreeRADIUS到客户端 2026/10/2 11:26:30

802.1X与EAP-TLS证书认证部署排错实战:从FreeRADIUS到客户端

企业无线网络里,用 802.1X 配合 EAP-TLS 做认证,这两年已经成了很多公司做无密码接入的首选方案。如果你刚接手一套 802.1X 项目,管理员跟你说“证书认证,不用密码”,然后你在 FreeRADIUS、交换机、Windows 客户端之间…

阅读更多 →
jakarta-ant 使用入门:用 TaoToken 统一 Key 跑通 Java 编译工具链 2026/10/2 11:26:30

jakarta-ant 使用入门:用 TaoToken 统一 Key 跑通 Java 编译工具链

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
智慧钢铁工厂信息化:从数据采集到数字孪生的落地实践 2026/10/2 11:26:30

智慧钢铁工厂信息化:从数据采集到数字孪生的落地实践

简介:面向钢铁企业信息化与智能制造建设场景,这份六十五页演示文稿呈现了智慧钢铁工厂的整体解决方案,可供信息化规划人员、项目团队及系统集成商直接借鉴。内容以物联网、大数据、云计算融合的云上物联平台为核心,涵盖智能制造、…

阅读更多 →
从零构建AI系统:深入理解AI工程底层原理与实战 2026/10/2 11:26:23

从零构建AI系统:深入理解AI工程底层原理与实战

1. 这个项目到底在解决什么问题第一次看到 "ai-engineering-from-scratch" 这个标题,我脑子里蹦出来的第一个念头是:又是一个教人调包的教程?但仔细琢磨了一下 "from scratch" 这四个字,我意识到它想做的事情…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉