news 2026/10/17 3:17:05

中文点选验证码自动识别方案:从目标检测到坐标排序的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中文点选验证码自动识别方案:从目标检测到坐标排序的工程实践

做政务系统自动化测试的朋友,八成都被“中文点选验证码”折磨过。一张背景花里胡哨的图片,上面零散分布着七八个汉字,系统随机提示“请依次点击(安)(全)(第)(一)”,你得顶着各种扭曲字体、干扰线和背景纹理找到这些字,按顺序点对位置。我最近就在某政务平台的接口联调里反复和这种验证码打交道,从最初的人工标记,到后来搭出一套能够自动定位汉字并输出点击坐标的识别方案,前后折腾了半个多月。这篇文章把整套思路、踩过的坑、以及最后沉淀下来的可复用结构整理一下,给同样被验证码卡住的朋友做个参考。

这个方案能解决什么?本质上是把“图片中找字并按顺序点击”这件事,拆成目标检测、文字识别、坐标排序三个环节。输入一张验证码图片,输出的是按提示顺序排列的点击坐标列表。对自动化测试、无障碍辅助、授权后的数据归档这类合规场景,能把大量人工盯屏的时间省下来。适合正在做政务、金融、企业内部系统自动化项目的开发或测试同学参考,也适合对验证码识别技术感兴趣的人了解整体实现思路。

1. 项目概述与核心需求拆解

1.1 中文点选验证码为什么难处理

中文点选验证码属于行为式验证码的一种,常见的有滑块、旋转、图标点选、文字点选。文字点选的具体形态很多:有的给一句话,让你按语序点击;有的给一组目标字,让你按指定顺序点击。虽然形式上有点不同,但共同点都是“先识别图中文字,再精准点击位置”。

难点主要有几个:第一,字体不是标准打印体,会有圆体、楷体、手写体,同一个字在不同图片里写法差异很大。第二,背景不是纯色,经常叠加纹理、噪点、干扰线和背景文字,边缘检测很容易失真。第三,文字之间存在旋转、缩放、透视,传统模板匹配基本无效。第四,部署环境的算力受限,政务内网很多还是CPU机器,模型不能太大。

因此,我最终没有选择直接套用通用OCR库,而是基于目标检测+单字分类的流水线来做,理由后面详细说。

1.2 项目到底要解决什么问题

这个项目的直接需求,来自某政务平台的接口联调:因为业务流程里要模拟用户操作,而登录环节有中文点选验证码,测试流程在验证码处必须人工介入,导致自动化用例没法完整跑通。原本的验证码识别是让人工在脚本暂停时手动点击,效率很低,半夜跑回归脚本时更是痛苦。

我接手后第一件事不是写模型,而是把需求问清楚:是要100%识别所有验证码吗?不是。测试环境是内部可控的,允许用现成模型,允许识别失败后自动重试。只要单张识别耗时不超过1秒,整体通过率能到90%以上,就比人工速度快很多。这也让我后续的设计可以不用追求极端精度,而优先保证速度和稳定性。

所以,这里的“识别”并不是为了绕过真实生产环境的安全风控,而是在授权测试环境里替代人工操作。这一点非常重要,整个实现思路和安全边界都建立在这个前提之上。

1.3 验收标准怎么定

结合前面的需求,我给自己定了三个指标:单张验证码从输入图片到输出坐标的耗时控制在1秒内(CPU推理);在测试环境采集的1000张真实验证码样本上,按顺序输出的坐标准确率不低于90%;模型和预处理管线能用Python脚本调用,也可以封装成HTTP接口。这三个指标直接决定了后面的技术选型和模型规模。

准确率定义也要说清楚:判定一次点击是否算对,不是看检测框和真实框的IoU,而是看最终按提示顺序点击的坐标是否落在目标汉字的中心区域。行业内通常以目标框中心周围一定像素范围内为正确。这种定义更贴近实际点击行为,也避免为了追求过高的IoU而过度调优。

2. 整体方案设计思路

2.1 任务拆解:不是OCR,而是检测+分类+排序

拿到验证码图片,我的第一反应是能不能用现成的OCR引擎直接把文字识别出来。试了某开源中文OCR模型,问题很明显:它适合印刷体、文本行,而这种点选验证码的文字位置随机、不构成句子、有旋转和扭曲,OCR把每个字识别出的顺序和位置无法直接对应,最后得到的框和文字匹配也很乱。

正确的做法是把任务拆成两步:先用目标检测模型找出图片里每一个候选汉字的边界框,然后对每个裁剪出来的小图片做单字分类,识别出这个框里到底是哪个字。得到“位置+文字”的对应关系后,再根据提示词顺序去检索对应坐标。这样的好处是,目标检测模型不需要认识几千个汉字,只需学会“这里是不是一个字”,分类模型则不需要关注字在哪,专注判断字形。两个任务各自简单,模型也更容易训练。

2.2 为什么选YOLOv5做检测

目标检测方案里,我对比过YOLOv5、PaddleDetection和单纯的传统轮廓检测。传统方法在简单背景下还行,遇到背景干扰多的验证码几乎必翻车。PaddleDetection功能全但依赖重,政务内网离线部署很头疼。最后选YOLOv5s,理由很简单:模型文件小(约14MB),推理快,训练社区资料多,而且对CPU环境友好,导出ONNX后可以在不装PyTorch的机器上运行。

这里要特别说明,我训练YOLO时并没有让模型直接输出“这是哪个汉字”,而是把所有汉字归为一类“text”,也就是二分类检测。原因在于,验证码的候选字来自一个动态字库,如果直接让YOLO输出几千类,每一类的样本可能都不够,收敛困难,模型体积也会爆炸。检测只负责框位置,识别交给后面的分类网络,分工明确,也方便未来扩展字库。

2.3 单字分类模型怎么选

在YOLO裁出汉字小图后,我用一个单字分类模型判断具体是什么字。候选方案有CRNN+CTC、PaddleOCR的识别分支、以及轻量分类网络。实测下来,由于这里每个框只有一个字,不存在序列建模需求,直接用图像分类网络更简单。我选择了ResNet18,输入64x64,全连接输出字符集大小的向量。字符集按验证码出现的汉字统计出来,大概是500个常用字加上数字和字母,总共约520类。

很多人觉得汉字识别要用OCR,其实在单字场景下,分类网络反而更快更准。CRNN的优势在于变长文本,用在单字上白白增加复杂度。为了让模型对打印体、手写体都鲁棒,我在训练时对裁剪图做了随机旋转、缩放、颜色抖动、高斯模糊等增强。这一部分在精度提升上见效最快。

2.4 数据准备与半自动标注

做验证码识别最大的难点不是模型,而是数据。由于真实验证码不能大量拉取,我采用“合成数据为主、真实数据为辅”的策略。我先收集了一些开源字体,包括黑体、宋体、楷体、圆体,每种字体随机生成汉字,再按验证码的风格加上干扰线、噪点、背景纹理,最后随机旋转和透视变换,输出带标注框的图片。合成软件就是自己写的一个Python脚本,大概生成了2万张。

真实数据方面,经授权从测试环境抓取了一千多张真实验证码,我和同事手工标注了一部分,用LabelImg框出每个汉字的位置和内容。标注很费时间,所以后来我自己写了个半自动脚本:先用初期模型对未标注图片做预标注,再人工修正。这样人工成本降了大概70%,数据迭代速度快很多。

3. 核心实现细节

3.1 图像预处理:不要过度处理

刚开始我花了很多时间在图像预处理上,试了灰度、二值化、中值滤波、形态学开闭运算,想尽量把干扰线去掉。但很快发现两个问题:第一,过度的预处理会把浅色文字也抹掉,尤其当文字颜色和背景颜色接近时;第二,验证码图片本身分辨率不高,二值化后字形细节丢失严重,导致检测模型特征不充分。

最后我只保留了三种轻度预处理:缩放统一到640x640、归一化、以及限制对比度自适应直方图均衡化(CLAHE)。CLAHE能在不破坏颜色结构的前提下增强局部对比度,对检测和后续分类都有帮助。干扰线和背景纹理不再靠规则去清洗,而是交给卷积网络在训练中自动学会忽略。这个思路的转变让整体准确率提升了不少,所以想提醒大家:不要和预处理较劲,让模型去学习干扰分布往往更有效。

3.2 YOLOv5检测模型训练要点

数据准备好了,训练反而是最顺利的环节。我用的是YOLOv5s,输入分辨率640x640,batch size 16,训练100轮,优化器AdamW,初始学习率0.01,用了余弦退火和预热。因为只需要检测一类“text”,任务比较简单,模型很快就收敛,验证集上的mAP@0.5达到了0.98左右。不过mAP高不代表最终点击准确率高,因为很多误检框虽然小,但会影响后续匹配。

这里有几个关键点:一是标注框要尽量贴合汉字的外接矩形,不要包含太多边距,否则分类阶段输入不干净;二是对扭曲严重的样本做复制粘贴增强,让模型见过更多形变;三是推理时NMS的IoU阈值要适当调低一点,比如0.3,避免重叠框干扰后续分类。这些都算经验值,不同数据集可能需要微调。

3.3 单字分类模型训练技巧

分类模型的结构很简单,但训练时有一些坑。首先,类别不平衡很严重,“的、一、是、不”这类高频字出现很多,生僻字很少。我用了加权采样,确保每轮每个类别至少出现一定次数。其次,合成图片和真实图片风格差异大,直接训练后真实样本上准确率只有70%左右。我加了风格迁移和随机背景增强,并用了大约800张真实标注图片做微调,最终真实测试集上分类准确率也到了97%上下。

另一个技巧是,对于分类置信度低于0.5的框,我倾向于丢弃而不是硬猜。因为点选验证码通常有几个候选字,而提示字一般都在图中,如果某个提示字分类置信度低,很可能是检测框切偏了,或者字形被干扰,这时不输出该字坐标,触发重试会比硬猜更有效。

3.4 从模型输出到点击坐标的完整流程

整个推理流程可以用一段伪代码概括。首先是加载ONNX模型,YOLO输出检测框,分类模型输出每个框的文字类别。然后遍历提示文字列表,对每个目标字在检测结果里找类别一致的框,取置信度最高的那个,输出中心点坐标。如果某个字没找到,就返回识别失败,由上层逻辑重试。

import numpy as np def recognize_captcha(img, prompt_chars, detector, classifier): boxes, scores = detector.detect(img) # [N, 4] + [N] results = [] for box, score in zip(boxes, scores): if score < 0.3: continue crop = crop_image(img, box) # 裁剪汉字区域 char, conf = classifier.predict(crop) if conf >= 0.5: results.append((box, char, conf)) point_list = [] for ch in prompt_chars: match = [r for r in results if r[1] == ch and r[2] >= 0.5] if not match: return None # 识别失败,重试 match.sort(key=lambda x: x[2], reverse=True) x_center = int(match[0][0][0] + match[0][0][2] / 2) y_center = int(match[0][0][1] + match[0][0][3] / 2) point_list.append((x_center, y_center)) return point_list

实际部署时,我用ONNX Runtime加载模型,在普通4核CPU上单张图片推理耗时约0.6秒,其中YOLO占0.4秒,分类器对每个候选框各跑一次,占0.2秒。满足验收指标。如果换GPU可以压缩到0.1秒以内。

3.5 接口封装与重试策略

为了让测试脚本调用方便,我把识别流程封装成HTTP服务,接口只接收图片文件,返回坐标列表或识别失败。为了减少失败率,单张验证码最多允许三次识别重试:第一次正常流程,第二次对图片做一次轻度锐化,第三次把检测阈值降低0.1。实测整体通过率能从88%提升到93%左右。重试逻辑一定要放在调用方而不是服务内部,避免一个慢请求占满线程池。

4. 实操过程与踩坑记录

4.1 标注那点事

标注真实验证码是我做过最枯燥的工作之一。有一千多张图,每张图平均7个字,意味着近万个框要画。刚开始用LabelImg逐个框选,一天最多标200张,而且越到后面越容易手滑标错类别。后来我改用半自动标注:先用早期模型检测,再把模型预测的框和文字结果导出来,人工只修正错误的框。这个方法虽然前期要花一天搭环境,但能持续用在整个项目迭代中,非常值得。

另外,不要忘记在标注时把“提示文字”和图片上的“背景文字”都框出来。很多人只框目标汉字,但背景文字如果出现在图上,实际上是被点选区域之外的干扰,不标注也没关系。我最初只框候选字,导致模型对背景文字完全无感,实际推理时遇到干扰文区域出现过误检,后来在合成数据里加入随机背景文字,问题才缓解。

4.2 一个让我困惑很久的检测偏移问题

有段时间,模型检测出的汉字框总是偏左或偏上。排查后发现是标注时人的习惯问题:我习惯把文字区域框得稍微紧凑,但有些同事会把边距留得很大,导致同一批数据标注不一致,模型学到的框中心偏移。解决方法是重新统计所有标注框的宽高比,做了一次统一裁剪:在训练时把标注框向外扩展20%,确保模型学到的是包含完整字形的区域。这个细节直接影响后续分类准确率,务必重视。

4.3 合成数据与真实数据差异的修正

第一版模型在合成测试集上mAP很高,但一跑真实截图准确率崩到70%多。对比发现,合成的背景太干净,干扰线是直线,而真实验证码有很多曲线干扰和纹理。我调整了合成脚本,加入贝塞尔曲线干扰、彩色的随机矩形、颗粒噪点,并随机改变文字颜色和边框色,这样合成样本和真实样本的分布更接近。随后用真实样本微调,准确率才慢慢涨回来。合成数据的价值在于让模型学到汉字结构,真实数据的价值在于适配特定验证码风格,两者缺一不可。

4.4 排查速查表

现象可能原因解决办法
检测框漏掉某个字该字和背景颜色太接近调低检测阈值;训练时加入低对比度增强
把背景字误检为候选字背景文字区域干扰训练时合成背景文字;后处理按颜色或置信度过滤
分类结果偶尔错裁剪框包含相邻字边扩展检测框的统一边距后重新裁剪
提示字在图中找不到字符集未覆盖该字检查字库,把生僻字符加入分类类别
推理速度超标分类模型太大换MobileNetV3,或只对置信度高于阈值的框做分类

这一套排查方法不只适用于点选验证码,对任何“检测+分类”形式的目标识别项目都有参考意义。

5. 合规与安全边界

5.1 验证码识别的合理用途

很多人看到验证码识别,第一反应是爬虫或风控对抗。但在我接触的项目里,更多是正向需求:自动化测试平台需要模拟真实用户点击;无障碍辅助工具需要帮助视障用户完成验证码操作;企业内部系统在做历史数据迁移时,需要把旧页面的验证码流程自动化。这些场景都有明确授权,识别验证码只是流程里的一环,而不是为了绕过安全措施。

如果你也想做类似项目,我的建议是先确认自己有没有权限接触目标系统,测试环境是否受控,以及是否有书面的授权范围。在这个前提下做技术选型,心里踏实很多,出了问题也有据可依。

5.2 哪些操作坚决不能碰

技术方案本身是中性的,但用在错误的地方就会变成问题。比如,拿验证码识别去批量注册账号、抢票、爬取用户信息,或者在没有授权的情况下突破真实业务系统的风控,这些都是明显越界的行为。轻则账号被封,重则涉及法律风险。尤其涉及政务、金融类系统,安全合规要求更高,我非常不鼓励把这个方案直接套用到任何未经授权的真实生产环境上。

文章中提到的方法、模型结构、训练思路,希望读者把它当作一份技术笔记,用来解决自己授权范围内的自动化问题,而不是拿去挑战安全边界。我在最后这部分把话说得重一些,是因为做技术的人更需要对边界保持敏感。

5.3 我的几条长期建议

第一,不要把模型权重和完整识别服务公开分享,尤其是针对具体平台的模型,因为这会成为绕过该平台验证码的工具。分享思路、讲解原理、给出不依赖特定网站数据的示例代码,已经足够帮助同行。第二,如果项目结束后还需要长期维护,建议在服务层加访问控制,只在测试内网开放接口。第三,定期用真实的授权样本做回归测试,因为验证码样式会变化,模型也需要跟着迭代。这些都是从实际项目中得到的教训,希望对你有用。

最后说点个人体会吧。做这个项目最大的收获,不是把准确率堆到多高,而是学会了在“能不能做”和“该不该做”之间划线。验证码识别本质是个工程问题,检测、分类、坐标映射都有成熟方案,真正难的是想清楚自己要解决什么、在什么边界内解决。如果你也在做类似的自动化测试项目,建议先拿到书面授权,再动手去搞模型;如果没有这个前提,再漂亮的识别效果也没有意义。另外,合成数据+真实微调的思路,后来被我用到别的图像识别项目里,也省了不少标注时间,算是意外的收获。希望这些踩坑记录能帮你少走点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 3:16:26

Servlet实战全解:从生命周期到会话管理,吃透Java Web核心

Java Web 这个方向里&#xff0c;Servlet是那种“你绕不开、躲不掉、最后还得感谢它”的老前辈。哪怕你现在项目里全是 Spring Boot 起步&#xff0c;Swagger 文档点得飞起&#xff0c;底层处理 HTTP 请求的核心组件依然是 Servlet——那个藏在 DispatcherServlet 背后真正干活…

作者头像 李华
网站建设 2026/10/17 3:16:11

ChartCtrl移植VS2015:MFC图表控件编译修复与GDI性能优化

简介&#xff1a;一套针对VS2015优化过的老牌ChartCtrl图表控件源码&#xff0c;适合需要在MFC桌面应用中嵌入曲线、柱状、蜡烛、甘特等图表的Windows开发者&#xff0c;也适合希望研究自定义绘图控件实现与迁移老代码的进阶学习者。压缩包共86个文件&#xff0c;以41个头文件、…

作者头像 李华
网站建设 2026/10/17 3:15:44

BGP路由聚合详解:华为设备原理、配置与实验

打开BGP路由表&#xff0c;我第一眼看的永远是那条“总路由数”。做网络越久越明白&#xff0c;BGP最难熬的不是协议本身&#xff0c;而是路由表大到一定程度之后&#xff0c;设备资源、链路带宽、故障收敛速度全都跟着遭殃。BGP路由聚合就是在这样的背景下被反复提起的&#x…

作者头像 李华
网站建设 2026/10/17 3:15:43

MySQL表设计实战:数据类型与完整性约束的避坑指南

2. 为什么数据类型选择会决定你的表建得好不好提到MySQL,很多新手第一反应是装库、建表、写SQL,把数据类型当成填空题,给字符串就VARCHAR,给数字就INT,能用就行。但等你真的在线上跑过几年,经历过几次因为类型选错导致的慢查询、磁盘暴涨、数据被截断、锁等待超时,就会明白:数据…

作者头像 李华
网站建设 2026/10/17 3:15:30

金融分布式计算落地指南:从架构选型到故障恢复

半夜两点&#xff0c;运维电话把我从睡梦里拽起来&#xff1a;日终对账任务跑了三个小时不见结果&#xff0c;数据库连接数打满&#xff0c;监控面板一片红色告警。这种场景做金融系统的朋友应该都不陌生——问题通常不在业务代码&#xff0c;而在数据量跨过某个阈值之后&#…

作者头像 李华
网站建设 2026/10/17 3:15:28

Sicar-Training-show-V16实战:KWP2000/CAN诊断流程与避坑指南

简介&#xff1a;这是一套基于Sicar标准程序框架的培训演示程序包&#xff0c;面向工业自动化现场工程师、PLC调试人员及学习PROFINET/PROFIsafe组态的技术人员。包内以Sicar_Training_Demo项目为核心&#xff0c;配合MVK-MPNIO阀岛模块、Siemens G120驱动器及PSSu安全控制器等…

作者头像 李华