ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

文字点选验证码识别完整方案:从模型训练到低配部署

2026/9/24 18:10:39 拓冰建站 浏览量
文字点选验证码识别完整方案:从模型训练到低配部署 简介针对点击选择文字验证码识别场景的Python课程设计项目面向需要完成文字点选、选字类验证码识别作业或课设的学生覆盖数据准备、模型训练、接口部署全流程。压缩包共48个文件涵盖19个Python脚本、训练好的best.bin与pre_model.bin模型文件、供测试与展示用的PNG/JPG图片、Gunicorn配置及说明文档整体约121.82MB。已有437人学习下载经python3.6、3.8、3.10环境验证并可在1核2G低配置服务器上无压力运行。项目识别速度约100~300毫秒准确率达96%仅用300张样本完成小样本训练非常适合快速入门验证码识别代码结构清晰附带依赖清单与演示脚本便于直接复现实验或在此基础上进行二次开发、撰写课程设计报告同时前端静态资源与接口示例也一并提供几乎涵盖课设所需全部模块。1. 文字点选验证码识别一份能直接跑的 Python 课设资源做爬虫登录或者接验证码打码平台的人大概率都见过这种交互背景图上散布着若干汉字提示栏给出一句话需要按顺序点击对应的字。这种点选验证码比普通字符验证码难处理的地方在于它不是“单字符识别”而是“检测 识别 排序”三个问题的叠加。这次拆的这份资源就是一套完整的文字点选验证码识别方案模型用了 300 张小样本训练识别速度在 100~300ms准确率标称 96%Windows 下 Python 3.6、3.8、3.10 都测过代码经过编译后能在 1 核 2G 的低配服务器上跑。对做课设的学生、刚接触验证码识别的 Python 开发者或者想把验证码识别能力集成到爬虫项目里的人这份资源能省掉不少从头搭环境、调模型的折腾时间。下面直接拆包看怎么用。2. 先认识这份资源的骨架模型文件与推理链路拿到压缩包后一个很容易犯的错误是直接跑 demo.py结果报错找不到依赖或者模型加载失败。正确做法是先按文件结构把项目职责分清。2.1 目录里哪些文件是核心解压后第一眼看到的东西比较杂有 src、utils、model、static、docs 等目录还有一堆图片和两个 .bin 模型文件。先别看花眼核心其实就这几个model/best.bin 与 model/pre_model.bin训练好的模型权重best.bin 是最终模型pre_model.bin 是预训练模型。src/captcha.py验证码识别核心逻辑负责检测、识别、坐标排序。service.pyHTTP 服务入口把识别能力封装成接口。demo.py本地单张图片识别的演示脚本。requirements.txt依赖清单。gunicorn_conf.py生产部署用的 gunicorn 配置。在课设资源里最常见做法是把训练好的模型序列化成 .bin 文件保存推理时反序列化加载。如果你后续要改成 .pth 或者 .onnx 格式只需要在加载模型那一段调整反序列化方式其他流程不用动。2.2 推理链路一张点选验证码是怎么被识别的文字点选验证码的核心难点在于验证码里的文字不是规整排列的可能倾斜、旋转、缩放背景还有干扰线。因此单靠传统 OCR 工具比如给 Tesseract 调参数效果很差。常见的解决思路是“先检测候选字再识别每个候选字的内容最后按提示词顺序排序”。具体来说推理过程分这几个阶段图像预处理把输入图片缩放成模型输入尺寸归一化像素值到 [0,1]必要时做灰度化或保留 RGB 三通道。文字候选区域检测检测模型在背景图上找出所有可能是文字的候选框。对每个候选框做识别把候选框里的子图送进识别模型得到对应的字符。按提示词匹配拿到提示词比如“点一下 ‘春’ ‘节’ ‘快’ ‘乐’”在识别结果里找到对应文字输出坐标列表。从工程实现来看这份资源识别速度能控制在 100~300ms说明模型结构不会太重大概率是轻量级 CNN 网络。对于课设场景这个速度完全够用。2.3 怎么理解“96%准确率”和“300张小样本”这两个数字要拆开看。“300 张训练样本”听起来很少但对于点选验证码这种任务只要验证码的字体、背景风格比较统一300 张确实能训出一个能用的模型这是这类任务的特点——类别有限、分布集中。但如果验证码换了字体、背景颜色或者干扰线样式准确率可能跌得很快。所以 96% 的准确率应该理解为“在训练集同分布数据上的准确率”换一个验证码源需要重新采集数据微调。提示拿到资源后先别急着对线上验证码跑先用它自带的测试图片验证效果。如果换了数据源效果翻车优先考虑用新数据微调而不是调推理代码。3. 把训练数据跑起来从原始验证码到可训练样本这份资源不只是推理代码还包含训练相关的内容。但课设包里的训练脚本通常没有打包成开箱即用的形态需要自己动手组织数据。这里讲清楚数据怎么组织、脚本怎么改、参数怎么设。3.1 数据标注格式先明确你的训练样本长什么样要训练一个“检测 识别”模型数据标注需要两层信息每个文字框的位置坐标以及框内文字的内容。常见格式有两种VOC 格式用 XML 文件记录图片名、图片尺寸、每个目标框的类别和坐标。YOLO 格式用 txt 文件记录类别 id 和归一化后的中心点坐标、宽高。这份资源里没有自带标注工具但你可以用 LabelImg 手动标注一批数据或者写脚本自动生成仿真验证码。对课设来说最简单的路径是先用“合成验证码”的方式生成训练集代码里用 PIL 在背景图上随机位置绘制文字自动生成标签省去手动标注的痛苦。import random from PIL import Image, ImageDraw, ImageFont # 合成一张点选验证码训练图 def synthetic_captcha(bg_path, text_chars, font_path, output_path): bg Image.open(bg_path).convert(RGB) draw ImageDraw.Draw(bg) font ImageFont.truetype(font_path, 28) labels [] for char in text_chars: # 随机位置避免文字重叠 x random.randint(10, bg.width - 60) y random.randint(10, bg.height - 60) draw.text((x, y), char, fontfont, fill(random.randint(0,255), random.randint(0,255), random.randint(0,255))) labels.append({char: char, x: x, y: y, w: 40, h: 40}) bg.save(output_path) return labels labels synthetic_captcha(bg.jpg, [春, 节, 快, 乐], simhei.ttf, sample_001.jpg) print(labels)逻辑说明这段脚本做了两件事——在背景图的随机位置画上指定文字同时把每个文字的坐标和内容记下来。label 的格式是每个候选框的左上角坐标加宽高这是目标检测训练时最常用的标注方式。参数说明bg_path 是背景图路径text_chars 是你要写入的字符列表font_path 指定字体文件output_path 是生成图片的保存位置。文字大小由 font 的 ptsize 参数控制这里设为 28实际训练时需要和模型输入尺寸匹配否则候选框大小会不对。3.2 把合成数据切成训练集和验证集小样本训练更要讲究数据划分。300 张图听起来少但如果不做划分模型很容易过拟合到训练集上换几张新图就崩。常见的做法是 8:2 划分训练集 240 张、验证集 60 张。同时加数据增强随机旋转、平移、颜色抖动。import os import shutil import random data_dir captcha_dataset train_dir captcha_dataset/train val_dir captcha_dataset/val os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) images [f for f in os.listdir(data_dir) if f.endswith(.jpg)] random.shuffle(images) split_idx int(len(images) * 0.8) for img in images[:split_idx]: shutil.copy(os.path.join(data_dir, img), os.path.join(train_dir, img)) for img in images[split_idx:]: shutil.copy(os.path.join(data_dir, img), os.path.join(val_dir, img)) print(ftrain: {split_idx}, val: {len(images) - split_idx})逻辑说明这段代码按 8:2 比例随机划分数据集把图片文件复制到 train 和 val 目录。参数说明split_idx 是划分点索引random.shuffle 保证每次运行顺序不同。注意如果你的数据标注文件XML 或 txt和图片同名同步复制标签文件否则训练时会出现图片没有对应标注导致报错。3.3 训练脚本怎么跑参数设置建议训练脚本不在压缩包根目录的显眼位置通常在 src 或 utils 里文件名可能是 train.py。跑之前先把 requirements.txt 的依赖装齐然后用下面的命令启动训练pip install -r requirements.txt python src/train.py --data captcha_dataset --epochs 100 --batch-size 32 --lr 0.001 --img-size 160参数说明--data 指定数据集根目录训练脚本会在里面找 train 和 val 子目录--epochs 设为 100 是因为小样本场景训练轮次太少收敛不足太多又容易过拟合100 轮配合 early stopping 是常见做法--batch-size 32 在 1 核 2G 的 CPU 机器上也能跑显存不够可以降到 8 或 16--lr 0.001 是 Adam 优化器比较稳的初始学习率--img-size 160 是模型输入尺寸点选验证码通常不需要太高分辨率160x160 或 224x224 够用。如果训练过程中 loss 不下降优先检查学习率是不是太大、数据标注坐标有没有归一化出错。另一个常见问题是训练集和验证集的输入尺寸不一致用 PIL 读取图片后忘记 resize导致模型输入维度报错。提示训练时把 early stopping 打开一般 patience 设 10 轮。小样本训练时验证集准确率波动大不要只看最后一轮的模型把验证集准确率最高的那一轮保存下来对应到这份资源里就是 best.bin 的由来。4. 部署成本地服务从 demo.py 到 HTTP 接口模型训练好之后下一步是把它变成能用的服务。压缩包里给了 demo.py 和 service.py 两个入口前者用于本地测试后者用于部署 HTTP 接口。这两个脚本的职责要分清楚避免在部署时踩坑。4.1 先跑通 demo.py验证单张图片识别链路demo.py 是最简单的入口运行方式通常是python demo.py --image res.jpg --model model/best.bin它的内部逻辑是加载模型文件读取图片调用 src/captcha.py 里的识别函数输出文字坐标列表。第一次跑通之前别急着改任何代码先把依赖环境解决掉。# demo.py 核心逻辑简化版 import sys from src.captcha import CaptchaSolver def run(image_path, model_path): solver CaptchaSolver(model_path) result solver.solve(image_path) # result 形如 [{char: 春, x: 120, y: 80}, ...] for item in result: print(f{item[char]}: ({item[x]}, {item[y]})) if __name__ __main__: run(sys.argv[1], sys.argv[2])逻辑说明这里示意了 demo.py 的调用方式CaptchaSolver 负责加载模型和图片、执行推理。参数说明image_path 是待识别的验证码图片路径model_path 是模型权重文件路径。注意这里命令行传参的参数顺序如果你的图片路径包含空格记得加引号。4.2 service.py把识别能力封装成 HTTP 接口生产环境里验证码识别通常是作为独立服务提供给爬虫或其他业务模块调用的。service.py 做的事就是封装一个 HTTP 接口接收图片文件或 base64 字符串返回识别坐标结果。常见的实现方式是基于 Flask 或 FastAPI这里以 Flask 为例# service.py 核心逻辑简化版 from flask import Flask, request, jsonify from src.captcha import CaptchaSolver app Flask(__name__) solver CaptchaSolver(model/best.bin) app.route(/recognize, methods[POST]) def recognize(): file request.files.get(image) if not file: return jsonify({code: 1, msg: no image}), 400 result solver.solve(file.stream) return jsonify({code: 0, data: result}) if __name__ __main__: app.run(host0.0.0.0, port8080)逻辑说明service.py 把模型加载放在模块导入阶段避免每次请求都重复加载模型造成内存浪费。参数说明/recognize 接口接收 multipart/form-data 格式的图片字段名是 image也可以用 request.get_json() 接收 base64 字符串在接口内部用 base64.b64decode 转成图片再识别这种方式更适合验证码图片从前端直接传 base64 的场景。4.3 gunicorn 部署低配服务器上怎么跑在 1 核 2G 的服务器上部署时直接python service.py启动 dev server 会有一个问题Werkzeug 自带的服务器是单进程单线程的并发能力很差。生产环境用 gunicorn 是常见做法而且配置在 gunicorn_conf.py 里已经写好gunicorn -c gunicorn_conf.py service:appgunicorn_conf.py 里几个关键参数值得注意# gunicorn_conf.py 关键配置示例简化版 workers 2 threads 2 timeout 30 preload True参数说明workers 2 是 worker 进程数对于验证码识别这种轻量计算任务workers 数可以设为 CPU 核数的 1~2 倍线程数 threads 2 可以提升单 worker 的吞吐timeout 30 是 worker 超时时间如果模型推理偶尔卡住超过 30 秒未返回会自动重启 workerpreload True 表示在创建子进程之前加载模型多个 worker 共享同一份模型权重文件节省内存。需要强调的是低配服务器上 workers 不建议设太高2 个 worker 加 2 个线程对 1 核 2G 的机器已经够用了。4.4 100~300ms 的耗时怎么来的这个耗时包含了图片预处理、模型推理、坐标排序的完整链路通常模型推理占大头。如果你发现自己的机器上跑一次要 1 秒以上优先检查三件事是否用了 CPU 推理但没装对应框架的 CPU 优化版本比如 PyTorch 的 CPU 版。图片是否做了不必要的 resize 上采样输入尺寸越大耗时越长。是否每次请求都重新加载模型正确做法是像 service.py 那样在启动时加载一次后续只做推理。5. 避坑指南四个最容易翻车的问题这一章写我在实际跑这类资源时遇到过的真问题。每条都是“现象 → 原因 → 解决”的结构直接给结论。5.1 依赖安装不上cv2 报错现象pip install -r requirements.txt 时opencv-python 安装失败提示找不到对应版本或者编译报错。原因最常见的两种一是 Python 版本和 opencv-python 版本不兼容尤其是 Python 3.10 刚出来那阵子二是 pip 默认源下载速度慢导致超时或下载损坏。解决先升级 pip再指定国内源安装比如阿里云或清华源。再不行就用 opencv-python-headless 替代验证码识别不需要 GUI 界面headless 版本更轻量也不依赖 QT 库。命令如下pip install --upgrade pip pip install opencv-python-headless -i https://pypi.tuna.tsinghua.edu.cn/simple5.2 模型加载失败的“玄学”路径问题现象demo.py 报错找不到 model/best.bin或者在 Linux 服务器上能跑、Windows 上跑不了。原因很多课设代码里用了相对路径加载模型比如model_path model/best.bin。这个路径依赖于执行命令时的当前工作目录。如果在 Windows 上直接用 IDE 运行工作目录可能是项目根目录也可能不是在 Linux 上用 systemd 启动服务时工作目录往往被改为 / 或其他目录自然找不到模型文件。解决改成基于文件所在目录的绝对路径拼接无论从哪里启动都能找到模型import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) MODEL_PATH os.path.join(BASE_DIR, model, best.bin)从那以后我每次写加载模型或资源文件的代码都强制用这种方式拼路径这已经成了习惯能省掉很多半夜被线上告警叫醒的麻烦。5.3 准确率达标但点击坐标偏了现象识别结果中文字内容是对的但输出的坐标不对比如应该点“春”但点到了“春”旁边的干扰线上。原因这是图片缩放导致的坐标偏移。推理时为了适应模型输入尺寸会把图片 resize 到 160x160模型输出的坐标是基于 160x160 尺寸的输出后必须映射回原图尺寸。如果代码里忘了做逆映射或者映射比例算错就会坐标偏移。解决resize 时记住宽高的缩放比例推理完成后按比例还原坐标。比如原图是 320x240resize 到 160x160那么 x 方向比例是 2y 方向比例是 1.5原始坐标等于模型输出坐标乘以这个比例。注意宽高比例要分别计算不能混用一个值。scale_x orig_width / target_width scale_y orig_height / target_height orig_x int(pred_x * scale_x) orig_y int(pred_y * scale_y)很多课设项目在这个地方做得不严谨直接拿模型输出的坐标去点击结果准确率高但实际点不中。验证方式是打印出原始坐标和还原后的坐标手动对照图片看是否对齐。5.4 小样本训练后换一种验证码就失效现象用资源自带的模型测试自己的验证码截图识别率不到 50%甚至一个都识别不对。原因这不是代码 bug而是数据分布差异。300 张训练样本只能涵盖某种特定字体、特定背景的验证码。你的验证码如果字体、颜色、背景纹理和训练数据差异大模型自然失效。解决两条路。第一采集目标验证码 300~500 张用 LabelImg 标注后微调模型第二如果不想手动标注用 3.1 节的合成数据方案把目标字体和背景模拟出来生成 300 张训练数据重训。合成数据的成本比标注低得多效果也够用。5.5 服务器部署后并发请求卡死现象gunicorn 启动正常单次请求识别正常但并发一上来就超时或 502。原因模型推理是 CPU 密集型任务1 核 2G 的服务器本来算力就有限workers 数超过 CPU 核数反而会触发频繁的上下文切换降低吞吐。解决把 workers 调整为 1threads 调为 4。1 核 CPU 上多进程不如多线程因为 Python 的 GIL 在推理框架内部通常会释放线程级别的并发在推理场景比进程级别更高效。实测 1 核 2G 的机器用 1 worker 4 threads 能扛住日常爬虫并发单次识别耗时稳定在 200ms 左右。6. 进阶技巧写一个批量回归脚本把模型真实水平测出来项目上线前一定要做一次批量回归测试别拿一两张图试完就以为没问题。方法很简单准备一个标注好的测试集50~100 张就行写脚本批量调用识别接口统计准确率和平均耗时再把失败样本单独导出看是哪些图识别错了这样能快速定位模型短板还是代码 bug。import requests import os import time import json def batch_test(test_dir, api_urlhttp://127.0.0.1:8080/recognize, label_filelabels.json): total 0 correct 0 cost_list [] failed_samples [] with open(label_file, r, encodingutf-8) as f: labels json.load(f) for img_name, expected in labels.items(): img_path os.path.join(test_dir, img_name) start time.time() with open(img_path, rb) as f: resp requests.post(api_url, files{image: (img_name, f)}) cost_ms (time.time() - start) * 1000 cost_list.append(cost_ms) result resp.json()[data] # 比较识别出的字符顺序是否与期望一致 pred_chars [item[char] for item in result] total 1 if pred_chars expected: correct 1 else: failed_samples.append({img: img_name, pred: pred_chars, expect: expected}) avg_cost sum(cost_list) / len(cost_list) print(f准确率: {correct}/{total} {correct / total:.2%}) print(f平均耗时: {avg_cost:.1f}ms) with open(failed_samples.json, w, encodingutf-8) as f: json.dump(failed_samples, f, ensure_asciiFalse, indent2) batch_test(test_images, http://127.0.0.1:8080/recognize, labels.json)逻辑说明labels.json 里存的是每张测试图的期望字符列表比如{img_001.jpg: [春, 节, 快, 乐], ...}。脚本依次调用 HTTP 接口用返回的字符顺序与期望顺序比对完全一致才算识别正确。参数说明test_dir 是测试图片目录api_url 是服务地址label_file 是标注文件路径。输出两份结果——准确率和平均耗时打印在控制台失败样本写入 failed_samples.json 方便人工复核。这套批回归脚本的价值在于它能逼你定义清楚“准确率”的度量方式。如果你的验证码要求按顺序点击那么字符顺序错了也算识别失败如果只要求点对位置那就要用坐标匹配来比对。度量方式不同得出的准确率可能差 20 个百分点。我那会儿跑完批回归才发现单字识别准确率虽然高但整图按顺序点击的成功率会掉一截后来在输出坐标前先对字符识别结果做一次排序过滤才把整体准确率提上来。从那以后验证码识别相关的改动我都会强制走一遍批回归再上线——这比在任何单张图上反复调参都管用。希望帮到你。本文还有配套的精品资源点击获取