
简介面向需要完成文字点选验证码识别课设或研究Python图像识别应用的开发者本资源提供一套完整可运行的选字验证码识别方案。模型基于小样本训练仅用300张验证码即可达到96%准确率单次识别耗时约100~300毫秒在1核2G低配置服务器上也能流畅运行已通过Windows下Python3.6、3.8、3.10版本测试。压缩包共48个文件总体积约122MB包含19个Python源码、4个pyd编译模块、2个模型权重bin文件、多张png/jpg示例图片及说明文档等。py文件覆盖数据预处理、模型训练、API推理与演示脚本png/jpg/gif用于展示识别效果与界面txt/md提供使用说明与项目介绍目录划分了src、utils、static、docs等模块结构清晰便于快速定位和二次开发。已有437人学习下载适合作为课设参考、学习文字定位与识别方法也可直接部署到实际业务中。1. 这个课设到底在做什么点选验证码识别的技术闭环如果你在Python课设里拿到“点击选择文字验证码识别”这个题目大概率看到的是“请按顺序点击安全验证”那张图——背景有波浪纹、字体有扭曲甚至整行字是歪的。这种验证码和我们熟悉的数字字母验证码最大的差别在于它要的不是“识别出一个字符串”而是“识别出图片里每个文字的坐标并按顺序模拟点击”。把这个过程拆开就是图像预处理、文字检测、坐标映射、浏览器事件模拟一条链。这个课设很适合用来练OpenCV、OCR和Selenium因为每一环都有能看到的输出且翻车点非常多。下面我会顺着一条可以直接交差的路给出完整方案先讲选型理由再给能跑的代码最后把最容易卡住的四个坑讲透。2. 拆解点选验证码的技术栈预处理、OCR选型与完整链路2.1 点选验证码的常见形态与识别难点这类验证码常见有“文字点选”和“文字选字”两种。文字点选通常是一张大图里散布着若干汉字提示区写着“请点击登录”“请点击验证”文字选字则是把几个字和十几个干扰字混排要求按特定顺序点选。不论哪种核心都是“先识别文字内容再定位文字中心点”。难点来自三处一是背景干扰波浪线、雪花噪点、渐变阴影都会影响二值化二是字体虽然来自统一字体库但随机旋转和拉伸会让OCR误判三是点击顺序必须和提示一致OCR返回的顺序往往不能直接用。课设里最容易踩的就是把验证码当成普通字符验证码直接OCR输出字符串然后拿去匹配结果发现没有坐标也没法点击。正确思路是把识别结果当成一组带坐标的文本对象再按提示词重新排序。2.2 图像预处理去噪、二值化与颜色过滤图像预处理的目的不是让图片变漂亮而是让文字和背景在颜色分布上能分开。点选验证码的背景通常和文字颜色不同比如背景是浅蓝色波纹、文字是深灰色这时先用HSV颜色过滤比直接灰度更有效。以下是我课设里常用的预处理函数import cv2 import numpy as np def preprocess(image_path): img cv2.imread(image_path) if img is None: raise FileNotFoundError(f图片读取失败: {image_path}) # 转HSV后做颜色范围过滤深色文字的灰度值大致在0~200之间 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, (0, 0, 0), (180, 255, 200)) # 对原图灰度再按mask取交集背景的干扰线会被去掉 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.bitwise_and(gray, gray, maskmask) # 反向二值化让文字变成白色、背景变黑便于后续轮廓检测 _, thresh cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY_INV) # 开运算去小噪点核太大会把浅色文字的边缘也吃掉 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) thresh cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) return thresh这段代码里最重要的是inRange的两个参数。(0, 0, 0)到(180, 255, 200)表示的并不是固定颜色而是“饱和度不限、亮度小于200”的深色区域适合大多数深色文字验证码。如果你的验证码是白色文字就把inRange改成(0, 0, 200)到(180, 255, 255)同时把THRESH_BINARY_INV换成THRESH_BINARY。kernel大小也要跟着调文字笔画细的用(2, 2)粗的用(4, 4)千万别用(10, 10)那种大核会把“横折钩”拆断。预处理做完之后把二值图交给OCR识别率通常能提升好几个点。我见过直接拿原图跑PaddleOCR然后抱怨误识别的情况多半是漏了这一步。2.3 OCR引擎选型Tesseract、PaddleOCR、ddddocr怎么选选型直接决定你能不能按时交差。Tesseract的优点是轻、离线可用但中文识别需要额外下载chi_sim语言包且对扭曲汉字的效果一般课设里如果没有网络环境会卡在安装上。ddddocr 这类验证码专用库对纯字符串验证码很友好一行代码就能输出结果不过它对“带坐标、多文字散布”的点选图支持有限更适用于滑块缺口或普通字符验证码。PaddleOCR 是这几个里对“检测识别”支持最完整的它既能给出每个文字的四点坐标也能给出识别文本和置信度正好满足点选验证码的两个需求。装完依赖之后记得在PyCharm的Settings里确认当前Project的Python Interpreter是同一个环境不然代码里import paddleocr会直接报ModuleNotFoundError。以下是我常用的安装方式pip install paddlepaddle paddleocr opencv-python如果机器没有NVIDIA显卡不要额外装CUDA版本CPU版就能跑只是识别一张图要多等一两秒。在使用PaddleOCR时建议把show_log关掉不然终端会被调试日志淹没from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse)注意这里用的是PaddleOCR 2.x的API如果你的环境里装的是3.x返回格式会从“列表套列表”变成字典结构解析方式不一样。我的建议是课设环境固定安装2.x版本因为网上的教程大多按2.x写遇到问题好查。上述代码会加载方向分类器和中文识别模型第一次运行会下载模型文件大概一两百兆建议提前跑通不要在答辩当天现场下载。3. 文字检测与坐标映射把“图里的字”变成鼠标可点的坐标3.1 用PaddleOCR检测文字位置并提取候选词拿到预处理后的图片下一步是让OCR输出“每个字的位置”。PaddleOCR的ocr.ocr()返回结果里每一项是一个包含[box, (text, score)]的对象box是四个角点的坐标text是识别的文本score是置信度。直接写一个解析函数import json def parse_ocr_result(result): items [] # PaddleOCR 2.x 返回的是 [[box, (text, score)], ...]最外层还有一层列表 for line in result: if line is None: continue for box, (text, score) in line: xs [point[0] for point in box] ys [point[1] for point in box] item { text: text, score: float(score), cx: int(sum(xs) / 4), cy: int(sum(ys) / 4), box: box, } items.append(item) return items这个函数的逻辑很简单四个角点横纵坐标分别取平均得到文字框的中心点。后面模拟点击时用的就是cx和cy。解析完后可以先打印出来看一眼确认识别结果里有没有混入背景文字。3.2 从OCR结果中过滤干扰项点选验证码的提示词通常写在图片右侧或上方例如“请依次点击安全 验证 登录”。OCR会把提示词也当成图片里的文字识别出来如果不过滤点击时会多出很多错误坐标。过滤策略分两步先把置信度低于0.5的项丢掉再把和候选词列表无关的项丢掉。下面是匹配函数def filter_candidates(items, keywords): result [] for item in items: if item[score] 0.5: continue text item[text].strip() # 点选验证码一般只包含汉字过滤掉字母、数字和符号 pure .join(c for c in text if \u4e00 c \u9fff) if not pure: continue # 用“包含”而不是“相等”因为OCR偶尔会多识别一个偏旁或空格 if pure in keywords or keywords in pure: result.append(item) return result这里用“候选词包含在识别文本里”而不是“完全相等”是因为OCR有时会把“验证”识别成“验证一”或“验证”多一个偏旁或符号更常见。keywords可以直接从验证码页面上抓取比如用Selenium读取提示元素的文字。如果拿不到提示词就退化成“把所有置信度高的候选按左上到右下排序”但那样成功率会低不少。3.3 把识别结果映射到原始图片坐标PaddleOCR返回的坐标是原始图片的像素坐标。如果验证码图片通过浏览器缩放显示直接点击必然偏移。常见的网页会把一张400×200的验证码显示成300×150这个比例变化必须换算回去。我习惯先获取图片原始尺寸和显示尺寸再统一到一个函数里处理def scale_coords(item, src_w, src_h, disp_w, disp_h): return { cx: round(item[cx] * disp_w / src_w, 1), cy: round(item[cy] * disp_h / src_h, 1), }src_w和src_h来自图片文件本身在浏览器里对应naturalWidth和naturalHeightdisp_w和disp_h是元素实际显示宽度。如果你用PIL读图可以用Image.open(...).size用Selenium则直接读属性。比例换算看似简单却是课设里最容易翻车的地方因为不少网页还会给图片加边框和内边距点击基准要额外再加一次元素偏移。4. 模拟点击与答题逻辑怎么让验证码真正通过4.1 构造点击顺序从文本匹配到坐标排序验证码要求“按提示顺序点击”所以不仅要找到文字坐标还要按顺序生成点击序列。假设提示词是“安全 验证 登录”正确的点击顺序就是它们的排列顺序。这里有一个技巧先把提示词拆成列表再用每个词去匹配过滤后的候选框匹配失败的文字要给出明确提示不要静默跳过。def build_click_sequence(candidates, prompt_words): seq [] for word in prompt_words: matched None for cand in candidates: if cand[text] word: matched cand break if matched is None: print(f警告: 未匹配到 {word}) else: seq.append({text: word, cx: matched[cx], cy: matched[cy]}) return seq如果OCR结果里文字本身没问题只是顺序乱这个方法能保证输出顺序和提示一致。但现实中OCR经常把“安”识别成“口”或“女”这时需要调整过滤逻辑对单个汉字的点选优先做“字符包含匹配”而不是“词匹配”比如word in matched_text or matched_text in word。另外很多点选验证码的提示词本身就是图片里的一个词比如提示“点击验证”图片里可能既有“验”和“证”两个单字也有“验证”一个词这时要优先匹配词其次匹配单字避免重复点击。4.2 用Selenium驱动浏览器并点击坐标算好之后剩下的就是模拟点击。我一般用Selenium因为课设环境兼容性最好。核心是拿到图片元素的位置再相对于图片左下角移动鼠标点击。from selenium.webdriver import Chrome, ChromeOptions from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains options ChromeOptions() # 无头模式适合批量测试但部分验证码页面会检测无头环境自行决定是否开启 # options.add_argument(--headless) browser Chrome(optionsoptions) browser.get(https://example.com/login) img browser.find_element(By.ID, captcha_img) src_w int(img.get_attribute(naturalWidth)) src_h int(img.get_attribute(naturalHeight)) disp_w img.size[width] disp_h img.size[height] for step in click_seq: target scale_coords(step, src_w, src_h, disp_w, disp_h) ActionChains(browser).move_to_element_with_offset( img, target[cx], target[cy] ).pause(0.2).click().perform()move_to_element_with_offset的基准点是元素左上角所以传入的偏移不需要再加图片在页面里的绝对位置。pause(0.2)是为了模拟人类点击间隔很多验证码会检测点击间隔是否过度均匀或过快。如果点击后验证码还是刷新先检查坐标有没有落在文字中心其次检查点击速度。4.3 给课设加一个Web演示界面课设答辩时不能只给别人看命令行输出最好有一个本地Web页面上传验证码图片就能显示识别结果和点选坐标。用Flask写这个界面很快一个.py文件加一段HTML模板就够from flask import Flask, request, render_template_string from paddleocr import PaddleOCR import cv2, os app Flask(__name__) ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) UPLOAD_DIR ./uploads os.makedirs(UPLOAD_DIR, exist_okTrue) HTML form methodpost enctypemultipart/form-data input typefile nameimg button typesubmit识别/button /form {% if items %} table border1 trth文字/thth置信度/thth中心点/th/tr {% for item in items %} trtd{{ item.text }}/tdtd{{ item.score }}/tdtd{{ item.cx }}, {{ item.cy }}/td/tr {% endfor %} /table {% endif %} app.route(/, methods[GET, POST]) def index(): if request.method POST: f request.files[img] path os.path.join(UPLOAD_DIR, f.filename) f.save(path) result ocr.ocr(path, clsTrue) items parse_ocr_result(result) return render_template_string(HTML, itemsitems) return render_template_string(HTML, itemsNone) if __name__ __main__: app.run(debugTrue)这个演示界面最大的价值是让你在答辩前用真实截图批量验证识别和坐标映射是否正确。很多细节问题比如“某个字总是识别成另一个字”通过网页反复传图很快就能看出来比命令行一遍遍跑要直观得多。生产环境不要开debugTrue但课设演示可以方便改完代码自动重载。5. 避坑文字点选验证码识别中我踩过的四个坑5.1 OCR把背景纹路识别成文字现象解析出来的候选列表里有大量无意义字符比如“·”“了”“一”这种高频误判点击时直接多点了背景。原因验证码背景里的波浪线、雪花噪点在灰度图里的颜色接近文字二值化后形态和汉字笔画相似OCR模型把部分纹路当成文字检测框。解决在预处理里加上HSV颜色过滤只保留文字颜色范围。如果背景和文字颜色太接近就改用“颜色差异增强”计算背景主色调把与主色调距离较远的像素保留其他的置为白色。核大小不要设得太大否则会把文字和背景粘连。另一个有效手段是过滤掉面积过小的检测框因为单个汉字的最小尺寸是有限度的。5.2 文字旋转导致识别乱码现象OCR把“验”识别成“金”或“又”置信度也不高关键词匹配失败。原因这类验证码的文字随机旋转角度PaddleOCR的方向分类器对椭圆包围盒支持不够旋转超过30度时检测框虽然还在但识别头吃不到完整笔画。解决在预处理阶段用OpenCV的minAreaRect对检测到的每个文本区域做旋转校正把文字转正后再喂给OCR。如果没有精力做旋转校正最低限度是把use_angle_clsTrue打开并适当放大图片因为小尺寸下旋转后的汉字信息损失更严重。我试过把图片用cv2.resize放大到原来的2倍识别率能提升不少。5.3 点击坐标偏了一个身位现象中心点算出来了点击位置总是偏上或偏左验证码提示“点错了”。原因最常见的不是OCR坐标算错而是网页里的图片被CSS缩放。PaddleOCR返回的是原图像素坐标而move_to_element_with_offset用的是浏览器显示坐标两者没有换算。除此之外图片外层如果有border或padding元素左上角已经包含了这部分偏移但鼠标偏移里没有再加。解决统一用naturalWidth/naturalHeight和img.size做比例换算并且在点击前先用浏览器打开一张已知位置的图片测试坐标是否准确。如果图片是背景图而不是img元素就要先拿到背景图所在的div的坐标再用背景图的实际尺寸和CSS尺寸比进行换算。5.4 答题顺序出错导致验证码刷新现象所有文字都能识别出来坐标也对但点击完三个字后验证码马上刷新提示失败。原因验证码要求按照提示词顺序点选但你按OCR输出的顺序点。OCR输出顺序是从上到下检测到的和验证码设定顺序基本无关。尤其当验证码要求“先点右下角再点左上角”时按检测顺序点必然全错。解决先从页面里抓取提示词比如“请依次点击登录 验证 安全”拆成列表按列表顺序找到对应文字框再点击。抓不到提示词时退化为“从左到右、从上到下”排序但成功率会明显下降。更高阶的做法是把提示词的顺序也交给模型理解但课设阶段用文本匹配就够。点击间隔建议在0.2到0.5秒之间随机过快的机械化点击会被风控识别。6. 让课设更完整加一个批量验证脚本与四个参数调优建议最后一章的落点放在“如何证明你的方案真的可用”。课设答辩最怕的是演示时现场翻车。我建议你把验证码截图分成三组同源背景不同文字、不同背景同字体、带旋转文字每组准备十张图写一个批量验证脚本统计“识别率”和“点击成功率”。识别成功指所有提示词都找到了坐标点击成功指验证码通过。批量验证其实很简单循环遍历文件夹里的图片调用预处理和OCR用提示词匹配判断结果把失败图片单独存到一个目录里。跑过一轮之后统计出来的数据比口头解释“识别率很高”有说服力得多。针对失败样本再调整下面四个参数这是我试下来影响最大的参数位置建议值影响use_angle_clsTrue方向分类器对倾斜文字很关键能提升旋转场景的识别率置信度过滤阈值0.5~0.6过滤低置信度结果太低会混入背景噪点太高会丢掉有效字预处理kernel大小(3, 3)或(5, 5)小核保留细节大核去噪文字笔画细的用前者图片缩放倍数2倍旋转小字放大后再识别准确率提升明显代价是耗时变长如果你用的是PaddleOCR 2.x部分版本的ocr.ocr直接支持传drop_score如果提示参数错误就在解析结果时按上面的置信度阈值自己过滤。另一个能让答辩更出彩的技巧是把识别失败的文字截图存下用cv2.imwrite单独放一个目录最后总结时直接展示“失败原因是旋转过大”比空谈“需要调参”更有技术含量。我自己做这个课设时最头疼的就是坐标映射第一次演示时所有文字都识别对了但怎么点都提示“请正确点击”后来才发现是CSS缩放导致偏移。从那次以后我养成了一个习惯所有涉及坐标的功能先打印原始坐标和缩放后的坐标用一张图校准后再上整套流程。希望帮到你。本文还有配套的精品资源点击获取