十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

文字点选验证码识别实战:基于YOLOv5与OCR的Python课设指南

文字点选验证码识别实战:基于YOLOv5与OCR的Python课设指南 简介这是一份面向Python课程设计或验证码识别入门者的完整项目资源专注解决文字点选、选字类验证码的自动识别问题。项目在仅300张样本的小样本训练下达到96%准确率单次识别耗时约100~300ms且经过Windows平台Python3.6/3.8/3.10实测低至1核2G的服务器也能流畅运行适合作为毕业设计、课设或业务原型参考。资源包共48个文件大小约121.82MB以Python源码(.py)为主辅以编译后的.pyd加速模块、训练好的.bin模型权重、示例图片(png/jpg)以及配置文件覆盖数据预处理、模型训练、接口调用与Web展示等环节。其中纯文本说明和markdown文档便于快速理解项目结构图片资源则可用于自测与效果展示。目前已有437人学习下载适合希望快速上手验证码识别技术、借鉴小样本训练思路或在此项目基础上二次开发的开发者。1. 文字点选验证码识别难的不是“认字”是“把字和位置对上”文字点选验证码识别核心不是“认字”。很多第一次做课设的同学拿到题目第一时间去搜 OCR想的是把整张图的文字全读出来——方向错了。真正的文字点选验证码背景图里散落着若干候选文字干扰线、噪点、旋转字符混在一起系统任务是“按提示顺序依次点击正确的文字”不是把整张图当作文本识别。所以识别链路拆开是三步定位文字位置、读清文字内容、按顺序生成点击坐标。这篇笔记按照一个 Python 课设的实际路径来讲从技术选型、造数据、训练检测模型到坐标映射和常见坑尽量让新手能一步步复现让已经跑通基础流程的人看到边界和参数陷阱。文字点选方向的课设可演示、可数据化评估、可扩展是个性价比不错的 CV 入门项目。2. 文字点选识别链路拆三步定位、读字、排序文字点选验证码虽然叫“验证码”但它和传统数字字母验证码有本质区别。传统验证码是“一行文本”而文字点选的结构是一张背景图上有若干个候选文字数量和位置随机系统提示“请依次点击甲、乙、丙”。算法要做的不是把全图文字读出来而是先找到这些文字在哪、再判断哪个字是哪个最后按提示顺序给出坐标。这个结构决定了它的识别链路必须拆成定位、读字、排序三段。2.1 文字点选验证码的识别任务拆分检测是主线OCR 是辅助先看三个常见误用方案理解为什么不能直接套用现成工具。方案一直接整图 OCR。OCR 对印刷体整段文字有效但文字点选里的文字是离散的、可能旋转、带干扰整图 OCR 会漏识别还会把干扰线识别成文字更致命的是 OCR 输出的是文本流没有位置和顺序语义。方案二模板匹配。模板匹配需要给每个候选文字准备标准字模实际样本里字体、大小、旋转、背景都在变模板匹配几乎必崩适合做对照组不适合做主线。方案三端到端模型直接输出点击坐标理论上可行但需要大量标注数据课设时间和算力都不现实。正确主线是用目标检测模型先框出所有候选文字的位置这一步只区分“是文字”和“不是文字”不关心具体是什么字然后对每个框裁图用 OCR 或单字分类器识别框内文字最后按提示文字顺序排序输出点击坐标。三个任务的输出分别是位置框、单字内容、有序坐标点。理解这条链路后面所有参数调整都有了依据。2.2 两条可落地的技术路线轻量检测模型 vs 传统 CV根据课设常见条件一台普通笔记本、没有 GPU、两周到一个月时间我一般让同学在下面两条路线里选也建议两条都跑一条保底一条冲指标。对比项路线A纯 OpenCV 传统视觉路线B轻量目标检测模型YOLOv5s开发难度低不依赖深度学习框架中需要装 torch 和模型文件数据要求不需要训练数据需要几百张标注图可合成抗干扰能力弱干扰线稍复杂就崩强模型能学到干扰线特征CPU 推理速度极快单张几十毫秒较快单张 0.1~0.3 秒课设得分点流程完整但指标一般准确率高可扩展对比实验主要风险真实场景泛化差合成数据和真实数据分布不一致路线 A 的核心是灰度化、二值化、连通域分析把每个文字块当作候选框再用轮廓特征过滤干扰。路线 B 的核心是先用合成数据训练 YOLOv5s推理时用检测框裁图再交给单字分类器。如果只有 CPU 笔记本YOLOv5 可以换 nano 版本并把输入尺寸降到 416训练时间能省一半检测精度损失不大。环境准备方面python 环境建议用 vscode 或 pycharm 配好解释器装好 opencv-python、numpy、torch、paddleocr 这些第三方库。这里有个容易忽略的点torch 在 CPU 和 GPU 版本的行为差异只在速度不影响结果课设用 CPU 版完全够。3. 文字点选的数据从哪来合成样本与图像预处理目标检测模型训练的最大痛点是人工标注。手工框几百张图每张图 5~8 个字工作量极大而且容易标歪。课设里最常见的做法是合成样本用 OpenCV 和 PIL 把文字画到背景上同时自动生成 YOLO 格式的标注文件。合成数据的可控性强干扰线数量、文字旋转角度、背景复杂度都能调后面做对比实验时天然有梯度。3.1 用 OpenCV 合成带标注的样本自动生成训练集下面这个脚本会生成 500 张 416x416 的训练图每张图随机放 5~8 个汉字随机画干扰线同时写出对应的 YOLO txt 标注文件。字体文件用 Windows 自带的 simhei.ttf换成其他字体也行但建议至少两种字体混合避免模型过拟合到单一字形。import os import random import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont chars 甲乙丙丁戊己庚辛壬癸子丑寅卯辰巳午未申酉戌亥 out_dir dataset/images/train label_dir dataset/labels/train os.makedirs(out_dir, exist_okTrue) os.makedirs(label_dir, exist_okTrue) def draw_char(img_pil, ch, x, y, size, angle): # 每个字符画在独立透明图层上方便旋转后贴回背景 font ImageFont.truetype(simhei.ttf, size) tmp Image.new(RGBA, (size * 2, size * 2), (0, 0, 0, 0)) tmp_draw ImageDraw.Draw(tmp) tmp_draw.text((size // 2, size // 2), ch, fontfont, fill(0, 0, 0, 255), anchormm) tmp tmp.rotate(angle, expandTrue) img_pil.paste(tmp, (x, y), tmp) for idx in range(500): # 浅色背景模拟真实验证码的底色 img np.random.randint(200, 255, (416, 416, 3), dtypenp.uint8) img_pil Image.fromarray(img) draw ImageDraw.Draw(img_pil) num_words random.randint(5, 8) selected random.sample(chars, num_words) boxes [] for ch in selected: x random.randint(20, 320) y random.randint(20, 320) size random.randint(32, 52) angle random.randint(-15, 15) draw_char(img_pil, ch, x, y, size, angle) boxes.append((x, y, size, ch)) # 干扰线数量、粗细、颜色都随机 for _ in range(random.randint(3, 8)): x1, y1 random.randint(0, 415), random.randint(0, 415) x2, y2 random.randint(0, 415), random.randint(0, 415) draw.line((x1, y1, x2, y2), fill(random.randint(0, 120),) * 3, widthrandom.randint(1, 2)) img np.array(img_pil.convert(RGB)) cv2.imwrite(f{out_dir}/syn_{idx:04d}.jpg, img) # YOLO 标注类别0 归一化中心坐标和宽高 with open(f{label_dir}/syn_{idx:04d}.txt, w, encodingutf-8) as f: for (x, y, size, ch) in boxes: cx (x size) / 2 / 416 cy (y size) / 2 / 416 w size / 416 h size / 416 f.write(f0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n)这段代码的关键设计是所有候选字的类别都写 0因为检测模型只需要回答“这里有没有字”不需要回答“这是什么字”。字的识别放到后面单独做这样可以把检测模型简化成单类别目标检测训练难度和过拟合风险都会明显下降这是文字点选课设里一个很实用的小技巧。参数说明size 控制字号范围32~52 对应 416 图上的实际像素太小模型难学太大文字容易相互遮挡angle 控制在 -15~15 度旋转超过 30 度后单字识别难度陡增真实验证码很少超过这个范围干扰线使用深色随机灰度宽度 1~2 像素和文字形成对比模型才有东西可学。合成图的类别只有一类所以 data.yaml 里的 nc 写 1 即可。还有一个重要动作真实验证码截图也要收集一小批30~50 张就够用 LabelImg 手工标注后加入训练集。合成数据负责让模型学会“文字长什么样”真实数据负责纠正分布偏差两者混合才能让准确率在真实场景不掉链子。3.2 图像预处理降噪、二值化、形态学开运算很多同学一上来就做暴力预处理把图二值化再用膨胀腐蚀结果把文字也抠烂了。预处理在文字点选场景里的定位应该是“辅助工具不是主线”。具体来说模型训练阶段直接用原始图或轻微增强图不做二值化传统 CV 保底路线必须做二值化和连通域分析推理阶段可根据图像质量做轻度降噪。import cv2 import numpy as np def preprocess_for_detection(img_path): # 灰度化 中值滤波去椒盐噪声 img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img cv2.medianBlur(img, 3) # OTSU 自动阈值二值化前景文字为白色 _, binary cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) # 开运算先腐蚀后膨胀去掉孤立噪点和细干扰线 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) opened cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations1) # 连通域分析按面积过滤小噪点 num, labels, stats, _ cv2.connectedComponentsWithStats( opened, connectivity8) boxes [] for i in range(1, num): x, y, w, h, area stats[i] if area 30 or w 10 or h 10: continue boxes.append((x, y, w, h)) return boxes逻辑说明medianBlur 的核大小选 3过大会把文字笔画磨掉OTSU 阈值适合背景和文字灰度差异明显的图如果背景复杂可以改用自适应阈值但要注意自适应阈值对噪声更敏感。开运算的 kernel 也是 3x3迭代 1 次就停目的是去掉 1~2 像素的椒盐噪声和细干扰线不是让文字变形。连通域过滤的阈值 30 和 10 是针对 416 图经验值换了大图要等比例放大。这里必须提醒一句预处理宁轻勿重。检测模型本身对噪声有一定鲁棒性真正影响准确率的是“文字被破坏”和“干扰线被保留”前者比后者严重得多。合成数据里的干扰线是模型训练的一部分直接喂原始图让模型学习“哪些是干扰、哪些是文字”效果往往比人工规则过滤更好。4. 用 YOLO 训练文字检测从模型输出到点击坐标数据准备好之后进入主流程训练检测模型、推理得到候选框、把框映射成点击坐标。这一章是课设的核心也是参数细节最多的地方。4.1 用 YOLOv5s 训练文字检测模型数据组织与训练命令先按 YOLO 的目录规范把数据组织好train 和 val 分开标注文件与图片同名同目录规则。dataset/ ├── images/ │ ├── train/ │ │ ├── syn_0000.jpg │ │ └── ... │ └── val/ │ ├── syn_0001.jpg │ └── ... └── labels/ ├── train/ │ ├── syn_0000.txt │ └── ... └── val/ └── ...data.yaml 内容如下类别只有一类名称叫 char表示“文字候选框”。train: dataset/images/train val: dataset/images/val nc: 1 names: [char]训练命令python train.py --data data.yaml --weights yolov5s.pt \ --img 416 --batch 16 --epochs 50 --patience 10参数说明img 选 416 而不是默认的 640因为课设数据是合成的文字目标本身不大416 在 CPU 上能省近一半训练时间batch 16 是 CPU 和 4G 显存都基本能跑的值显存不够就降到 8epochs 从 50 起步用 patience 10 做早停合成数据简单训练后期 loss 下降很快没必要硬跑到 100 轮weights 用 yolov5s.pt 作为预训练。为什么不选 nanonano 虽然在 CPU 上更快但对小尺寸文字的检测边框贴合度明显不如 s课设验收时要展示检测框可视化s 的效果更稳定。训练完成后看 runs/train/exp/weights/best.pt 这个文件后面的推理和演示都用它。如果训练时 loss 一直不降先检查 data.yaml 路径是不是相对路径、图片和 txt 是否一一对应这种问题占训失败原因的一半以上。4.2 推理端检测框坐标、OCR 识别、顺序整理推理端是整篇代码里最容易翻车的地方翻车点集中在坐标换算。模型输入是 letterbox 后的 416 图输出的坐标是相对 416 图的要还原到原图必须记录缩放比例和 padding 量。下面这段代码是完整的推理流程按“检测 → 裁图 → 单字识别 → 排序输出”顺序执行。import cv2 import torch import numpy as np model torch.hub.load(ultralytics/yolov5, custom, pathruns/train/exp/weights/best.pt, force_reloadFalse) model.conf 0.35 model.iou 0.45 def letterbox(im, new_shape416, color(114, 114, 114)): h, w im.shape[:2] r min(new_shape / h, new_shape / w) new_unpad (int(round(w * r)), int(round(h * r))) dw (new_shape - new_unpad[0]) / 2 dh (new_shape - new_unpad[1]) / 2 top, bottom int(round(dh - 0.1)), int(round(dh 0.1)) left, right int(round(dw - 0.1)), int(round(dw 0.1)) im cv2.resize(im, new_unpad, interpolationcv2.INTER_LINEAR) im cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor) return im, r, dw, dh def infer_and_get_points(img_path, prompt_chars): img cv2.imread(img_path) orig_h, orig_w img.shape[:2] img_input, r, dw, dh letterbox(img) results model(img_input) boxes results.xyxy[0].cpu().numpy() char_boxes [] for *xyxy, conf, cls in boxes: x1, y1, x2, y2 [int(v) for v in xyxy] # 回原图坐标先减 padding 再除缩放比 x1_orig int((x1 - dw) / r) y1_orig int((y1 - dh) / r) x2_orig int((x2 - dw) / r) y2_orig int((y2 - dh) / r) cx (x1_orig x2_orig) // 2 cy (y1_orig y2_orig) // 2 char_boxes.append((cx, cy, x1_orig, y1_orig, x2_orig, y2_orig)) # 对每个框裁图交给单字识别 # 这里用 PaddleOCR rec 模型或自训练分类器 from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsFalse, langch, show_logFalse, detFalse) char_dict {} for (cx, cy, x1, y1, x2, y2) in char_boxes: crop img[y1:y2, x1:x2] if crop.size 0: continue result ocr.ocr(crop, clsFalse) text result[0][0][0] if result and result[0] else if text: char_dict[text] (cx, cy) # 按提示文字顺序输出点击坐标 click_points [] for ch in prompt_chars: if ch in char_dict: click_points.append(char_dict[ch]) return click_points这段代码有三个关键点。第一是坐标回算letterbox 时记录 r、dw、dh推理坐标先减 padding 再除以缩放比这里是“差一行代码坐标就偏”的高危区。第二是单字识别PaddleOCR 的 detFalse 表示只做识别不做检测因为裁剪框已经由检测模型给出再做检测会重复且更慢。第三是 char_dict 的键是文字内容提示文字是什么就去字典里取对应坐标而不是按检测框的 x 坐标从左到右排顺序逻辑由提示字符串决定。参数说明model.conf 和 model.iou 是 YOLOv5 推理的核心阈值conf 默认 0.25 对合成数据偏低容易把干扰线框进来建议 0.35~0.45iou 0.45 控制相邻框的合并文字点选场景里两个字间距通常足够不用调太低。PaddleOCR 的 use_angle_clsFalse 跳过方向分类单字识别可以省一点耗时。如果候选文字是已知集合也可以不接 OCR直接用一个小分类网络或模板匹配课设里更可控。5. 避坑笔记文字点选课设最容易翻车的五个细节下面这些问题都是课设里实际见过、卡住半天以上的典型坑按“现象 → 原因 → 解决”写清楚训练前先看一遍能省不少时间。5.1 模型训练与推理阶段的三个坑坑一置信度阈值太低干扰线被识别成文字。现象检测结果里多了很多框把噪点和干扰线圈进去点击坐标自然就错。原因合成样本里的干扰线形态单一模型学到的是“有深色块就框”加上默认 conf 0.25 给了干扰线太多机会。解决训练时把干扰线改成随机曲线、随机粗细和颜色不要只用直线推理时把 conf 从 0.25 提升到 0.35~0.45并在验证集上扫一遍阈值选准确率最高的值。坑二训练图尺寸和推理图尺寸不一致坐标偏移。现象单张测试时看着没问题批量跑准确率骤降检测框和实际字位置错开。原因训练时 YOLO 内部统一缩放到 416推理时如果直接 cv2.resize 到 416 而不是 letterbox宽高比被拉伸文字变形坐标也对不齐。解决推理时严格使用 letterbox记住 r、dw、dh 三个量坐标回算时先减 padding 再除缩放比。这条解决了一半的坐标错位问题会消失。坑三合成样本里文字颜色太单一模型对白底黑字过拟合。现象在合成验证集上准确率 95%换成真实截图直接掉到 40%。原因合成代码里文字固定黑色背景固定浅色模型只是在学“深色是字浅色是背景”这种简单规则。解决合成时给文字加随机颜色抖动RGB 三通道各加减 30~50背景加渐变或噪声纹理同时收集 30~50 张真实截图做手工标注混合进训练集泛化能力会明显改善。5.2 坐标与交互阶段的两个坑坑四输出顺序不是提示顺序。现象文字框识别出来了但点击顺序错准确率按整单计算就是 0。原因很多代码把检测结果按 x 坐标从左到右排序但提示文字是乱序的“丙、甲、乙”必须按提示字符串顺序取坐标。解决先解析提示文字列表用识别结果建立“文字 → 坐标”的字典然后按提示顺序遍历字典。注意识别结果里可能有多余字或漏字遇到提示文字不在字典里时宁可跳过也不要乱序补位。坑五标注框只框住部分文字导致点击位置偏移。现象检测边框歪斜有的缺左上角有的缺右下角点击坐标偏出文字范围。原因合成脚本里文字尺寸计算有偏差手工标注时框得太紧导致模型学到的边框本身就紧。解决合成时在文字四周留 2~4 像素 padding统一边框生成逻辑对已有边框做等比膨胀 2 像素再把膨胀后的框用于裁图和计算中心点。还有一个相关细节角点坐标必须落在原图范围内越界会导致裁图失败。6. 给文字点选识别做可视化验证与评估课设验收时光有准确率数字不够评委更愿意看到“图上有框、有顺序、有轨迹”的直观演示。我习惯把最终输出做成一张标注图每个候选字画一个绿色矩形框框上方写识别出的文字点击顺序用黄色数字标在框中心再用一条折线把点击顺序连起来。这样三秒钟就能看出系统有没有跑通比一堆命令行日志有说服力得多。def draw_result(img, click_points, boxes): result img.copy() for i, (cx, cy) in enumerate(click_points): cv2.circle(result, (cx, cy), 5, (0, 255, 255), -1) cv2.putText(result, str(i 1), (cx 8, cy - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2) for (x1, y1, x2, y2) in boxes: cv2.rectangle(result, (x1, y1), (x2, y2), (0, 255, 0), 2) return result评估脚本也要写目的是量化“点选顺序完全正确”的比例而不是单个字对不对。判断标准很简单算法输出的坐标序列和真实坐标序列逐点距离小于 15 像素且顺序一致才算这一单通过。批量跑测试集输出整体通过率这份指标就是课设报告里的核心数据。轨迹动画可以加用模拟鼠标点击或画贝塞尔曲线动画都行属于加分项。我当年做课设时只顾着调模型最后演示脚本连顺序标注都没做评委根本看不出点选逻辑白白丢了印象分。后来花一晚上补了可视化效果立竿见影。做这个方向先跑通最小闭环再逐步加参顺序对了坑踩完了剩下的都是时间问题希望帮到你。本文还有配套的精品资源点击获取
返回列表