← 返回文章列表

从图像到点击坐标:YOLOv8 与 CRNN 构建中文点选验证码识别链路

中文点选验证码需要同时解决文字定位、字符识别和点击顺序还原。文章以授权测试和安全评估为边界,梳理数据标注、合成增强、YOLOv8 检测、CRNN 识别及部署验证的完整思路。

从图像到点击坐标:YOLOv8 与 CRNN 构建中文点选验证码识别链路

说明:验证码承担着区分真实用户与自动化程序的安全职责。下面的技术内容适用于自有系统、测试环境和明确授权的安全评估,不应被用于绕过第三方平台的访问控制。实际项目中,除了研究识别模型,也应优先考虑验证码设计、风控策略和异常流量拦截。

一、点选验证码本质上是两个问题

文字点选验证码通常由一张主图和一段提示组成。主图中散落着若干汉字,提示可能要求依次点击“下、心、云”。人看到提示后,可以迅速找到对应文字并记住点击顺序;程序则需要完成一条更长的处理链:读取图片、定位每个文字区域、识别区域中的汉字、按照提示排序,最后生成坐标结果。

因此,工程上通常把任务拆成文字检测和文字识别两个阶段。检测模型只关心“字在哪里”,输出矩形框或中心点;识别模型只关心“框里是什么字”,输出字符类别或字符串。两个模型各自训练、各自评估,出现问题时也更容易定位。比如框的位置偏了,优先检查检测数据和输入分辨率;框的位置准确但汉字经常混淆,则要回到识别数据、字体覆盖率和图像预处理。

对于单字分散、目标数量不多的验证码,YOLOv8n 这类轻量检测器通常已经够用。识别部分可以使用 CRNN,将卷积网络提取的特征送入双向循环网络,再通过 CTC 解码得到字符序列。若每个检测框只包含一个字,识别任务可以进一步简化为分类;若图片中存在相邻文字、粘连或多个字符,则保留 CRNN 的序列建模能力更稳妥。

二、数据标注决定模型上限

每个样本至少需要记录两类信息:文字框坐标和框内汉字。检测训练只使用坐标,识别训练则需要图像裁剪与字符标签。建议在第一次标注时同时保存这两部分内容,因为后续可以直接根据统一坐标生成识别数据,避免重新打开原图逐字框选。

多数场景下,水平矩形框足以覆盖轻微旋转的汉字。框不宜贴得过紧,四周保留少量背景有利于模型学习真实边缘,但外扩太多会把干扰线和邻近文字一并裁进去。只有在文字旋转明显、水平框包含大量无效背景时,才需要考虑旋转框。旋转框能够描述角度,却会增加标注、训练和后处理成本,第一版系统不建议一开始就追求复杂标注。

如果使用 LabelImg 导出的 VOC 文件,需要将左上角、右下角坐标转换成 YOLO 所需的中心点、宽高,并按图像尺寸归一化。点选验证码的检测类别通常只有一个,例如 text,类别编号固定为 0。转换之后不要只看脚本是否运行成功,还应随机抽取图片,把生成的框重新绘制出来检查。坐标顺序、图像缩放和宽高读取错误,往往会让训练在错误标签上持续很久。

import xml.etree.ElementTree as ET

def voc_to_yolo(xml_file, width, height):
    root = ET.parse(xml_file).getroot()
    result = []
    for obj in root.findall('object'):
        box = obj.find('bndbox')
        x1 = float(box.findtext('xmin'))
        y1 = float(box.findtext('ymin'))
        x2 = float(box.findtext('xmax'))
        y2 = float(box.findtext('ymax'))
        cx, cy = (x1 + x2) / 2 / width, (y1 + y2) / 2 / height
        bw, bh = (x2 - x1) / width, (y2 - y1) / height
        result.append(f'0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}')
    return result

真实样本不足时,可以用字体渲染补充训练集。合成数据应覆盖不同字体、字号、旋转角度、颜色、背景纹理和干扰线密度。不要只生成干净白底图片,否则模型容易记住背景规律,一旦换成真实验证码就会明显掉点。黑体、宋体、楷体和带有轻微手写特征的字体都值得加入,形近字也应提高出现比例,例如“未”和“末”、“日”和“曰”、“土”和“士”。

三、YOLOv8 负责稳定找到文字

YOLOv8n 的优势在于模型体积小、推理速度快、部署资料多,而且对离散的小目标检测足够灵活。DBNet 等文本检测模型更擅长连续文本行或复杂场景文字分割,而点选图片中的文字通常彼此分散,直接采用目标检测框更容易得到可用中心点。

单字在原图中可能只有三十多个像素高,输入尺寸对召回率影响很大。640 通常可以作为快速验证配置,若小字漏检较多,可尝试 768 或 832。分辨率提高会带来显存占用和延迟增加,因此应结合实际设备测量,而不是盲目追求更大的输入。训练集和推理阶段的缩放方式也必须保持一致,尤其要确认是否使用了等比例缩放和边缘填充。

数据增强要贴近真实分布。Mosaic 可以增加目标组合的多样性,但过强的拼接会破坏验证码原本的布局;Copy-Paste 可以补足稀有字符,却要防止复制后的文字相互重叠。旋转增强建议从小角度开始,逐渐扩展到真实图片能够出现的范围。检测评价不能只看 mAP,还要关注漏检率、重复框数量和中心点误差,因为最终使用的是点击位置,而不是单纯的分类分数。

后处理阶段通常要做置信度过滤、非极大值抑制和坐标还原。若模型输入经过 padding,预测框必须先去掉填充偏移,再映射回原图。排序也不能直接按检测框的返回顺序,而应依据提示文字和识别结果建立对应关系。两个候选框距离过近时,可以保留较高分框,同时把异常样本记录下来,供后续补标和分析。

四、CRNN 识别汉字,CTC 处理变长输出

识别模型的输入是检测框裁剪出的局部图像。常见做法是统一高度、按比例缩放宽度,再进行适度补边。强行拉伸成固定正方形会改变汉字结构,尤其容易影响横竖比例相近的形近字。裁剪时还要注意边缘文字,如果框过紧导致笔画被截断,识别模型再大也很难恢复。

CRNN 通常由 CNN、BiLSTM 和 CTC 三部分组成。CNN 提取笔画与局部纹理,BiLSTM 聚合水平方向的上下文,CTC 则允许输入特征长度和标签长度不同,不必为每个字符手工对齐。单字任务可以把识别头改成分类层,训练更简单;多字任务则保留 CTC,并准备包含空白符的字符表。

中文字符集不能只按常见汉字随意截取。字符表应来自业务样本和合成数据的并集,训练、验证、推理必须使用同一份映射文件。解码时需要去除 CTC blank,并合并连续重复字符。字符表版本变化后,旧模型和新字典不能混用,否则会出现模型置信度正常但结果全部错位的情况。

def ctc_decode(logits, blank_id=0):
    ids = logits.argmax(dim=-1).tolist()
    output = []
    previous = None
    for idx in ids:
        if idx != blank_id and idx != previous:
            output.append(idx)
        previous = idx
    return output

# 真实项目中还需通过字符表将编号映射为汉字

增强策略应围绕真实干扰设计,包括轻微模糊、亮度变化、压缩噪声、旋转、透视变换和局部遮挡。增强不是越重越好,过度模糊会让模型学到与真实图片无关的特征。评估时应单独统计常见字、形近字、低频字和不同字体下的准确率,这比只看一个总准确率更能说明问题。

五、从识别结果到可验证的工程链路

完整推理流程可以分成几个明确步骤:读取原图并记录尺寸;检测所有文字框;把框坐标映射回原图;按框裁剪并送入识别模型;将识别结果与提示词匹配;最后按照提示顺序整理候选坐标。在授权测试环境里,建议把每一步的中间结果保存下来,包括检测可视化图、裁剪小图、识别置信度和匹配日志。这样遇到错误时,能够判断问题来自检测、识别、排序还是坐标换算。

匹配时不能只依赖最高置信度。可以综合字符相似度、检测框面积、识别置信度和候选框之间的距离,建立简单的评分函数。提示词中若有重复字符,还必须允许一个字符对应多个实例,并使用集合或二分图匹配避免同一框被重复使用。对无法确定的结果,应返回低置信度状态,而不是强行输出一个看似完整的答案。

部署阶段可将 PyTorch 模型导出为 ONNX,再根据 CPU、GPU 或边缘设备选择对应运行时。导出后要逐层或逐批对比 PyTorch 与 ONNX 的输出,重点检查图像通道顺序、归一化范围、动态尺寸和 NMS 实现差异。量化能够降低延迟和内存占用,但中文小字识别对精度较敏感,必须用独立验证集确认量化前后的字符准确率。

安全评估不应只关注模型是否“能识别”,还要观察系统是否能识别异常行为。验证码服务可以结合请求频率、设备指纹、会话状态、鼠标轨迹、失败次数和接口调用模式进行风险判断,并通过动态调整题型、干扰方式和有效期降低自动化攻击收益。对于自有业务,优先采用分层风控和无障碍替代方案,避免把安全压力全部转移给验证码模型。

当项目进入正式环境,训练数据、字符表、模型文件和预处理配置应统一版本管理,任何一项变化都要重新跑回归测试。记录漏检样本、形近字错误和极端旋转样本,形成持续迭代的数据闭环,模型才不会停留在一次性演示阶段。