Python+OpenCV实现游戏验证码自动识别:完整CV工程链路 梦幻西游互通版里那套“四小人成语”验证码再加上时不时蹦出来的“漂浮字”验证码估计劝退了不少老玩家。每天上线做个日常、领个礼包碰到这种验证码就得盯着屏幕猜成语遇到漂浮字更是考验眼力。我最初做这个自动打码工具纯粹是被反复手动输入搞烦了又想把图像识别这套东西落到一个真实场景里跑一遍。项目做下来其实就是一条完整的“截屏 — 图像预处理 — 模型推理 — 自动填字”链路本质上是目标检测、图像分类、OCR 这些通用计算机视觉技术的组合应用不是黑科技。如果你也在玩这个游戏或者对验证码识别、OCR 这类方向感兴趣这篇文章应该能给你一个可复用的思路。读者需要一点 Python 基础懂 OpenCV 更好即便刚入门照着思路一步步拆也能明白每一环节在干什么。1. 先搞明白要识别的是什么东西1.1 四小人成语验证码动作组合成词这类验证码的呈现形式很固定画面中间四个格位各放一个 Q 版小人小人姿势各有不同整体组合起来指向一个四字成语。比如一个小人作拉弓状、另一个小人作奔跑状合起来可能就是在暗示某个跟“射”或“追”相关的成语。玩家的任务就是把猜到的成语四个字准确输进输入框。这里有个关键点它本质上不是“文字识别”而是“图像语义理解”。四个小人的动作、朝向、服饰、背景都可能是干扰项同一个成语在不同刷新条件下小人的颜色、位置、大小都可能变化。你要是想靠预先穷举成语库或者简单模板匹配硬吃基本走不通因为成语题库动辄几千条动作组合的变化量更是远超静态模板能覆盖的范围。1.2 漂浮字验证码动与不动之间漂浮字验证码和四小人完全是两种风格。它的字符会在验证码区域内不规则漂移有的版本是整个字符串一起平移有的版本是每个字独立抖动。字体偏艺术化笔画粗细不均还常常叠一层半透明花纹背景。更麻烦的是文字与背景的对比度有时极低肉眼看着都费劲更不用说程序去分割字符。这种验证码的难点不在“看不懂字”而在“找不到字在哪”。因为字符位置不固定、字体形变大传统 OCR 的版面分析逻辑在这里基本失效。你得先解决“文字区域在哪里”的定位问题再解决“这段文字是什么”的识别问题。两步分开做每一步都有对应的成熟工具可以选。1.3 “自动打码”在整个流程里到底做什么行业里把人工识别验证码叫“人工打码”机器自动识别就叫“自动打码”。我这个项目做的事就是把玩家手动完成的“看图、理解、输入”三步替换成程序自动完成。具体到流程大概是这样的程序先截取游戏画面中验证码所在区域做一轮图像预处理然后分流进入两个识别模块——四小人走图像分类漂浮字走目标检测加 OCR。识别出结果后先做置信度校验再通过模拟键盘把答案填进去点击提交。如果提交失败或者验证码刷新就自动重新来一轮。整个过程不依赖任何外部打码平台完全本地离线运行。这套流程真正的价值在于它是一个完整的计算机视觉工程闭环有数据采集、有模型训练、有参数调优、有工程部署。验证码本身只是载体背后的方法论可以迁移到很多其他图像识别场景。2. 技术选型为什么我没用现成 OCR 库草草了事2.1 传统 OCR 在这类验证码面前为什么不行很多人第一反应是直接调 Tesseract 不就行了我最早也是这么试的结果相当惨。Tesseract 这类传统 OCR 引擎对印刷体、版面规整的文档效果好但对艺术字体、扭曲形变、低对比度背景的鲁棒性非常差。四小人更不用说了它根本不是文字你把整张图丢给 Tesseract它连个字符都检不出来。模板匹配也试过。对四小人验证码来说如果每格小人都是固定姿态、固定背景matchTemplate 确实能跑通。但实际刷新时小人会有旋转、缩放、遮挡、颜色偏移模板一多匹配速度就上来了准确率还直线下降。我自己的实验里模板匹配在 50 张测试图上只有六成出头的准确率而且一旦碰到半透明背景就完全崩盘。结论很明确这类非标准化的视觉输入必须走数据驱动路线靠模型去学特征而不是靠人手工定义规则。2.2 为什么主线走深度学习深度学习方案最大的优势是鲁棒性。一个训练好的图像分类模型对颜色偏移、旋转、遮挡的容忍度远比手工特征高。尤其是 YOLO 这类目标检测模型定位漂浮字区域这种任务几乎是量身定做比传统轮廓检测稳定一个量级。另一个优势是部署成熟度。现在不管是 PyTorch、ONNX Runtime 还是 PaddleOCR都能很轻松地把模型集成到本地脚本里不需要服务器不需要 GPUCPU 上跑一个小模型也只要几百毫秒。对个人工具来说这个体验是完全可接受的。不过我也不是把所有环节都交给深度学习。图像预处理仍然保留了大量传统 CV 的操作灰度化、二值化、形态学去噪、帧差法提取运动区域。这类操作计算量小、可解释性强在深度学习之前把图像质量拉上去能明显降低模型的识别压力。传统和深度学习的组合才是这个项目能跑稳的关键。2.3 两条识别链路的架构设计四小人成语识别我设计了两种路线对比。第一种是“整体分类”把四个小人连在一起的完整截图直接缩放成固定尺寸扔进一个多分类 CNN 模型输出直接就是成语类别。这种做法最简单一个模型搞定全部但需要每个成语都有足够多的样本来覆盖视觉变化数据成本较高。第二种是“逐格分类 成语映射”先用工具把四个小人分别切开对每个小人单独做动作分类再把四个动作组合结果拿到成语映射表里查。这种做法的好处是灵活同一套动作分类器可以组合出大量成语理论上数据成本更低坏处是误差会累积单格分类错一个整个成语就废了。我最后实际采用的是整体分类为主、逐格分类为辅的混合方案。主链路用整体分类保证速度当置信度偏低时再用逐格分类做二次验证。实测下来准确率能稳定在九成上下速度也就多花几十毫秒。漂浮字链路则是“定位 识别”两段式先靠帧差法或者 YOLO 把文字区域框出来再把裁剪后的文字图交给 OCR 模型做序列识别。上一步解决了“字在哪”下一步解决“字是啥”逻辑清晰出了问题也好排查。3. 核心模块落地每一步的参数和代码3.1 截屏与区域定位先拿到一张干净的图第一步是截屏这一步没做好后面全白搭。我最开始直接用 pyautogui.screenshot() 截全屏再靠坐标把验证码区域抠出来。这个方法在固定分辨率下没问题但换一台高分屏机器坐标就全偏了。这里有一个特别容易踩的坑Windows 的 DPI 缩放。如果你的系统缩放是 125% 或 150%pyautogui 拿到的坐标和 OpenCV 处理图像时的坐标会对不上出现“明明截图里有验证码但裁剪区域全是空白”的诡异现象。解决办法是在程序最开头调用一次 DPI 感知设置让坐标逻辑不被系统缩放干扰。import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(1)区域定位上我最终没有用固定像素而是把验证码面板在游戏窗口中的相对位置记录下来再根据当前窗口尺寸换算成绝对坐标。要做到这一点得先拿到游戏窗口的句柄和矩形区域再把验证码面板中心在窗口中的比例固定下来。这样窗口大小变了坐标也会跟着变不用每次重新标定。import pyautogui import win32gui hwnd win32gui.FindWindow(None, 梦幻西游互通版) left, top, right, bottom win32gui.GetWindowRect(hwnd) # 验证码面板中心相对窗口位置比如横向 0.5、纵向 0.42 panel_x left int((right - left) * 0.5) panel_y top int((bottom - top) * 0.42) img pyautogui.screenshot(region(panel_x - 200, panel_y - 100, 400, 200))3.2 图像预处理把干扰降到最低拿到截图后第一件事不是直接送进模型而是先做一轮预处理。我的标准流水线是灰度化 → 高斯模糊 → 大津法二值化 → 中值滤波 → 形态学开运算。import cv2 import numpy as np frame cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) _, binary cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) binary cv2.medianBlur(binary, 3) kernel np.ones((3, 3), np.uint8) clean cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)这里几个参数值得说明。大津法(OTSU)会自动计算全局最优阈值比手工定一个固定阈值稳妥得多因为验证码背景深浅每次都在变。中值滤波对椒盐噪声特别有效验证码上的细碎噪点基本都是这种类型。开运算用 3x3 的核能去掉独立的小亮点但不至于把文字笔画也腐蚀掉。如果你遇到文字本身有断笔的情况可以把开运算换成闭运算或者核放大到 5x5。闭运算能把断开的笔画连上代价是可能把相邻字符粘在一起。具体用哪个得看你采集到的实际图片字符密度。3.3 四小人成语识别整体分类还是逐格分类先说整体分类的实现。我用的是 ResNet18预训练权重加载后把最后一层全连接改成成语类别数。输入是 224x224 的 RGB 图归一化参数用 ImageNet 的标准均值和方差。训练时每个成语类别至少准备 60 到 100 张图包含各种背景下的小人组合再叠加随机旋转、平移、色调抖动、随机遮挡等增广操作。模型推理的代码框架大概是这样import torch from torchvision import models, transforms model models.resnet18(pretrainedTrue) model.fc torch.nn.Linear(model.fc.in_features, num_idioms) model.load_state_dict(torch.load(idiom_model.pth)) model.eval() transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) with torch.no_grad(): prob torch.softmax(model(transform(clean).unsqueeze(0)), dim1) confidence, pred prob.max(dim1)训练时我踩过一个坑迁移学习的最初阶段如果直接把整个模型一起微调很容易过拟合。正确的做法是先把最后一层单独训练几个 epoch再解冻前面的层用更小的学习率整体微调。我最后用的是这个策略验证集准确率从 78% 提到 92% 以上。逐格分类这边如果不想额外训练检测模型最简单的方式是按验证码面板的固定网格坐标直接切图。因为四个小人的格位分布相对规整切出来后再对每个格位单独分类。我用来做二次验证的“动作分类器”就是这种结构切图 → 缩放 64x64 → 小型 CNN → 动作类别。3.4 漂浮字识别的完整链路漂浮字如果是动态漂移有一个非常经典且高效的预处理手段帧差法。连续取三帧画面分别做差再对两个差分结果取交集就能把静止背景去掉留下运动中的文字区域。这个思路对“只有文字在动、背景基本不动”的验证码特别有效。diff1 cv2.absdiff(frame1, frame2) diff2 cv2.absdiff(frame2, frame3) motion cv2.bitwise_and(diff1, diff2) gray cv2.cvtColor(motion, cv2.COLOR_BGR2GRAY) _, mask cv2.threshold(gray, 25, 255, cv2.THRESH_BINARY) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8))为什么对两个差取交集而不是直接用一个差因为单帧差会把所有发生变化的像素都标记出来偶尔背景里的某个光点闪一下也会混进来取交集能大幅过滤掉这类偶发噪声。这是我在调参过程中发现效果最好的做法。如果漂浮字是静止的帧差法就没意义了。这时候直接用 YOLO 检测文字区域。我用的是 YOLOv5 的小模型自己标了大概两千张验证码截图文本区域用矩形框标注就行不需要逐字标。检测出来之后裁剪文字区域再交给 OCR 模型识别。OCR 环节我直接用了 PaddleOCR 的中文模型。它对艺术字体和低对比度图像的容错比 Tesseract 好太多而且中文识别准确率有保证。调用也很简单from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch) result ocr.ocr(cropped_img, clsTrue)不过 PaddleOCR 直接吐出来的结果偶尔会带全角空格或误识别字所以我在后面加了一层词表纠错把所有识别候选和预设成语词表算编辑距离取距离最小的成语作为最终输出。这一小步把漂浮字的准确率又拉高了 5 个百分点左右。3.5 结果回填与提交耐心比手快重要识别完成后的自动填字看起来简单但实际上也有讲究。直接用 pyautogui.typewrite() 一口气输完四个字实测经常出现丢字或者输入框没聚焦导致没打进去的情况。我后来改成三步走先点击输入框区域让焦点落进去再逐字输入每字间隔随机延时输入完成后检查一下输入框内容是否等于预期结果不等就清空重输。import pyautogui import time def fill_answer(answer, input_pos): pyautogui.click(input_pos) time.sleep(0.2) for ch in answer: pyautogui.write(ch) time.sleep(0.05 random.uniform(0.02, 0.08))点击提交之后不能立刻判断成功因为验证码系统有延迟。我采用了轮询机制提交后隔两秒截一张图检查验证码区域是否变化。如果还是同一个成语就说明提交失败或者还在等待如果刷新成新题就进入下一轮循环。每轮最多重试三次超过次数就放弃本单避免无限死循环。延时这块还有个小技巧不要用固定延时每次操作之间加一点随机波动。这样既不会因为系统过快判定异常也不会因为长时间无动作被判定超时。算上模型推理时间我的完整流程平均 3 秒内能完成一轮识别加提交。4. 实测效果准确率、耗时的真实数据4.1 我跑出来的指标长这样离线测试样本来自我本地收集的历史截图四小人成语约 800 张漂浮字约 600 张覆盖不同背景、不同清晰度。测试结果如下验证码类型样本量识别准确率平均单轮耗时四小人成语清晰背景50092.6%0.9 秒四小人成语复杂背景30084.3%1.3 秒漂浮字静态30088.0%1.1 秒漂浮字动态30082.7%1.6 秒这些数字是在我本地环境下的实验结果不同游戏版本、不同分辨率、不同截图工具都会影响结果仅供参考。但整体趋势很清楚背景越复杂准确率越低动态漂浮字本来就比静态的难。对比人工输入的话正常人手看加打字一个成语少说也要 5 到 8 秒遇到难认的漂浮字可能盯着看十几秒还打错。自动识别把时间压到 2 秒以内准确率也在可接受范围内。这个提升对每天要过几十次验证码的使用场景来说体感差距非常大。4.2 哪些场景下识别会明显变差测试中最容易翻车的情况有三种。第一种是小人被半透明特效遮挡。游戏里某些场景特效会在验证码面板上叠加淡色光晕小人轮廓变得模糊整体分类模型容易把“拉弓”误判成“挥手”之类相近动作。第二种是漂浮字颜色和背景过于接近。比如浅灰色文字压在白色云纹背景上帧差法就算提取出运动区域文字笔画也断成一截一截OCR 后续完全没法拼出正确内容。第三种是验证码面板尺寸缩放。游戏窗口缩小到手机互通模式时整个面板会被压缩小人细节丢失明显准确率能掉到七成以下。面对这些情况我的处理方式是加置信度门槛。模型输出的置信度低于 0.6 时宁可放弃这一轮重新刷新也不盲目提交。实测下来强制提交低置信度结果的成功率不到 30%而主动刷新一次再识别成功率能回到 80% 以上。这个取舍非常重要。5. 踩坑记录与排查技巧5.1 数据层面的坑这项目最大的坑不在模型在数据。我第一次训练四小人分类器时只收集了大概 200 张截图结果验证集准确率虚高一到真实环境立刻打回原形。原因很典型截图都是连续几天在同一台机器上采集的背景、颜色、位置高度一致模型学到的其实是背景特征而不是小人动作特征。解决方法是两个方向同时做。一是换不同分辨率、不同画质设置去采集人为制造多样性二是做增广时加上随机背景替换把模型对背景的依赖打散。实测增广策略改变之后真实环境的准确率直接提升了 10 个百分点。还有一个容易被忽略的点成语样本分布极度不均。热门成语出现频率高冷门成语可能几百张截图都见不到一次。如果不处理模型对冷门类别的识别能力就形同虚设。我最终通过合成数据把样本数拉平拿干净模板加随机旋转、缩放、加噪、换背景每个成语合成到 100 张以上。5.2 工程环境层面的坑截屏黑屏这个问题折腾了我最长时间。在部分电脑上游戏客户端会拦截常规的屏幕捕获 APIpyautogui 截出来的图全是黑的。解决思路有几种用 win32 的 BitBlt 走硬件加速路径或者改用显卡采集方式。但这些方案代码复杂度高还受显卡型号影响。我的终极解法是“提前测截屏”程序启动时先截一张判断像素方差如果检测到全黑图就直接报错退出避免后续流程跑了一堆无效操作。DPI 缩放问题前面已经提过。另一个很常见的坑是Windows 上同时插了多个显示器且缩放比例不同pyautogui 和 OpenCV 的坐标计算会乱套。我后来的做法是固定只在主显示器上运行游戏窗口并且把布局设置统一成 100% 缩放测试稳定后再考虑其他环境。模型推理速度也是一个实际问题。四小人分类用 ResNet18 在 CPU 上单次要 300 毫秒左右漂浮字 YOLO 检测加 OCR 加起来接近一秒。如果想提速可以把模型导出成 ONNX 格式在 ONNX Runtime 上跑速度能有 20% 到 40% 的提升再激进一点就用 INT8 量化但准确率会牺牲一个点左右。5.3 排查技巧速查表现象可能原因解决方案截屏全是黑色客户端拦截截屏接口换采集方式或启动时自检报错坐标偏移严重系统 DPI 缩放启动时强制 DPI 感知统一缩放比率识别结果多空格/全角符号OCR 后处理不足加字符清洗和词表纠偏模型准确率虚高但实测暴跌训练数据分布单一换环境采集加背景替换增广输入框丢字焦点未落在输入框先点击输入框再逐字输入提交后无限等待结果判定逻辑缺失轮询截图检测验证码是否刷新6. 关于自动识别验证码的边界问题验证码设计的本意就是区分人和机器。自动识别验证码本质上确实属于 CAPTCHA 绕过的研究范畴这一点没有必要粉饰。技术研究本身没有原罪但用在哪里、怎么用差别很大。我在这个项目里严格限制使用范围只在自己可控的测试账号上做离线实验只在本地跑不对外提供打码服务不做批量刷取。如果你也想复现这个项目建议把同样的话记在脑子里。批量登录、批量抢资源、把工具打包成服务出售这些行为既违反游戏平台用户协议也可能触发账号风控乃至面临法律风险完全得不偿失。从技术学习的角度看这个项目其实是很好的 CV 入门到进阶的完整练习数据采集、数据清洗、模型训练、模型部署、工程调优每个环节都能学到东西。做这类项目时保持克制把它当作一个“图像识别技术实验”而不是“薅游戏资源的工具”心态会完全不同最后获得的成长也会更多。我个人做完这个项目最大的体会是识别模型反而不是最难的一环真正耗时间的是把“脏数据”变干净、把不同机器环境下的坐标和截屏差异抹平。后续如果你也想尝试类似工具建议先把数据采集管线打磨稳再碰模型顺序别反了。最后再分享一个小技巧模型先离线用历史截图批量评测准确率稳定了再放到线上不然反复试错很容易被验证码系统盯上。这套框架除了验证码场景也能迁移到表单票据识别、游戏截图信息提取等常见 OCR 任务上投入产出比其实挺高的。