简介:这是一份Python实现文字点选验证码(文字点选/选字)识别课程设计资源,面向正在完成相关课设、毕设或想了解小样本验证码识别方案的开发者。整套方案用约300张样本完成训练,识别精度约96%,单次识别耗时100~300ms,并在Windows的Python3.6、3.8、3.10及1核2G低配服务器上实测可运行,适合轻量部署。压缩包共48个文件,总大小121.82MB,以py源码、pyd编译模块为主,配合bin模型权重、png/jpg训练图片、gunicorn部署配置、service服务脚本及README说明,覆盖数据处理、模型推理、接口封装和上线运行四个环节。资源还包含demo演示脚本与辅助图片,可对照查看模型调用流程、接口返回格式和前端选字页面,方便快速跑通项目。已有437人学习,既可作为课设参考,也可直接复用为选字验证码识别工具。
1. 文字点选验证码的课设:难点不在认字,在找字和排序
期末课设拿到“Python 作业:点击选择文字验证码识别”,很多人第一反应是装好 Tesseract、调通 PaddleOCR,然后发现一个残酷现实:文字点选验证码的难点根本不在“认字”,而在“找到字在哪、按什么顺序点”。这类验证码通常给一句提示词——请依次点击:请、点、击——页面上的文字被旋转、加干扰线、压在花哨背景里,你要先检测出每个文字块的位置,再识别内容,按提示词的顺序依次执行点击。这本质上是一条“目标检测 + 文字识别 + 语义匹配 + 坐标操作”的组合链路。适合想把计算机视觉课设做出完整工程感的学生,也适合爬虫对抗方向想理解文字点选识别原理的从业者。认清这一点,你才不会在 OCR 环节白耗两周。
2. 技术路线怎么选:模板匹配、端到端单模型,还是检测+识别组合链
文字点选验证码的“点选”动作,决定了它和你平时做过的验证码识别课设完全不同。普通字符验证码只要把一串字符转成文本;文字点选要求的是从图中挑出目标文字,并且按提示词的顺序逐个点击。这意味着哪怕你把每个字都认对了,只要不知道它在图片上的坐标,或者把点击顺序搞反,结果都是失败。所以选型前先想清楚你要的最终产物是什么:不是一段识别文本,而是一串有序的点击坐标。
2.1 模板匹配只适合“考试型”验证码
最朴素的方案是模板匹配:把提示词里的汉字渲染成模板图,在验证码大图上做滑窗,算归一化互相关,得分最高的位置就是该字的坐标。这个方案在答辩 demo 里经常能跑通,因为课设演示一般用字体固定、无旋转、背景干净的样例。
但真实场景一上来就翻车:文字旋转 30 度,匹配得分直接掉到 0.2;背景有噪点,误检框比目标还多。模板匹配的优点是零数据、零训练,缺点是稍微换一种字体、加一条干扰线就失效。它本质上是一个“考试型”方案,适合你只想在一张图上演示效果,不适合做成一个能应对多套验证码的识别系统。
2.2 端到端单模型:数据成本直接劝退课设
有论文思路是把整个任务做成一个目标检测/关键点检测问题:输入验证码图片,输出 n 个文字的中心点坐标,并按提示词顺序回归出一个排序向量。听起来最优雅,但课设周期根本扛不住数据成本。
文字点选验证码的标注比普通检测数据贵得多:每个样本要标 n 个文本框位置、每个位置对应的文字内容、以及点击的先后顺序。一张图上三五个字,三个维度的标注缺一不可,半手工标注一天也就标一两百张。端到端模型还要解决顺序监督信号怎么定义的问题:不同验证码的文字数量不同,输出维度是动态的,Loss 里的匈牙利匹配得自己写,坑非常多。我的结论是:除非你已经有上千张标注好的数据,否则课设不要选这条路线。
2.3 检测+识别+匹配:最稳的组合链
绝大多数能落地的文字点选识别方案,都是把它拆成三个子问题:
- 检测:先用检测模型在验证码大图上框出所有文字候选区域,输出若干个 bbox 和类别,不关心先后顺序;
- 识别:对每个候选框,确认它到底是哪个汉字;
- 匹配与排序:把提示词解析成目标字符列表,逐个在候选框里做匹配,再按提示词顺序排序,按序点击。
三个子问题各自都有成熟工具,拆开做的好处是任何一个环节出问题都能单独排查。检测用 YOLO 系列,识别有两个分支选择:字库有限时,直接用 YOLO 的分类头预测字 id;字库不可控时,裁出文字块丢给 PaddleOCR。最推荐的课设做法是前者——把“识别”退化成“分类”,因为常见文字点选验证码的字库就几百个字,用一个有限类别检测模型同时输出“文字位置+字类别”,比“检测+OCR”两条模型串联稳定得多。
| 方案 | 需要数据量 | 抗干扰能力 | 开发周期 | 课设通过率 |
|---|---|---|---|---|
| 模板匹配 | 0 | 极差 | 1 天 | 40%(仅演示图) |
| 端到端单模型 | 1000+ 张全量标注 | 中 | 3~4 周 | 60%(容易过拟合) |
| 检测+分类/识别+匹配 | 200~500 张 | 强 | 5~7 天 | 90% |
这张表的结论很直接:目标检测承担“找字”,分类头承担“认字”,匹配逻辑承担“排序”,三个环节用的是同一个模型框架,训练和推理链路最短。你不需要会写复杂的 CTC Loss,也不需要准备一万张数据,这是课设性价比最高的路线。
3. 造数据集是第一步:合成脚本、标注转换与配比规则
文字点选验证码没有公开数据集,这是一个所有做这个方向的人都要面对的硬约束。比赛和论文里见过的公开数据大多是标准印刷体 OCR 数据集,真实文字点选样本涉及业务方利益,基本不会外流。所以课设数据只能自己造,而且动力很足:你不需要把验证码厂商的样本偷出来,只需要合成足够逼真的样例,模型照样能学。
3.1 合成数据怎么做才接近真实验证码
见过太多人用白底黑字合成数据,训练出来的模型在真实验证码上直接废掉。合成数据的核心不是把字摆上去,而是模拟干扰:旋转、色彩抖动、干扰线、背景纹理,缺一不可。下面这个脚本把验证码图片生成做成一个可配置的流程:
# 合成文字点选验证码数据:输出图片 + 每张图的文字位置与类别标签 import random import uuid import os from PIL import Image, ImageDraw, ImageFont, ImageFilter # 字库有限:文字点选验证码常见的汉字集合,可按实际场景扩充 WORD_POOL = ["请", "点", "击", "提", "交", "验", "证", "码", "确", "认", "旋", "转", "下", "载", "登", "录", "注", "册", "安", "全"] def random_color(light=True): if light: return (random.randint(120, 255), random.randint(120, 255), random.randint(120, 255)) return (random.randint(0, 80), random.randint(0, 80), random.randint(0, 80)) def random_font(size): # 注意:Windows 请换成 C:/Windows/Fonts/msyh.ttc 或 simhei.ttf font_path = "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf" return ImageFont.truetype(font_path, size) def draw_one_sample(img_size=(480, 320), num_chars=4): img = Image.new("RGB", img_size, random_color(light=True)) draw = ImageDraw.Draw(img) # 背景干扰线:随机画几条扭曲的线,模拟验证码常见干扰 for _ in range(random.randint(5, 10)): x1, y1 = random.randint(0, img_size[0]), random.randint(0, img_size[1]) x2, y2 = random.randint(0, img_size[0]), random.randint(0, img_size[1]) draw.line([(x1, y1), (x2, y2)], fill=random_color(light=False), width=1) labels = [] used_chars = random.sample(WORD_POOL, num_chars) for ch in used_chars: font_size = random.randint(36, 52) font = random_font(font_size) # 每个字单独渲染,便于控制旋转 char_img = Image.new("RGBA", (font_size * 2, font_size * 2), (0, 0, 0, 0)) char_draw = ImageDraw.Draw(char_img) char_draw.text((font_size // 2, font_size // 2), ch, font=font, fill=(*random_color(light=True), 255)) # 随机旋转:-25 到 25 度,文字点选验证码最典型的干扰 char_img = char_img.rotate(random.randint(-25, 25), expand=True, resample=Image.BICUBIC) # 随机位置:避免文字重叠,简化起见直接用随机坐标 cx = random.randint(30, img_size[0] - 80) cy = random.randint(30, img_size[1] - 80) img.paste(char_img, (cx, cy), char_img) bbox = (cx, cy, cx + char_img.width, cy + char_img.height) labels.append({"char": ch, "bbox": bbox}) # 整图轻微模糊,模拟真实截图的清晰度损耗 img = img.filter(ImageFilter.GaussianBlur(radius=0.5)) return img, labels if __name__ == "__main__": os.makedirs("syn_images", exist_ok=True) for i in range(500): img, labels = draw_one_sample() img.save(f"syn_images/{uuid.uuid4().hex[:8]}.jpg") with open(f"syn_images/{uuid.uuid4().hex[:8]}.txt", "w") as f: for lab in labels: f.write(f"{lab['char']} {lab['bbox']}\n") print("synthetic data done")这个脚本的逻辑是把每个汉字单独渲染到透明图层上,做随机旋转、随机缩放、随机位置粘贴,最后叠加干扰线和轻微模糊。有几个参数值得注意:旋转角度设在 25 度以内,超过 30 度人眼都难认,模型学出来也不可靠;字体字号控制在 36 到 52 像素之间,太小了检测器下采样后特征丢失,太大了一张 480x320 的图放不下互不重叠的四五个字。GaussianBlur 的 0.5 半径不要省略,真实验证码是从网页截图里来的,截图本身就有缩放损耗,不加这步模型会被格式化的“干净”文字惯坏。
3.2 标注格式选型:VOC 转 YOLO 的转换脚本
合成脚本输出的标签是“文字 + 左上右下坐标”,但 YOLO 训练需要的是归一化的 category 和 bbox。如果你的标注工具导出的是 Pascal VOC 的 XML 格式,下面这段转换脚本可以直接用:
# VOC XML 转 YOLO txt:把标注工具导出的 XML 转成 YOLO 训练格式 import xml.etree.ElementTree as ET import os # 类别字典:必须和合成脚本里的字库对齐,训练时字 id 保持一致 CLASS_MAP = {"请": 0, "点": 1, "击": 2, "提": 3, "交": 4, "验": 5, "证": 6, "码": 7, "确": 8, "认": 9, "旋": 10, "转": 11} # 按需扩充 def voc_to_yolo(xml_path, out_dir): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: continue # 过滤掉不在字库里的类别 cls_id = CLASS_MAP[name] box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) # YOLO 格式:class_id, x_center, y_center, width, height(均归一化) x_center = ((x1 + x2) / 2) / img_w y_center = ((y1 + y2) / 2) / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_name = os.path.splitext(os.path.basename(xml_path))[0] + ".txt" with open(os.path.join(out_dir, out_name), "w") as f: f.write("\n".join(lines)) # 批量转换 xml_dir, out_dir = "annotations", "labels" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith(".xml"): voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir)这段代码的重点是 CLASS_MAP 必须和训练时的类别顺序严格一致。YOLO 的类别 id 是从 0 开始递增的,如果你在合成脚本里字库顺序是“请点击提交…”,那么训练脚本里的 data.yaml 也要按这个顺序写,一旦错位,模型训出来永远把“请”当“点”,点击位置全部偏移。转换脚本里我加了类别过滤,不在字库里的标注会被跳过,这一步能避免标注员误标了一些生僻字导致类别数不统一。
3.3 数据集配比与验证集划分
合成数据 500 张,真实数据哪怕只有 50 张,也要混着用。合成数据负责教模型“文字长什么样、干扰长什么样”,真实数据负责教模型“网页截图里的纹理和字体风格”。配比上我习惯按 9:1 切训练集和验证集,但切分时要用随机抽样,不能把合成数据全放训练集、真实数据全放验证集——那样训练集和验证集分布差距太大,loss 会忽高忽低,你无法判断是模型问题还是数据集问题。
如果你能拿到少量真实验证码截图,别直接整图塞进训练集。真实截图往往包含提示文字、提交按钮等干扰元素,YOLO 会把这些 UI 元素当成背景或者误检成文字。我一般会把真实截图先人工裁掉顶部提示区域,只保留放置文字的区域,再标注。这一步脏活累活决定了模型的最终上限,值得花一下午。
4. 把流程跑通:YOLO 检测文字框、匹配提示词、按序模拟点击
数据集就位后,训练本身反而最无脑。用 YOLO 的官方命令行直接训,难点在训练参数和后面的推理链路——检测、识别、匹配、排序、点击,五个环节任何一个断了,最终通过率都上不去。
4.1 用 YOLO 训练文字检测器:命令与参数
# 训练文字检测器:yolov8n 轻量模型,适合课设场景的几百张数据 yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ epochs=50 \ imgsz=640 \ batch=16 \ freeze=10dataset.yaml 里要写清楚训练集和验证集路径,以及类别列表:
path: ./dataset train: images/train val: images/val nc: 12 names: ["请", "点", "击", "提", "交", "验", "证", "码", "确", "认", "旋", "转"]这里几个参数是课设场景最容易调错的。imgsz 用 640 是速度和精度的平衡点,如果你的验证码图片本身就很大,可以升到 768,但训练时间会明显变长;batch 设为 16 的前提是显存不低于 8G,如果笔记本显存只有 4G,改成 8 更稳,否则直接 OOM。freeze=10 是冻结前 10 层 backbone,对小数据集非常有用:你只有几百张图,从头训练 backbone 极易过拟合,冻结早期层让它只学中高层的语义特征,收敛更稳。epochs 50 是上限,配合 early stopping 实际可能 20 轮就停了,不用死等。
4.2 推理脚本:检测、匹配、排序、点击一次跑通
训练完拿到 best.pt,接下来是推理链路。这个脚本把整个点选流程串起来:
# 文字点选推理主流程:检测 -> 匹配 -> 排序 -> 点击 import re import time import random import pyautogui from ultralytics import YOLO from pypinyin import lazy_pinyin from rapidfuzz import fuzz model = YOLO("runs/detect/train/weights/best.pt") def parse_prompt(prompt_text): """从提示词里取出目标文字序列,忽略'请依次点击'等指令前缀""" # 常见句式:'请依次点击:请、点、击' 去掉指令部分,保留中文目标字 segment = re.split(r"[::]", prompt_text)[-1] chars = [ch for ch in segment.strip() if re.match(r"[\u4e00-\u9fa5]", ch)] return chars def char_similarity(a, b): """字相似度 = max(表面编辑距离, 拼音相似度),拼音兜底同音字""" surface = fuzz.ratio(a, b) / 100.0 try: pa, pb = lazy_pinyin(a)[0], lazy_pinyin(b)[0] phonetic = fuzz.ratio(pa, pb) / 100.0 except Exception: phonetic = 0.0 return max(surface, phonetic) def detect_text_boxes(screenshot_path): """YOLO 检测:返回 (类别名, bbox, 置信度) 列表,按置信度降序""" results = model.predict(screenshot_path, conf=0.55, iou=0.45) boxes = [] names = model.names for r in results: for box in r.boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() cls_id = int(box.cls[0]) score = float(box.conf[0]) boxes.append((names[cls_id], (x1, y1, x2, y2), score)) # 置信度降序,保证匹配时优先取最可靠的框 boxes.sort(key=lambda x: x[2], reverse=True) return boxes def match_prompt_to_boxes(boxes, target_chars): """对提示词里的每个字,在检测框里找最相似的候选,返回有序的点击坐标""" used = set() ordered = [] for ch in target_chars: best_bi, best_score = None, 0.0 for bi, (cls_name, bbox, score) in enumerate(boxes): if bi in used: continue # 每个框只能被点一次 sim = char_similarity(ch, cls_name) if sim > best_score: best_score, best_bi = sim, bi if best_bi is None: return None # 提示词里有字没找到,本次识别失败 used.add(best_bi) ordered.append(boxes[best_bi][1]) return ordered def click_in_order(ordered_boxes, offset_x=0, offset_y=0, scale=1.0): """按顺序点击:坐标做缩放和偏移,时间间隔模拟人工,降低行为特征""" for bbox in ordered_boxes: x1, y1, x2, y2 = bbox cx = (x1 + x2) / 2 * scale + offset_x cy = (y1 + y2) / 2 * scale + offset_y # 中心点附近加随机偏移,避免每次都点同一像素 jitter_x = random.uniform(-3, 3) jitter_y = random.uniform(-3, 3) pyautogui.click(cx + jitter_x, cy + jitter_y) time.sleep(random.uniform(0.2, 0.5)) # 随机等待,模拟人工点击节奏 # 主流程 if __name__ == "__main__": screenshot_path = "captcha_now.png" prompt_text = "请依次点击:请、点、击" target_chars = parse_prompt(prompt_text) boxes = detect_text_boxes(screenshot_path) ordered = match_prompt_to_boxes(boxes, target_chars) if ordered is None: print("存在未匹配到的目标字,需要保留现场排查") else: # 实际使用时按浏览器截图的偏移量和缩放比调整 click_in_order(ordered, offset_x=0, offset_y=0, scale=1.0)这个脚本的每个环节我都加了超出“能跑”的细节。parse_prompt 里用正则先切掉冒号前的指令前缀,是为了防止“请依次点击”这五个字里的“请”被误当成目标字,如果提示词句式变化,比如“按顺序选择:…”也能兼容;match_prompt_to_boxes 里用“每个框只能被点一次”的 used 集合,是防止两个字匹配到同一个检测框——这种情况出现频率比你想象的高,字库相近的字模型本来就容易混淆;click_in_order 里的随机延迟不是玄学,验证码风控会记录点击间隔,如果两次点击间隔恒定为 0 毫秒,行为特征一眼假。
4.3 阈值怎么调:conf 与 iou 的边界
检测的 conf 和 iou 是推理环节最值得调的两个参数。conf=0.55 意味着置信度低于 0.55 的框会被丢弃,调低了容易把背景纹理误检成文字,调高了真实文字被漏检,尤其在旋转超过 20 度的样子上。iou=0.45 是 NMS 的合并阈值,多个重叠框会合并成一个,调太大会把相邻的两个文字框合并成一个,调太小同一个字会输出两个框导致匹配逻辑去重失效。我的习惯是先跑 100 张验证集,统计漏检数和误检数,conf 从 0.5 起步每次加 0.05,直到漏检率显著上升的一刻,那个点就是当前数据集的 conf 上限。阈值调参没有标准答案,每套验证码的合成数据和真实数据分布不同,唯一靠谱的方法就是回放测试。
5. 通过率暴跌的 5 个坑:现象、原因与解法
这一节的每条经验都来自实际跑数据翻车后的排查记录,按“现象 → 原因 → 解决”写清楚,基本覆盖课设答辩前最常卡住你的问题。
5.1 检测框很准,但单字识别总对不上
现象:YOLO 把四个文字框全部框对了位置,但提示词是“请、点、击”,模型预测的分类结果却是“讠青、点、陆”,相似度匹配直接失败。原因:合成数据里的字体和真实验证码字体不一致,模型学到的是合成字体的纹理特征,真实验证码的文字旋转加压缩后,笔画挤压让“请”看起来像“讠青”,令分类头错判。解决:字库有限时不要把识别和检测拆成两个模型,直接用 YOLO 的分类头输出字 id,因为分类头只区分字库里的几十个类,比通用 OCR 的几千个类简单得多;同时在合成数据里混入 10% 到 20% 的真实截图字体,让分类头学到真实字体的笔画变形。
5.2 点击坐标和文字位置偏了一截
现象:检测框在图片上的位置画出来完全正确,但 pyautogui 点下去点到了文字旁边的空白处。原因:模型预测的坐标是相对于验证码图片的,而 pyautogui 点击的是屏幕坐标。如果验证码图片是截屏后裁剪的,或者浏览器有缩放比例,模型输出坐标必须经过缩放和偏移换算。解决:在建图时就记录截图的原始尺寸和模型输入尺寸,换算公式很简单:
# 截图坐标 -> 屏幕坐标:先除模型缩放比,再加截图区域在屏幕上的偏移 screen_x = model_x / scale_factor_x + offset_x screen_y = model_y / scale_factor_y + offset_yscale_factor 是截图原始宽度除以模型输入宽度,offset 是截图区域左上角在屏幕上的位置。我一般会在推理脚本里把这两个值做成配置项,而不是写死在代码里,因为浏览器窗口位置一变就得重新算。
5.3 合成数据太干净,换一套验证码就翻车
现象:训练集 loss 收敛到 0.02,验证集准确率 99%,但只要换一个网站的验证码,通过率直接掉到 30%。原因:合成数据用的是纯色背景加规则干扰线,真实验证码的背景是复杂纹理、渐变、噪点叠在一起,模型只学会了在“干净”图上找文字,背景一变就当机。解决:在合成脚本里加背景纹理混合层,把真实验证码的截图背景直接抠出来当底图,再把文字贴上去;训练时开启 HSV 扰动和随机模糊。这条是数据增强里最有效的一招,背景一变你的模型泛化能力立刻暴露。
5.4 提示词带方向:“从右往左依次点击”
现象:提示词是“请从右往左依次点击:提、交、验、证”,模型识别和匹配全部正确,但点击顺序变成从左到右,验证失败。原因:parse_prompt 只提取了目标汉字,把“从右往左”这个方向指令丢弃了。解决:在解析函数里先检测方向词,包含“右往左”就对目标坐标按 x 降序排列,包含“下往上”按 y 降序排列,其他情况按 y 升序(通常验证码文字是横向排列的)。不要小看这个细节,课设答辩时老师经常故意换一套带方向词的验证码来试你。
5.5 笔记本训练:几百张图过拟合到亲妈都不认识
现象:训练到第 15 轮验证集 loss 开始反弹,但训练集 loss 还在降,说明模型在背训练图。原因:数据量只有几百张,模型容量太大,把每张图的随机干扰线都记住了。解决:第一,模型从 yolov8l 换成 yolov8n,参数减少一大截;第二,freeze=10 冻结 backbone 前 10 层;第三,数据增强打开,合成数据里把旋转角度、干扰线条数、背景纹理的随机范围加大;第四,加 early stopping,patience 设为 10 轮,val loss 连续 10 轮不降就停。这四步组合下来,几百张图也能训出不明显过拟合的检测器。
6. 验证方法兜底与把课设做出进阶分
训练完模型,不要只看 val loss,那东西说服不了答辩老师。我会写一个批量回放脚本:准备 50 张真实验证码截图,每张图配好提示词,脚本自动执行“截图 -> 检测 -> 匹配 -> 点击”全流程,然后统计最终通过率。这里的关键不是说单字识别率,而是“整题通过率”——五个字点对四个也算失败,这个指标才和真实业务一致。我一般给自己定的及格线是 80%,低于这个数字回去调数据增强和阈值,高于 90% 再考虑扩展字库。
6.1 用真实验证码批量回放测通过率
回放脚本不用特意写,直接把第 4 章的推理脚本包一层循环,失败时保留“提示词+截图+模型输出”的三元组现场,方便排查是哪一环挂了。注意每次回放之间要加随机等待,连续高频请求会触发风控,通过率会被人为拉低,测出来不是模型真实水平。回放还有个好处:你能统计出失败样本的分布——是“字识别错了”还是“字找到了但排序错”,这两个问题的对策完全不同。
6.2 把同类字合并成语义簇,用拼音+词向量兜底
如果做完基础版还想冲优秀,我建议在两个方向上升级。第一个是把匹配逻辑从单字编辑距离换成“拼音相似度 + 语义词向量”的融合打分。文字点选验证码经常用同音字干扰,提示词写“请点点击”,图片里放的是“请点讠击”,表面编辑距离匹配不上,但拼音完全一致,用 pypinyin 转拼音后比对就能对上。第二个方向是把纯检测模型升级成带中心点回归的轻量关键点模型,直接用 CenterNet 的思路回归 n 个文字中心点,省掉 NMS 环节,推理更快——这个方向写进课设报告里能体现出你对检测原理的理解不止于调包。
我当年在这个课设上最大的教训,就是死磕 PaddleOCR 的单字识别率,花了整整一周在“请”被识别成“讠青”的泥潭里打转。后来把思路从“识别一张图里的所有字”转成“用分类头预测有限字库里是哪个字”,所有问题迎刃而解。这个方向如果你能老老实实把合成数据做好、把坐标换算写对、把回放脚本跑起来,通过率做到 85% 以上是大概率事件。希望帮到你。
本文还有配套的精品资源,点击获取