南大统一身份认证验证码识别：从数据集构建到模型部署的全流程开源实践

https://www.do1e.cn/posts/deepl/nju-captcha

前言

之前写的 NJUlogin 中账号密码登录需要进行验证码识别，当时采用了 ddddocr 并且有了不错的精度。

同时也部署了一个服务端并找朋友帮忙写了一个油猴脚本，让我在每次需要登录时自动填充验证码（账号密码浏览器会自动填充），因此只需要点击登录即可。

不过最近想着想把识别模型做得更加轻量一些，更方便边缘端部署，于是有了这个项目。（求个 Star 好吗 ＞︿＜，如果你只想使用，并不想了解相关技术，直接拉到最后，推荐 NJU server api 版本）

数据采集

https://github.com/Do1e/NJUcaptcha/tree/main/build_dataset

数据集集构建基本上是自动化进行的，主要依赖以下两个工具：

ddddocr：用于初步识别验证码

NJUlogin：用于验证识别结果的正确性

稍微魔改了一下 NJUlogin 判断识别的正确与否，然后分别保存到不同的文件夹下，识别错误（大概几百张？）的手动重命名一下即可。
为了采集者 100,000 张图片，在后台跑了大概 3~4 天，time.sleep还不能太小，不然会封 IP。 ＞︿＜

于是有了这个数据集，欢迎下载使用，包含 100,000 张验证码图片，文件命名格式为 {验证码文本}_{图片md5}.jpg，验证码文本均为小写。
数据集下载链接：NJU-captcha-dataset.7z
解压密码：@Do1e

Dataset 如下：

https://github.com/Do1e/NJUcaptcha/blob/main/model/dataset.py

识别模型

https://github.com/Do1e/NJUcaptcha/tree/main/model

有了数据之后就可以设计模型并进行训练了。模型设计这次我完全交给了 AI 帮我完成， 效果也还算 OK。

模型大小 12.98MiB -> 2.25MiB
模型精度 99.37% -> 99.83%
吞吐率 173.95 images/sec -> 1076.56 images/sec [AMD Ryzen 7 8845H]

https://github.com/Do1e/NJUcaptcha/blob/main/model/model.py

也许可以再小一点？留着下次升级吧

服务端部署

https://github.com/Do1e/NJUcaptcha/tree/main/service

之前也使用 fastapi 简单实现了一个识别服务器，识别接收到的 base64 图片并返回验证码的内容，这次也借着开源的机会部署到了 vercel 上。Linux 下测试命令：

Copy
curl -s -L "https://authserver.nju.edu.cn/authserver/captcha.html" -o "captcha.jpg" && [ -f "captcha.jpg" ] && curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "captcha=$(base64 -i captcha.jpg | tr -d '\n')" "https://njucaptcha.vercel.app" || { echo "Failed to download captcha image"; exit 1; }

油猴脚本自动填充

正如前言所述，为了实现登录时不用人工识别并输入验证码，写了油猴脚本自动填充。之前的版本为基于服务端的：

https://github.com/Do1e/NJUcaptcha/blob/main/njucaptcha.user.js

开源代码里用的还是 vercel 的服务，速度很慢，而且必须登录 p.nju，而在需要登录 p.nju 时不起作用。 (￣﹃￣)

我自己的解决方案是在校内搭建服务并通过 frp 映射到我公网的服务器，并且在登录 p.nju 时访问内网服务：

Copy
const url_pub = 'https://example.com/';
const url_nju = 'https://nju.example.com/';
const currentUrl = window.location.href;
const serverUrl = currentUrl.includes('//p.nju.edu.cn') ? url_nju : url_pub;

这次整个项目最难的还是如何直接在客户端执行 onnx 的推理，借助 AI 工具捣鼓了好几个小时才成功搞定。使用 ONNX Runtime Web 实现。

https://github.com/Do1e/NJUcaptcha/blob/main/njucaptcha_onnx.user.js

onnx 版本的一个缺点是在没有缓存时需要联网并科学上网下载一些必要的推理依赖，不过还在第一次使用后能缓存（ort-wasm-simd-threaded.jsep.mjs 和 ort-wasm-simd-threaded.jsep.wasm 只能缓存 7 天，也不算太长，如果有大佬有办法实现像 @resource 一样的近乎永久缓存，欢迎提交 PR）。

总之上述两种方案各有优缺点吧，最推荐的还是按照我的方式自行部署使用，或者直接使用我在文末给的 NJU server api 版本。

上述版本的油猴脚本可点击下面的链接直接安装（在已安装油猴插件的前提下）：

| | vercel api 版本 | NJU server api 版本 | onnx 本地推理版本 |
| :--- | :--- | :--- |
| 优点 | 不用科学上网 | 最佳实践，个人认为比较完美 | 很快，页面加载完成前就已经填充完毕，且能够在登录 p.nju 时使用（有缓存的前提下） |
| 缺点 | 很慢，且无法登录 p.nju 时使用 | 需要有校内外服务器进行部署，我毕业后将无法使用 | 无缓存需要科学上网缓存部分文件，无法登录 p.nju 时使用，且缓存仅有 7 天 |

注：这次的代码使用 GPL-3.0 开源协议，请忽略下述有关开源协议的说明。懒得改网页代码，我的网站解释权在我没毛病吧