1. 从“点选”到“识别”:一个看似简单却暗藏玄机的任务

“中文点选识别”,这六个字拆开来看,每个词都平平无奇,但组合在一起,就构成了一个在互联网安全、人机交互和自动化测试领域里既常见又充满挑战的技术课题。简单来说,它的核心任务就是让机器能够“看懂”一张图片或一个界面上的中文文字,并准确地“点击”指定的文字或词语。听起来是不是有点像我们每天在手机上进行的验证码操作?没错,这正是它的一个典型应用场景——验证码识别与对抗。但它的应用远不止于此,从自动化测试脚本需要点击某个特定按钮,到数据采集工具需要模拟用户点击特定文本链接,再到辅助工具帮助用户快速定位界面元素,背后都可能需要这项技术的支持。

我之所以对这个话题有深入的体会,是因为在实际的爬虫开发和自动化项目中,我无数次与各种形态的“点选验证码”狭路相逢。早期的验证码只是扭曲的数字字母,后来进化到了需要点击图中特定文字,比如“请点击下图中所有的‘公交车’”。这种从“识别”到“执行”的跨越,对机器而言难度陡增。它不再是简单的OCR(光学字符识别)问题,而是一个融合了 目标检测、文本识别和坐标映射 的复合型任务。机器需要先找到图中所有的文字区域(目标检测),然后识别出每个区域里是什么字(文本识别),最后判断哪些字是目标词,并计算出它们的精确屏幕坐标以供模拟点击。每一个环节都可能成为“翻车”现场。

2. 技术栈全景:不止于OCR的复合型解决方案

要实现一个健壮的中文点选识别系统,我们不能指望一个“银弹”算法,而需要搭建一个技术栈。这个栈通常分为几个清晰的层次,每一层都有其特定的技术选型和考量。

2.1 图像预处理:为识别扫清障碍

原始图像往往并不“干净”。可能存在噪声、模糊、低对比度、复杂背景干扰等问题。预处理的目标就是提升图像质量,让后续的识别步骤更容易。这里有几个关键操作:

灰度化与二值化 :这是最基础的步骤。将彩色图像转换为灰度图,减少计算量。然后通过设定一个阈值,将灰度图转为黑白二值图像,让文字与背景彻底分离。这里的关键在于阈值的选择,全局阈值(如OTSU算法)适用于背景均匀的图片,但对于光照不均或背景复杂的验证码,可能需要采用自适应阈值算法。

去噪与滤波 :图像中可能存在椒盐噪声或高斯噪声。中值滤波器对去除椒盐噪声特别有效,它能很好地保护边缘信息,而高斯滤波器则常用于平滑图像、抑制高斯噪声。在实际处理验证码时,我常常会先尝试中值滤波,因为验证码的干扰线、点常常表现为椒盐噪声。

形态学操作 :这是处理文字笔画断裂或粘连的利器。膨胀操作可以加粗笔画,连接断裂的部分;腐蚀操作可以细化笔画,分离粘连的部分。通过开运算(先腐蚀后膨胀)可以去除小的白点(噪声),闭运算(先膨胀后腐蚀)可以填充小黑洞(断裂)。例如,一个笔画纤细且带有断裂的“中”字,经过适当的闭运算后,可能会变成一个更完整的连通区域。

注意:预处理是一把双刃剑。过度处理(如过强的滤波或形态学操作)可能会扭曲文字形状,反而降低识别率。通常需要根据具体的图片样本进行参数调优,没有一套放之四海而皆准的参数。

2.2 文本检测:找到文字在哪里

预处理后的图像,我们需要定位所有文字出现的位置。这就是文本检测(Text Detection)模块的任务。近年来,基于深度学习的检测模型已经全面超越了传统方法。

传统方法:连通域分析 :对于经过良好二值化的简单图片,连通域分析(Connected Component Analysis)是一个轻量且有效的选择。它通过扫描图像,将相邻的、具有相同像素值的区域标记为同一个连通域。每个连通域的外接矩形就可以视为一个候选的文字区域。这种方法速度快,但对图像质量要求高,如果文字与背景分离不彻底,或者文字之间有粘连,效果就会大打折扣。

深度学习方法:CTPN、EAST、DBNet :对于背景复杂、文字排列不规则(如弯曲、倾斜)的图片,深度学习模型是更好的选择。

  • CTPN(Connectionist Text Proposal Network) :它擅长检测水平文本行,将文本行视为一系列宽度固定的细长文本提议框(text proposal),然后通过循环神经网络(RNN)将这些提议框连接成完整的文本行。在早期的验证码识别中应用较多。
  • EAST(Efficient and Accurate Scene Text Detector) :这是一个非常高效的模型,它可以直接预测每个像素点到文本区域边界的距离(几何图),从而生成任意方向的旋转矩形框或四边形框。它的速度很快,适合需要实时处理的场景。
  • DBNet(Differentiable Binarization Network) :这是当前在自然场景文本检测中的SOTA(State-of-the-Art)方法之一。它的核心创新是提出了一个可微分二值化(DB)模块,将二值化这一关键但不可微的步骤融入到训练过程中,使得模型可以端到端地学习如何生成更精确的文本区域二值图,检测精度非常高。

在中文点选场景中,文字通常是离散的词语或单字,且可能散落在图片各处。因此,一个能够输出独立、精确文本框的检测器(如DBNet或改进版的EAST)比一个输出文本行的检测器(如CTPN)更为合适。我的经验是,对于互联网上常见的点选验证码,使用在ICDAR等中文场景数据集上微调过的DBNet模型,通常能取得非常好的检测效果。

2.3 文本识别:读懂文字是什么

检测出文本框后,我们需要裁剪出这些区域,送入文本识别(Text Recognition)模型,识别出具体的字符内容。

CRNN + CTC 经典组合 :在过去很长一段时间里,CRNN(Convolutional Recurrent Neural Network)结合CTC(Connectionist Temporal Classification)损失函数是文本识别的标配。CNN部分(如VGG、ResNet)负责提取图像特征,RNN部分(通常是LSTM或BiLSTM)负责对特征序列进行建模,捕捉上下文信息,最后CTC负责对齐不定长的特征序列和标签序列。这个组合对规整的印刷体文字识别效果很好。

基于注意力机制的序列识别模型 :这类模型(如Attn)使用编码器-解码器(Encoder-Decoder)结构,解码时通过注意力机制动态地关注编码特征的不同部分,更适合处理长文本或带有复杂语言模型的场景。但对于点选验证码中的短词、单字,其优势并不明显,且训练更复杂。

Vision Transformer (ViT) 系列 :近年来,Transformer架构在视觉任务上大放异彩。对于文本识别,有研究者将图像切块后送入Transformer进行编码,然后接一个简单的解码头进行识别。这类模型在大规模数据上表现出强大的特征提取能力,但模型参数量大,推理速度相对较慢。

实践选型建议 :对于中文点选识别,我们面对的字库通常是有限的(几百到几千个常用汉字),且文字相对规整。一个在大型中文数据集(如Chinese Text Recognition Benchmark)上预训练,并在自己业务相关的验证码样本上微调过的CRNN模型,往往是性价比最高的选择。它平衡了精度、速度和部署复杂度。如果对精度有极致要求,且计算资源充足,可以考虑基于Transformer的模型。

2.4 坐标映射与点击执行

识别出目标文字后,最后一步是将图像坐标转换为屏幕坐标并执行点击。这里有一个关键细节:检测模型给出的坐标通常是相对于 原始输入图片 的。而我们的程序可能是通过浏览器驱动(如Selenium)或桌面自动化工具(如PyAutoGUI)来操作屏幕。

坐标转换流程

  1. 获取元素与截图 :首先,需要定位到包含验证码图片的网页元素或屏幕区域。
  2. 计算缩放比例 :验证码图片在显示时可能被CSS缩放,或者我们截图时进行了缩放。需要计算出图片原始分辨率与当前显示尺寸之间的比例因子(scale_factor)。
  3. 坐标映射 :将检测模型输出的文本框中心点坐标 (x_img, y_img) ,乘以缩放比例,得到相对于 验证码容器元素左上角 的坐标 (x_local, y_local)
  4. 屏幕绝对坐标 :再获取验证码容器元素在屏幕上的绝对位置 (elem_x, elem_y) ,最终的目标点击屏幕坐标为 (elem_x + x_local, elem_y + y_local)

执行点击 :使用自动化工具(如Selenium的 ActionChains , PyAutoGUI的 click 函数)移动到该坐标并执行点击操作。这里需要模拟人类点击的轻微随机延迟和微小位置偏移,以避免被反爬机制判定为机器行为。

3. 实战构建:一个基于深度学习的点选验证码破解示例

下面,我将以一个模拟的“点击图中所有‘苹果’”验证码为例,勾勒一个完整的实战流程。请注意,此示例仅用于技术交流与学习,请严格遵守相关网站的服务条款,不得用于恶意攻击或侵犯他人权益。

3.1 环境准备与数据采集

首先,我们需要一个Python环境,并安装必要的库。

# 深度学习框架
pip install torch torchvision
# 图像处理
pip install opencv-python pillow
# 文本检测识别(以PaddleOCR为例,它集成了优秀的检测识别模型且易于使用)
pip install paddlepaddle paddleocr
# 自动化与网络请求
pip install selenium requests

数据是模型的燃料。我们需要收集目标验证码的图片样本。可以通过编写脚本,自动访问目标页面并保存验证码图片。同时,需要人工或借助一些半自动工具为这些图片标注两部分信息:

  1. 检测标注 :每个文字区域的边界框坐标(通常为四边形四点坐标或矩形框的左上右下坐标)。
  2. 识别标注 :每个边界框内的文本内容。

标注格式可以参考COCO或VOC数据集格式。对于这个项目,一个简单的自定义JSON格式可能更灵活:

{
  "image_name": "captcha_001.jpg",
  "annotations": [
    {"bbox": [x1, y1, x2, y2], "text": "苹"},
    {"bbox": [x3, y3, x4, y4], "text": "果"},
    {"bbox": [x5, y5, x6, y6], "text": "香"},
    ...
  ]
}

3.2 模型训练与微调

我们选择PaddleOCR作为基础工具链,因为它提供了开箱即用的高质量中文检测(DB)和识别(CRNN)模型,并且支持自定义数据训练。

步骤一:检测模型微调

  1. 将收集到的图片和对应的标注文件,按照PaddleOCR要求的格式组织(通常是一个图片文件夹和一个标注文本文件,每行记录图片路径和所有框的坐标、文字)。
  2. 使用PaddleOCR提供的配置文件(如 det_ch_db.yml ),修改其中的数据集路径、类别数(文本检测通常为1类)、训练轮次等参数。
  3. 启动训练。由于我们有预训练模型,微调(fine-tuning)通常只需要少量数据(几百张)和较少轮次(几十epoch)就能达到很好的效果。
python tools/train.py -c configs/det/det_ch_db.yml -o Global.pretrained_model=./pretrain_models/ch_det_db_resnet50_vd

步骤二:识别模型微调

  1. 同样,准备识别数据集。这里需要的是裁剪好的单字或词语图片,以及对应的文本标签。
  2. 使用PaddleOCR的识别配置(如 rec_chinese_common_train.yml ),修改字典文件( ppocr/utils/ppocr_keys_v1.txt ),确保包含你验证码中出现的所有字符。
  3. 启动识别模型训练或微调。
python tools/train.py -c configs/rec/rec_chinese_common_train.yml -o Global.pretrained_model=./pretrain_models/ch_rec_mv3_crnn

3.3 集成推理与自动化脚本

模型准备好后,编写一个完整的推理脚本。以下是一个高度简化的核心逻辑框架:

import cv2
from paddleocr import PaddleOCR
import numpy as np
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time

class ClickCaptchaSolver:
    def __init__(self, det_model_dir, rec_model_dir):
        # 初始化PaddleOCR,指定自定义模型路径
        self.ocr = PaddleOCR(det_model_dir=det_model_dir,
                              rec_model_dir=rec_model_dir,
                              use_angle_cls=False,  # 如果不需文字方向分类,可关闭
                              lang='ch')

    def solve_from_image(self, image_path, target_words):
        """
        核心解决函数:给定图片路径和目标词列表,返回需要点击的坐标列表
        """
        img = cv2.imread(image_path)
        result = self.ocr.ocr(img, cls=False)

        click_positions = []
        for line in result:
            for word_info in line:
                box = word_info[0]  # 文本框四个点坐标
                text = word_info[1][0]  # 识别出的文本
                confidence = word_info[1][1]  # 置信度

                # 过滤低置信度结果
                if confidence < 0.7:
                    continue

                # 判断是否为需要点击的目标词
                if text in target_words:
                    # 计算文本框的中心点坐标(相对于图片)
                    pts = np.array(box, dtype=np.int32)
                    center_x = int(pts[:, 0].mean())
                    center_y = int(pts[:, 1].mean())
                    click_positions.append((center_x, center_y, text))
                    print(f"找到目标词 '{text}', 坐标 ({center_x}, {center_y}), 置信度 {confidence:.2f}")

        return click_positions

    def automate_click(self, url, captcha_element_selector, target_words):
        """
        自动化流程:打开网页,截图,识别,点击
        """
        driver = webdriver.Chrome()
        driver.get(url)

        # 1. 定位验证码元素并截图
        captcha_element = driver.find_element_by_css_selector(captcha_element_selector)
        # 这里需要获取元素位置和尺寸,并截图。简化起见,假设已保存为captcha.png
        captcha_element.screenshot('captcha.png')

        # 2. 识别目标坐标
        positions = self.solve_from_image('captcha.png', target_words)

        # 3. 坐标转换与点击
        # 获取验证码元素在页面中的位置
        element_location = captcha_element.location
        element_size = captcha_element.size

        for (img_x, img_y, _) in positions:
            # 计算相对于元素左上角的坐标(假设截图是原尺寸)
            # 注意:如果截图有缩放,这里需要乘以缩放因子
            relative_x = img_x
            relative_y = img_y

            # 计算绝对屏幕坐标(这里简化了,实际需考虑滚动条等)
            absolute_x = element_location['x'] + relative_x
            absolute_y = element_location['y'] + relative_y

            # 使用ActionChains执行点击,并加入人类化随机延迟和微小偏移
            action = ActionChains(driver)
            action.move_by_offset(absolute_x, absolute_y).pause(np.random.uniform(0.1, 0.3)).click().pause(np.random.uniform(0.2, 0.5)).perform()
            # 每次点击后需要将鼠标移回原点,否则偏移会累积
            action.reset_actions()

        driver.quit()

if __name__ == '__main__':
    solver = ClickCaptchaSolver('./custom_det_model/', './custom_rec_model/')
    # 模拟解决一个验证码
    target_words = ['苹果', '香蕉']  # 假设需要点击这两个词
    positions = solver.solve_from_image('test_captcha.jpg', target_words)
    print(f"需要点击的坐标:{positions}")

4. 核心挑战与应对策略:为什么你的模型总“翻车”

在实际应用中,直接套用上述流程很可能会遇到各种问题。下面是我在多个项目中总结出的核心挑战和应对策略。

4.1 对抗性干扰:验证码的“进化”

验证码设计者会不断引入干扰,增加机器识别的难度。常见的对抗手段包括:

1. 复杂背景与文字融合 :文字颜色与背景色相近,或者背景有密集的干扰图案、线条穿过文字。

  • 应对策略 :强化预处理。尝试使用 颜色分离 技术,如果验证码文字是固定颜色(如红色),可以提取红色通道或使用HSV色彩空间进行阈值分割。对于干扰线,可以尝试使用 形态学开运算 或特定的 图像修复算法 (如基于邻域的方法)来尝试去除细线,同时保留较粗的文字笔画。

2. 字体扭曲与变形 :使用非常规字体,或对文字进行随机旋转、缩放、波浪形扭曲、透视变换。

  • 应对策略 :数据增强是关键。在训练检测和识别模型时,必须对训练样本施加同样甚至更强烈的 空间变换增强 ,如随机旋转(-15°到15°)、随机缩放(0.8到1.2倍)、弹性形变、透视变换等。这能极大地提升模型对形变的鲁棒性。识别模型方面,CRNN+CTC对形变有一定容忍度,但更复杂的模型(如结合空间变换网络STN)可能效果更好。

3. 重叠、粘连与断裂 :文字之间部分重叠,或者单个文字的笔画出现粘连或断裂。

  • 应对策略 :这对检测器是巨大考验。DBNet这类基于分割的检测器,通过预测文本区域和阈值图,对粘连文本的处理能力优于基于回归的检测器。在识别阶段,笔画断裂可能被误认为是两个字符。这时需要在识别后处理中,结合语言模型(即使是简单的字频统计)进行纠错,或者训练模型时加入大量笔画断裂的增强样本。

4. 动态验证码 :文字位置随机出现,或者每次刷新部分文字变化。

  • 应对策略 :这要求我们的系统必须是“零样本”或“少样本”的,即模型要足够通用。除了使用大规模预训练模型外,几乎没有捷径。对于位置随机,我们的检测器本身就应该能处理任意位置。对于文字内容变化,则要求识别模型的字符集覆盖足够广。

4.2 坐标精度与点击反馈

即使识别对了,点不准或者点了没反应也是常事。

坐标映射误差 :这是最常见的坑。浏览器缩放、CSS变换、iframe嵌套、页面滚动都会导致坐标计算错误。

  • 应对策略 :必须进行 端到端的坐标验证 。可以写一个调试脚本,让程序在计算出坐标后,先在屏幕上画一个红圈(通过JavaScript在页面上叠加一个div,或者用自动化工具截图后画圈再显示),人工确认红圈是否精准套在目标文字上。确保映射逻辑100%正确。对于Selenium,使用 execute_script 执行JavaScript来获取元素精确的视口位置和变换矩阵是更可靠的方法。

点击行为模拟 :简单的 element.click() pyautogui.click(x, y) 太像机器了。

  • 应对策略 人类行为模拟 。包括:1) 移动轨迹:使用贝塞尔曲线模拟人手鼠标移动,而非直线移动。2) 随机延迟:在移动和点击前后加入随机时间间隔。3) 点击偏差:点击坐标不在正中心,而是在目标区域内随机偏移几个像素。4) 点击力度:有些前端会监听 mousedown mouseup 的时间差来模拟“按压”感。可以使用 ActionChains click_and_hold release 方法,并间隔一个随机时间。

前端反爬监听 :网站可能监听 WebDriver 属性、异常快的点击速度、过于规律的点击模式。

  • 应对策略 :1) 使用 stealth.min.js 等脚本隐藏Selenium的自动化特征。2) 随机化操作流程,比如偶尔先悬停再点击,或者在点击目标前先随机移动鼠标到无关区域。3) 最重要的,分析前端验证逻辑。有时点击后,前端会生成一个token,这个token是基于点击坐标的某种加密值提交给服务器的。这时就需要逆向JavaScript,理解其加密算法,直接生成token提交,绕过点击步骤。这已进入更复杂的逆向工程领域。

4.3 模型泛化与持续维护

一个今天能用的模型,明天可能就失效了,因为验证码更新了。

模型泛化能力不足 :在A网站训练的模型,在B网站上效果很差。

  • 应对策略 域适应(Domain Adaptation)与增量学习 。收集新网站(目标域)的少量标注数据,在原有模型(源域训练)的基础上进行微调。如果数据持续变化,可以考虑建立 持续学习(Continual Learning) 的管道,定期用新数据更新模型,同时防止对旧数据的遗忘。

建立数据闭环 :手动标注数据成本高昂。

  • 应对策略 :尝试 半自动或自监督数据标注 。例如,用现有模型对未标注图片进行预测,将高置信度的预测结果作为伪标签,加入训练集。或者,利用验证码本身的特点:如果“点击提交”后的反馈(成功/失败)可以获取,我们可以用强化学习的思想,将成功通过的点击序列作为正样本,反向优化模型。但这通常需要复杂的系统设计。

成本与效率权衡 :高精度模型往往计算量大,速度慢。

  • 应对策略 模型轻量化与工程优化 。将训练好的模型转换为更高效的推理格式,如ONNX、TensorRT或使用Paddle Lite、MNN等移动端/边缘端推理引擎。对于检测模型,可以尝试更轻量化的backbone(如MobileNetV3替代ResNet)。在架构上,可以探索“检测+识别”的一阶段端到端模型,虽然设计复杂,但可能减少中间过程开销。

5. 超越验证码:中文点选识别的更广阔天地

当我们把目光从“对抗”验证码移开,会发现中文点选识别技术在许多正向场景下大有可为。

UI自动化测试 :在移动App或Web应用的自动化测试中,经常需要根据屏幕上的文字内容来定位元素并操作。传统的通过ID、XPath定位的方式在动态内容或跨平台场景下非常脆弱。基于视觉的文字点选技术可以构建更稳定、更贴近真实用户操作的自动化脚本。例如,测试脚本可以寻找“登录”按钮并点击,无论它的位置或样式如何变化。

图形化界面(GUI)自动化与辅助工具 :对于某些没有提供API或难以用传统方式访问的桌面软件,可以通过OCR识别界面上的文字,然后模拟点击进行操作,实现自动化数据录入或报表生成。对于视障人士的辅助工具,识别屏幕文字并转换为语音是核心功能,而“点选”则是交互的延伸——用户听到“保存按钮”后,可以通过语音或手势触发“点击”操作。

文档智能处理与信息提取 :在扫描的PDF或图片格式的文档中,如果需要提取特定栏目下的信息(如合同中的“甲方”、“乙方”后面的名称),可以先通过文本检测定位所有文字块,识别后,通过规则或简单的NLP找到目标关键词,然后根据其相对位置提取关联信息。这比全文OCR后再进行文本分析,在结构化信息提取上有时更精准。

交互式数据标注平台 :在构建机器学习数据集时,标注员需要点击图片中的特定物体或文字。平台可以集成预训练的检测识别模型,自动预标注出所有文字区域和内容,标注员只需要核对和修正,或者直接点击确认,能极大提升标注效率。

从技术本质上看,“中文点选识别”是将计算机视觉(CV)与具体交互任务相结合的一个典型范例。它要求我们不仅要有强大的感知能力(看得准),还要有精准的执行能力(点得对),更要能适应复杂多变的真实环境。解决这个问题的过程,本身就是对CV工程化能力的一次全面锻炼——从数据采集、模型选型与训练、前后端集成,到对抗性应对和系统维护。每一次“翻车”后的调试,每一次为提升1%准确率而做的优化,积累下来的都是宝贵的工程经验。

更多推荐