ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

文字点选验证码算法全拆解:从目标检测到坐标还原的完整实践

文字点选验证码算法全拆解:从目标检测到坐标还原的完整实践

文字点选验证码这几年几乎是国内所有主流平台的标配,我身边不少做爬虫、自动化测试的朋友一碰到它就头疼。网上搜“某CSDN文字点选算法分析”,出来的文章大多是泛泛而谈,要么贴一段OCR代码就跑,要么只说“用深度学习就能过”,但真正核心的检测、排序、坐标还原怎么做,很少有文章讲透。这篇文章不绕弯子,直接拆解文字点选算法的完整链路,从需求分析、模型选型,到数据生产、训练部署,再到我实测拜访中的常见坑,一次性说清楚。

这类算法的本质,是把“机器能轻易识别的字符”升级为“需要定位、分割、按序点击的语义任务”。相比纯字符识别,它多了两个关键环节:一是要知道文字在图片中的位置,二是要按照提示文字的语义顺序去点击。单独拆开看都不难,但组合在一起,对精度和稳定性的要求就上来了。适合的人群,包括正在做验证码识别学习的技术爱好者、需要维护自动化脚本的工程师,以及想了解前端交互与图像识别如何配合的产品同学。我会尽量用大白话穿插原理,保证新手也能跟上。

1. 文字点选验证码,到底在验证什么

很多教程上来就写代码,忽略了最基础的问题:这个验证码到底想挡住什么?只有想明白产品侧的需求,算法方案才不会跑偏。

1.1 从“人机识别”到“文字点选”的演化逻辑

最早的验证码是纯文本扭曲字符,后来被OCR打得毫无脾气。于是出现了滑块验证码,又被人用轨迹模拟破解。再往后,就到了现在的文字点选:给定一张背景图,上面随机散布着若干中文文字,系统会提示“请依次点击【某】【某】”,用户需要按照文字内容找到对应的字并依次点击。

这个演化的核心逻辑,是把破解成本从“识别”转移到“定位+理解”上。纯文本验证码只需要识别内容,不需要知道文字在哪里;滑块验证码只需要模拟滑动轨迹,不需要理解语义。而文字点选同时要求机器理解“图中有什么字、字在哪个位置、提示文字的顺序是什么”,三个问题串在一起,任何一个环节出错都过不了。

从防御角度看,它还有个巧妙之处:验证码图片的字体、背景、干扰线每次都可能变化,模型很难靠单一特征吃遍所有场景。从破解角度看,这意味着不能只堆一个模型,得有一套完整的工程Pipeline。

1.2 算法拆解的整体视角与核心分工

把文字点选算法拆到最细,大致可以分成四个模块:

  • 目标检测模块:从背景图中找出所有文字的位置,输出每个字的边界框。
  • 文字识别模块:识别每个检测框里的文字内容,输出对应的文本。
  • 语义排序模块:根据提示语中的文字顺序,对检测到的目标进行排序。
  • 坐标映射模块:将模型输出的归一化坐标还原为真实点击坐标,并补偿各类偏移。

这四个模块可以串行执行,也可以部分并联。实际项目中,文字识别和目标检测往往合并在同一个端到端模型里,比如基于YOLO系列改造的多任务模型,既输出框也输出类别。但为了讲解清楚,我先按解耦的方式拆开,后面再讲怎么合并优化。

有个容易被忽略的点:提示文字里的“目标”可能是两个重复的字,比如“请依次点击【小】【小】”。这个“依次”非常关键。用户需要点击两次,模型就不能只输出一个框就完事,还得处理同一个字多次点击的情况。这在语义排序模块里会单独处理,我会在第三章详细展开。

2. 核心技术方案选型:检测、识别与排序

方案选型是最花时间的地方,选错了模型结构,后面几个月都在还债。我根据自己的实际使用经验,把每个模块的最优解和次优解都列出来,方便不同资源条件的团队做取舍。

2.1 目标检测:把图中的文字“框”出来

文字点选的第一步,是从整张背景图中把所有文字候选框找出来。这里没有太多悬念,工业界最常用的就是YOLO系列,特别是YOLOv5和YOLOv8的小体量版本。

为什么不用更轻量的模型?比如SSD或EfficientDet。我踩过的坑是,文字本身是细长型小目标,SSD对小目标的召回率不够,经常漏掉角落里的字。YOLO系列使用多尺度特征融合,对密集小目标的召回明显更好。实测下来,在同样的数据量下,YOLOv5s的mAP比SSD-MobileNet高出一截,推理速度也不会差太多。

具体实现上,需要把检测类别设置为两类或者动态类别。多数场景下,文字点选是任意汉字,不适合用固定类别数,因此检测头只输出“文字”这一类,框住所有可能的字,然后再用识别模块区分具体内容。这个策略的好处是,类别数少,模型更容易收敛,也不会因为汉字种类太多导致样本不均衡。

有一点值得注意:检测框的形状建议设置成窄高型。因为单个汉字的宽高比大致在1比1到1比1.2之间,如果anchor比例设置成自然图片里常见的1比2或2比1,召回率会受影响。我在训练时会把anchor比例从默认值改成0.8、1.0、1.5三档,实测对中文单字更友好。

2.2 文字识别:分类网络好还是OCR好

文字识别是另一个关键模块。这里有两个路线:一是直接把检测框里的图片输入OCR引擎,比如PaddleOCR;二是自己训练一个小型分类网络,识别固定字库。

对于验证码场景,我更推荐第二种,原因有三个。第一,验证码字体往往是艺术字、扭曲字或带干扰线的字,标准OCR模型在这种数据上效果会掉得很厉害。第二,验证码的候选字范围通常很有限,常见的字库可能只有几百个常用字,完全可以做成分类任务。第三,分类网络可以在GPU上大批量推理,性能比跑完整OCR流水线稳定。

分类网络的结构不需要很深,实测用ResNet18或者MobileNetV3,输入尺寸64×64就能达到95%以上的准确率。因为单字识别任务本身特徵颜色、笔画纹理比较明确,不需要ImageNet那样的千类复杂度。

需要补充一个细节:分类网络训练时,需要做充足的字体和数据增强。只拿原始验证码图片训练,遇到新的背景色、新的字体风格会崩。我会对每个样本做随机亮度扰动、轻微透视变换、加高斯噪声和随机遮挡,这样模型学到的才是文字结构本身,而不是背景的统计规律。

2.3 顺序推理:如何让模型学会“按提示点字”

这一步是文字点选和其他验证码最大的区别,也是最容易翻车的地方。

最简单的做法是纯后处理逻辑:先检测出所有文字框,再识别每个框的内容,然后在框列表里搜索提示文字的第一个目标,找到就点击,再搜索第二个目标,找到就再点击。这套逻辑在候选字不重复的时候完全够用。

但考虑一下“请依次点击【小】【小】”这种重复字情况。如果图上只有一个“小”字,你让它点两次,第二次点哪里?这时候需要兜底策略。我常用的方案是:如果同一个目标字只检测到一个位置,第二次点击落在第一次位置附近,加一点随机偏移,保证点击动作发生,至于能不能通过,就看验证码策略的宽容度了。如果同一个目标字检测到多个位置,那就把所有位置都记录下来,按置信度从高到低依次输出。

更进阶的做法是训练一个排序模型,输入检测框的特征序列,输出对应提示词的顺序。这种做法在学术上好看,但在实际项目中性价比不高,原因是验证码的提示词数量有限,用规则处理足以覆盖绝大多数场景。如果你的目标是写一篇高质量技术博客,规则方案更容易讲清楚,也更容易复现。

3. 数据生产与模型训练的关键细节

算法模型能不能落地,七分在数据,三分在模型。这节我专门讲数据从哪来,怎么处理,训练时有哪些别人不会明说的细节。

3.1 训练数据怎么来:合成、爬取与人工标注的取舍

训练数据有三大来源:合成数据、真实采集数据、人工标注数据。我强烈建议以合成数据为主,真实数据为辅。

合成数据的意思,是用代码自动生成验证码样图。随机挑选背景图,随机旋转文字,随机设置字体颜色和位置,加上干扰线,自动标注每个文字的位置和内容。这样做最大的好处是标注零成本,而且可以精确控制训练样本的分布。

我常用的合成流程是:

  • 准备几十张纹理背景图,可以是纯色、噪点、低饱和景物图。
  • 从字库中随机抽取2到6个汉字,字体类型随机选几种,比如宋体、黑体、手写体。
  • 为每个字生成随机位置,避免重叠,并给文字加上小角度旋转。
  • 整图叠加干扰线、噪声点,模拟真实网络传输后的压缩痕迹。
  • 自动输出标注文件,格式为:图片名、类别、框坐标、文字内容。

合成数据也有缺点,就是与真实数据存在分布差异。真实验证码的背景可能是街景图、抽象画,字体可能是平台定制字体,合成数据很难完全模拟。所以还需要真实数据补充。

真实采集的姿势要特别注意合规:只能在本身有公开接口和明确授权的测试环境中获取样本,并只用于正常的技术学习与测试。对于生产环境,我建议与合作方确认数据来源合法可用,不鼓励、也不应该把针对线上业务的样本用于未授权的训练。真实采集样本通常没有标注,可以先用合成数据训练的模型做自动预标注,再由人工校准。这样人工成本能降低一大半。

3.2 坐标回归的损失设计与训练技巧

训练检测模型时,默认的损失函数通常已经够用,但要获得更好的效果,有两个细节值得调整。

第一个是类别不平衡。背景区域远大于文字区域,如果直接使用全部像素计算损失,模型会倾向于把所有区域都预测为背景。解决方案是调整正负样本比例,或者使用Focal Loss。我在YOLO系列中会把类别损失权重稍微调高,让漏检的代价更大。

第二个是边界框损失的改进。标准YOLO使用的是CIoU损失,对多数场景已经没问题。但如果你的验证码文字偏小,ICoU还不够精确,可以考虑把输出特征图的检测头分辨率提升一档,让每个字至少落在16×16以上的特征区域,否则小目标的定位误差会被放大得非常明显。

训练过程还有一些通用技巧。比如,一定使用混合精度训练,节省显存和加速;学习率用余弦退火;早停策略的patience可以设大一点,因为文字点选数据集通常不大,模型很容易在验证集上震荡。我自己训练的模型一般训到150到200轮就比较稳定,再往后基本是过拟合。

4. 推理部署与坐标还原全流程实操

训练好模型只是第一步,真正决定成功率的是推理链路的设计。我在这个环节踩过的坑最多,也最有得聊。

4.1 从模型输出到点击坐标的完整链路

模型输出的是归一化坐标,而验证码页面需要的是像素坐标,中间还需要处理页面缩放、设备像素比、图片内边距等问题。完整的链路如下:

  1. 前端把验证码图片传入后端或本地推理服务。
  2. 图片预处理:缩放至模型输入尺寸,保持宽高比,多余部分用灰色填充。
  3. 模型推理,输出每个检测框的类别、置信度和归一化坐标。
  4. 将归一化坐标映射回原始图片的像素坐标。
  5. 再根据验证码图片在页面中的实际渲染大小,将像素坐标映射为页面坐标。
  6. 如果平台对点击坐标有随机性检验,还要在点击位置周围做少量随机扰动。

这里最常见的问题出在第四步和第五步的坐标系混乱。最稳妥的做法是,全程只用两类坐标系:一是原始图片坐标系,二是页面渲染坐标系。中间不要混用。每次缩放操作都要记录缩放比例,最后统一乘回去。

我踩到过一次特别典型的坑:模型训练时把图片统一缩放到640×640,但保留了原图的宽高比。推理时忘记做同样的长边缩放,而是直接拉伸到640×640,结果所有坐标都偏移了。这类问题不影响检测框的相对位置,但绝对坐标会整体偏移,导致点击位置歪得离谱。

4.2 置信度过滤与时空抖动处理

模型输出的检测框会包含大量低置信度的误检结果,需要设置合理的置信度阈值。这个阈值不能拍脑袋定,我的做法是统计验证集上的F1曲线,找到precision和recall平衡点。

根据我的经验,阈值设置在0.35到0.5之间比较合理。阈值太低,背景块被识别成文字,点击会命中外地;阈值太高,容易漏掉变形严重的文字。如果你用了多尺度测试增强,阈值可以适当提高,因为多尺度融合已经提高了置信度的一致性。

时空抖动处理,指的是对多次点击之间的时间间隔和坐标偏移做人性化模拟。机器直接输出的坐标太精确,连续点击间隔又均匀,很容易被行为检测识别。我通常会在每次点击前加一个200到500毫秒的随机延迟,坐标在原始点上再随机偏移1到3个像素,模拟真实鼠标的微小抖动。

另外,如果模型对同一目标输出了多个重叠框,需要通过NMS把重合度过高的框合并。NMS的IoU阈值我习惯设为0.4,低于默认的0.45。因为文字区域小,框之间挨得近,阈值高会导致两个相邻文字被合并成一个框,输出顺序直接错乱。

5. 实战中的常见问题与排错记录

这一节的每一个问题,都是我实际运行中遇到并解决过的,不是网上复制来的。按照问题出现频率从高到低排列。

5.1 坐标偏移一整块:问题多半在缩放逻辑

现象:检测全对,但点击落点全部偏到文字的左下角,偏移量固定。

排查过程:先检查原始图片坐标是否正常,再检查页面渲染缩放比例。起初我以为是设备像素比的问题,折腾了半天的dpr计算,后来发现是图片在预处理阶段被居中填充,填充的边缘没有在坐标映射时扣除。

这类偏移的特点是误差值固定,不会随检测目标不同而变化。解决办法很简单:把填充的偏移量记录下来,在最后映射像素坐标时减掉。我在代码里专门加了一个debug模式,直接把检测框画在原始图上,和前端渲染结果叠加对比,一眼就能看出是哪一层坐标系出了问题。

排查建议:先做可视化,再调参数。不要在没看清检测框位置的时候就去改NMS阈值和置信度,大概率越改越糟。

5.2 重叠文字与连笔字:检测框打架怎么处理

现象:提示文字是“打”,但图上的“打”和一个“折”字部分重叠,两个检测框交叠超过70%,NMS把其中一个框合并掉了,导致漏检。

解决思路:这类情况没法靠单一模型完全避免,需要结合识别模块的反馈做二次修正。我的做法是,对每个低置信度或重叠高的区域,同时截取原图和放大图,分别过一遍识别模型,如果识别结果中包含提示目标,就保留该框并重新计算精确位置。

另外,合成数据阶段要有意识地加入重叠样本。随机让两个文字的边界框重叠20%到40%,模拟手写连带的情况。模型见过的重叠模式越多,实际翻车率越低。

5.3 模型过拟合与泛化不足:换背景就废了怎么办

现象:在训练集和验证集上准确率都是99%,但一换到线上真实图片,成功率直接降到70%。

原因分析:合成数据和真实数据分布差异太大,模型学到了合成背景的纹理特征,而不是文字本身的笔画结构。

解决办法有三个层次:

  • 第一个层次:扩大合成数据中背景图的多样性,加入真实风格的街景、室内、抽象图,甚至可以随机叠加剪贴画。
  • 第二个层次:真实数据比例提升到总量的30%以上,用半自动标注降低人工成本。
  • 第三个层次:对图片做更强的颜色扰动,把模型的注意力从颜色分布逼向文字形状。

需要注意的是,不要把训练数据中的某些无关特征学进去。比如合成数据里文字都在图片正中心附近,模型就会把位置当作有效特征,线上图片文字分布在四角就直接识别不到。合成时位置要随机分布,让模型学会只关注文字本体。

5.4 推理性能瓶颈:批量请求时的延迟优化

如果你的自动化脚本需要同时处理很多验证码,推理延迟就变得非常关键。目标检测模型直接跑CPU会很慢,YOLOv5s单张640×640的推理时间在CPU上可能要200到500毫秒。

优化手段包括:

  • 用TensorRT或ONNX Runtime的GPU模式做加速。
  • 输入尺寸从640降到480,只要检测框能稳定框住文字,性能提升非常明显。
  • 识别模型和检测模型可以并行:先出检测框,截取区域后,同一批检测框一次性批量进入识别模型,而不是逐个识别。

我在一个项目里把原本700毫秒的单张推理时间压到了150毫秒以内,主要手段就是缩小输入尺寸加GPU推理。文字点选对检测框的精度容错相对高,不需要像目标检测竞赛那样追求像素级精确。

6. 合规底线与长期维护的一点感悟

说点题外话,也是我特别想强调的。文字点选算法的学习和研究,一定要控制在合法合规的范围内。把它用在自己拥有的测试系统、授权的研究项目里,是技术探索;未经授权去攻击线上业务,则是另外一回事了。技术圈里不缺聪明人,但真正缺的是明白自己边界的人。我写这篇文章,目的也是为了让对图像识别感兴趣的朋友理解这套算法的内部机制,而不是提供一门“面向违规”的技术。

从长期维护的角度看,这类算法没有一劳永逸的方案。验证码的形态会持续变化,模型需要定期用新样本迭代。我的习惯是每两周收集一次新场景样本,增量训练一轮,保证模型不会因为线上字体或背景变化而慢慢失效。这个维护成本说高不高,说低也不低,但如果你把算法当成一个持续演进的系统来运营,心态会踏实很多。

最后分享一个小技巧。每次改完模型或推理链路,别只盯着成功率看。把失败的样本全部保存下来,定期翻一遍。你会发现很多问题是系统性的,比如某一种背景色特别容易误检,某一种字体笔画特别容易漏检。带着这些观察去调数据比例,比盲目堆积模型层的技巧有效得多。这也是我个人的核心工作习惯,希望对你也有帮助。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进