ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python与OCR实战:文字点选验证码识别全流程解析

Python与OCR实战:文字点选验证码识别全流程解析 简介面向Python学习者与验证码识别开发者的课程设计资源聚焦“文字点选/选字/选择文字”类验证码的自动识别适合完成课设、毕设或快速搭建识别服务时参考。资源包共48个文件含19个Python脚本、模型权重bin、测试图片、前端展示样式等压缩包约121.82MB整体工程结构清晰涵盖模型训练、接口服务、Web演示等模块代码已适配Windows下Python3.6/3.8/3.10低配置服务器也可平稳运行。已有437人学习下载。该方案在仅300张小样本训练条件下实现约96%准确率单次识别耗时100~300ms兼顾效果与性能同时附带模型文件、演示图片、依赖清单及说明文档便于直接运行验证或二次开发是理解文字点选验证码识别原理与工程落地的实用资料。1. 文字点选验证码识别课设题目到底在考什么文字点选验证码也叫选字验证码、文字点击验证码和传统的四位数字、滑块验证码完全不同用户需要看图里的若干中文文字再按照题目的要求逐个点击正确的那几个字。比如背景图里有“春”“节”“快”“乐”四个字题目要求“请依次点击春节快乐”系统就按点击顺序判断对不对。这类验证码识别在 IT 从业者的日常爬虫、自动化测试、反爬对抗里经常遇到也是很多 Python 课程设计喜欢选的方向——因为它的技术链路很完整图片处理、目标检测、文字识别、坐标计算、模拟点击甚至有 UI 展示和结果评估适合做成一个期末课设项目。这个标题的核心难点不在“识别某个字是什么”而在于“先找到字在哪”。图表里的文字会有背景干扰、字体变形、旋转错位、颜色混杂有时候还有干扰线所以至少要分成“定位”和“识别”两个子问题。训练样本的标注、模型的选型与裁剪、识别结果到点击坐标的映射这三步最容易卡住新手。下文从数据出发把每一步涉及的 Python 代码、模型接口和参数都过一遍读完可以直接照着搭一套能跑通的方案。整个方案在普通笔记本上就能完成深度学习部分用 PaddleOCR 的预训练权重微调即可不要求 GPU 性能。2. 先解决数据文字点选验证码的定位与样本生成2.1 文字点选验证码图片的组成与难点分析拿到一张待识别的验证码图片先用眼睛去拆分底图是一张带纹理的背景文字可能叠加在图标、曲线、色块上字体随机字号随机每个字有独立的旋转角度。这决定了模型不能像 OCR 整段文字那样直接识别而是需要先把“候选文字区域”切割出来。从工程角度看这不只是算法问题更是数据标注成本问题——因为凡是做课程设计最缺的不是模型而是标注数据。自己手工 label 几百张验证码对个人来说耗时太大。这里有一个常见的误区不要一开始就上复杂的目标检测网络去做端到端模型而是先看文字点选验证码的结构。它包含的“文字区域”本质上就是带旋转角度的文本框数量在 48 个之间远少于通用目标检测数据集的规模。所以最简单的可复现方案是自己用 Python 模拟生成训练样本把字体、背景、干扰全部用代码控制。这样天然带有精确的标注框和文字内容绕开了人工标注的高成本而且可以随时扩增数据量、更换字体路径、增加干扰线。诚实地说合成数据在实力上的分布与真实线上数据有一点点差异但对课设题目来说完全够用。模板化生成的核心步骤如下先用 Pillow 创建纯色或渐变背景再用 OpenCV 在图上随机摆放文字、画干扰线、加噪点最后在保存标注文件时记录每个文字的类别和中心点坐标。这个中心点坐标就是后续逻辑里的“点击坐标”。2.1.1 合成样本的 Python 脚本骨架下面的代码实现了一个简单的样本生成器核心接口是generate_sample(texts, save_dir)import os import random import numpy as np import cv2 from PIL import Image, ImageDraw, ImageFont def random_bg(w, h): 生成随机渐变背景避免模型只学到纯色背景的特征 base np.random.randint(100, 200, (h, w, 3), dtypenp.uint8) noise np.random.randint(-20, 20, (h, w, 3), dtypenp.int16) bg np.clip(base.astype(np.int16) noise, 0, 255).astype(np.uint8) return bg def put_text_with_rotation(draw, pos, text, font, angle): 在 draw 对象上按角度渲染文字Pillow 的 rotated text 以中心为锚点 txt_img Image.new(RGBA, (120, 120), (0, 0, 0, 0)) txt_draw ImageDraw.Draw(txt_img) txt_draw.text((60, 60), text, fontfont, fill(0, 0, 0, 255), anchormm) txt_img txt_img.rotate(angle, expandTrue, resampleImage.BICUBIC) return txt_img def generate_sample(texts, save_dir): w, h 300, 200 bg random_bg(w, h) img Image.fromarray(bg) draw ImageDraw.Draw(img) font ImageFont.truetype(simhei.ttf, random.randint(28, 36)) boxes [] for text in texts: angle random.randint(-25, 25) rotated put_text_with_rotation(draw, None, text, font, angle) # 计算有效的放置区域避免文字超出边界 x random.randint(30, w - 80) y random.randint(30, h - 80) img.paste(rotated, (x, y), rotated.split()[3]) boxes.append({text: text, x: x rotated.width // 2, y: y rotated.height // 2}) # 加干扰线 for _ in range(random.randint(3, 6)): x1, y1 random.randint(0, w), random.randint(0, h) x2, y2 random.randint(0, w), random.randint(0, h) draw.line((x1, y1, x2, y2), fill(100, 100, 100), widthrandom.randint(1, 2)) img.save(os.path.join(save_dir, f{_.join(texts)}_{len(os.listdir(save_dir))}.png)) return boxes这个脚本里值得注意的参数有三个truetype(simhei.ttf, ...)中的字体路径可以根据系统环境变化Windows 常见是C:/Windows/Fonts/simhei.ttfLinux 则可能需要wqy-zenhei.ttc。字体差异直接影响后续识别模型的泛化能力。put_text_with_rotation里的anchormm指定 Pillow 的文本锚点为中心这样旋转后旋转轴才是文字中心否则位置会有几个像素的偏移累积起来会影响点击坐标。img.paste(rotated, (x, y), rotated.split()[3])第三个参数是蒙版表示只贴不透明部分避免把一个黑色矩形块贴到背景上。2.1.2 标注格式与点击坐标的关系保存坐标时只需要中心点位置不需要文本框的四点坐标因为模拟点击操作最后只需要一个点。代码里的boxes列表直接输出文字内容和中心点对应了验证码的语义标签。生成时记录一个 JSON 文件会更方便后续训练{image: sample_1.png, texts: [春, 节, 快, 乐], points: [[45, 67], [120, 89], [210, 145], [266, 88]]}这里四个坐标和文字一一对应表示用户在图片上依次点这四个位置就能通过验证。如果你打算用 YOLO 系列训练检测模型要额外把四点坐标转换成cx, cy, w, h格式YOLO 的标注比例需要除以图片宽高。不过课设场景里直接存中心点也够用因为后续 PaddleOCR 天生返回的是多边形四点坐标取均值就是中心。2.2 常见误用直接把整页截图拿去“识别”另一个高频问题学生拿到验证码图片第一反应是直接调用 OCR 接口把图片里所有文字一次性识别出来再挨个去找。这在简单背景上可行但凡是带背景纹理或干扰线的OCR 会误识别出大量不存在的文字。文字检测网络输出的候选框可能比实际文字多或少如果 OCR 把干扰线识别成了“一”后续匹配就乱了。所以实践上必须先把检测阈值调高过滤置信度低的框让干扰误识别的文本框无法进入点击候选列表。合成数据时也要刻意加入干扰线让检测模型学会忽略它们。3. 模型选型与训练让 Python 同时完成文字定位和识别3.1 双阶段方案目标检测OCR 为什么够用从工程成本和课设验收标准来看端到端的深度学习模型实现难度较大也没有必要。文字点选验证码识别只需要两个能力一是“图里有哪些文字、坐标在哪”二是“这个坐标上的字是什么”。前者是目标检测任务后者是 OCR 任务。拆成两个独立的模型可以分别调参和评估出现问题时定位也更清晰。PaddleOCR 的 PP-OCRv4 模型恰好把这两个能力集成在一个 Python 包里面PaddleOCR类的predict()方法直接返回检测框坐标、识别文本和置信度。接口调用上你不需要自己写检测模型和识别模型的推理代码但在课程设计答辩时要能说清楚内部结构检测是基于 DBNet 的变体识别是 CRNN 系列两者共用一个文本检测坐标系。这里有一个关键点PaddleOCR 的检测结果返回的是原始图片尺寸下的坐标单位是像素正好可以直接用于模拟点击。如果图片缩放过去坐标也必须跟着缩放否则点击偏移会导致验证失败。因此在实际工程里进入模型的图片尺寸和 UI 界面上展示的图片尺寸要严格一致或者记录缩放比例。3.2 PaddleOCR 的安装与最小推理代码安装 PaddleOCR 最容易踩的坑是版本不兼容。建议用以下命令创建一个干净的虚拟环境conda create -n captcha python3.9 -y conda activate captcha pip install paddlepaddle2.5.2 pip install paddleocr2.7.3注意paddlepaddle是 CPU 版本不需要额外装 CUDA。模型推理代码只需要几行from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch, det_db_thresh0.3, det_db_box_thresh0.5) result ocr.ocr(sample.png, clsTrue) for line in result[0]: box line[0] text line[1][0] score line[1][1] cx int((box[0][0] box[2][0]) / 2) cy int((box[0][1] box[2][1]) / 2) print(f文字: {text}, 置信度: {score:.2f}, 中心点: ({cx}, {cy}))参数det_db_thresh控制检测阶段二值化的阈值数值越低越容易检测到模糊文字但也会引入误检det_db_box_thresh控制输出文本框的置信度阈值0.5 算是一个经验值。如果图片里的文字很清晰可以提高到 0.6减少干扰线带来的误检框。result[0]是图片上的所有检测结果。每个line是一个文本框包含四个角点坐标、识别文字和一个置信度分数。中心点用box[0]和box[2]左上角和右下角取平均即可。3.3 微调还是直接用预训练模型很多刚接触验证码识别的同学会纠结要不要自己训练。实际上 PaddleOCR 官方提供的中文识别模型对印刷体汉字已经有非常高的精度课程设计的验证码基本是印刷体字体黑体、宋体微调的必要性不大。真正值得自己做的是数据合成和检测阈值调试。当然如果题目要求必须训练自己的模型可以用 PaddleOCR 提供的微调脚本。核心流程是把合成数据转换成 PaddleOCR 要求的标注格式每行写图片路径 制表符 文本框四点坐标 文字内容。下面是格式示例train/001.jpg\t[[45,67],[80,67],[80,110],[45,110]]\t春节快乐之后运行训练命令python tools/train.py -c configs/det/ch_PP-OCRv4_det.yml训练参数由 YAML 文件控制一般只需要改数据集路径和epochs。对课设来说epochs50、batch_size8用 CPU 训练大概两三个小时能收敛到一个可用的效果。不过要提醒一句文字点选验证码比整段文本简单因为每个字都相对独立、字间无粘连检测难度反而低于现实中拍摄的复杂街景文本。所以直接把预训练模型拿来用在绝大多数课设场景下都是更聪明的选择。3.4 识别结果可能出现的两种错误第一种错误是漏检图里有些字和背景融合度高检测阈值过滤掉了。解决方向是降低det_db_box_thresh比如调到 0.3如果还漏检就在图像预处理阶段做一次对比度增强。第二种错误是误检干扰线被当成文字。误检的直接后果是候选文字列表多于实际文字。此时你需要写一层过滤逻辑去和题目文字做匹配而不是把 OCR 结果全部当作有效候选。4. 实现点击序列从识别结果到模拟点击4.1 文字匹配OCR 结果和题目顺序怎么对齐现在图里的文字内容已识别出来用户要根据题目的顺序依次点击。假设题目是一个字符串target 春节快乐OCR 返回的结果是一个个单独的候选目标我们需要把每个候选文字和目标文字做一一匹配。最朴素的匹配逻辑是“先按文本内容过滤再按题目顺序排列”target 春节快乐 ocr_results [(春, 0.99, (45, 67)), (节, 0.98, (120, 89)), (快, 0.97, (210, 145)), (歹, 0.56, (90, 160)), (乐, 0.95, (266, 88))] matched [] for ch in target: for text, score, point in ocr_results: if text ch and score 0.6: matched.append(point) break else: print(f缺少文字: {ch})这段代码按题目中的字符顺序逐个去候选列表里找对应文字找到就记录中心点。for...else...的else分支会在内层循环没有找到匹配时执行用于提示缺字。这个做法的优点是直观缺点是如果 OCR 把某个字识别错了比如“快”识别成“怏”匹配就会失败。所以工程上建议把“字符匹配”升级为“相似度匹配”用编辑距离或字形象似度来做。一个简单有效的方法是如果候选文字和题目目标字符不相等就计算候选文字对应的图像裁剪区域和目标字符模板之间的相似度。这在课程设计里可以用difflib.SequenceMatcher快速实现但真正的生产逻辑通常用向量相似度比如使用 PaddleOCR 的文本向量特征。不过课设代码里用编辑距离就够了。4.1.1 相似度匹配的改进代码from difflib import SequenceMatcher def match_char(target_ch, ocr_item): text, score, point ocr_item if text target_ch: return score ratio SequenceMatcher(None, text, target_ch).ratio() return score * ratio * 0.8这个函数的意义在于target_ch是目标字符ocr_item是候选识别结果。如果文本完全相同则直接用 OCR 置信度如果不完全相同就用编辑相似度打折。打折系数 0.8 是经验值调低会导致错误接受、调高会导致漏识别。对课设来说这个参数通常不需要特别精确只要保证流水线能跑通就行。4.2 坐标转换从模型坐标到屏幕点击坐标这里有一个非常隐蔽的坑OCR 识别的坐标是图片坐标而自动化点击操作发生在浏览器或桌面窗口里。以 Selenium 模拟浏览器点击为例验证码图片显示在网页里时img元素的渲染尺寸可能和原始图片尺寸不一致。如果直接把图片像素坐标交给 Selenium 的ActionChains去点击点击位置必然偏移。正确的做法是计算缩放比例。假设原始图片宽为img_w在网页中显示宽度为display_w那么scale_x display_w / img_w。点击坐标为(center_x * scale_x, center_y * scale_y)再叠加上图片元素在页面中的偏移量。Selenium 获得元素位置的方式如下from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains element driver.find_element(id, captcha_img) location element.location # 元素左上角在页面中的位置 size element.size # 元素渲染后的宽高 scale_x size[width] / img_w scale_y size[height] / img_h for (cx, cy) in matched: click_x location[x] cx * scale_x click_y location[y] cy * scale_y ActionChains(driver).move_to_element_with_offset(element, cx * scale_x, cy * scale_y).click().perform()move_to_element_with_offset以元素左上角为原点所以坐标要传入相对的偏移量。如果操作的系统是桌面客户端而不是网页可以改用pyautogui.click(click_x, click_y)两者的坐标系都是屏幕左上角。注意不要混用Selenium 的坐标是页面坐标pyautogui是屏幕物理坐标如果浏览器窗口没有置顶或页面有滚动条屏幕坐标还需要额外调整。4.3 环境适配与 pip 运行时的常见报错运行 Python 脚本时最影响体验的问题集中在依赖库上。第一次pip install paddleocr后直接跑经常会遇到两个报错第一个是ModuleNotFoundError: No module named paddle。这说明paddlepaddle没有正确安装。在最新版网络热词里python 环境配置被频繁提及建议用pip list检查已装包确认paddlepaddle出现在列表中。没有就单独安装 CPU 版本安装命令已经在前文给出。第二个是cv2.error: OpenCV(4.x) ... assertion failed。这类报错大多是因为图片路径不对或读取出来的img是None常见于 Linux 系统下中文路径或文件名编码问题。代码里应在cv2.imread后判断返回结果是否为None如果为None就用os.path.exists先检查文件是否存在。点击坐标计算这一步还有一个细节PaddleOCR 返回的box坐标类型是 numpy 浮点数直接传给 Selenium 可能会因为类型问题报错。上文代码里已经做了int()转换这里要再强调一次因为 Selenium 对 float 坐标的容忍度不如pyautogui存在隐性精度截断风险。5. 用验证脚本把鲁棒性量化出来很多课程设计交上去之后老师会现场拿几张新的验证码图片来测。这时候最怕的是“在训练集上表现好换张图就崩”。因此最后一章单独讲验证方法怎么量化你的系统稳定性。先建立一个基准验证集。从合成数据中留出 20% 的图片不参与训练代码遍历这些图片对每张图片调用ocr.ocr()再执行匹配逻辑。判断的标准是点击序列是否与目标文字完全一致。统计“全部正确”“部分错误”“完全失败”三类比例。这个验证脚本的要点是不允许人工干预所有图片一视同仁跑完之后输出一个准确率数字作为答辩时的客观依据。def evaluate(test_dir, target_file): total 0 correct 0 for img_path, target in load_test_samples(test_dir, target_file): ocr_result run_ocr(img_path) click_seq build_click_sequence(ocr_result, target) total 1 if click_seq expected_click_points(img_path): correct 1 print(f点击准确率: {correct / total:.2%} ({correct}/{total})) if __name__ __main__: evaluate(test_images, targets.json)这段代码的逻辑很直白load_test_samples读图片列表和标准答案run_ocr返回坐标候选build_click_sequence按目标顺序匹配文字得到点击坐标序列最后与标准答案比对。如果准确率低于 80%优先调整det_db_box_thresh和匹配时的置信度阈值而不是换模型。更多时候问题出在合成数据与真实图片的分布差异上比如你合成的字体是黑体线上验证码是宋体那么 OCR 对宋体字形的置信度天然偏低。这时候最有效的做法不是调整参数而是把真实线上验证码截图加入训练集哪怕只有几十张分布对齐带来的提升也远超调参。进阶方向是绕开 OCR直接用模板匹配来做点击验证码。因为验证码的文字集合和字体相对有限把每个候选文字裁剪成小图再与题库里的模板做相似度计算可以做到比 OCR 更快的速度。PaddleOCR 对单字图片的推理大概几十毫秒但如果图片上有很多候选字总耗时累计仍可能超过验证码的过期时间。实践中可以预留一个开关首次识别用 OCR失败后降级到模板匹配。这种双通路设计放在课设报告里也是很好的加分项。最后提醒一个容易忽略的生产陷阱PaddleOCR 每次实例化都会加载多个模型文件首次调用耗时很长。在验证码识别的循环里不要频繁创建PaddleOCR对象应当在程序启动时创建一次并全局复用。如果你用 Flask 或 FastAPI 对外提供服务这一点尤其重要否则每个请求都要经历几秒钟的模型加载过程。本文还有配套的精品资源点击获取
返回列表