从拖延到落地:我的第一个 PyTorch + YOLOv8 验证码点选识别实战
其实很久之前,我就对 PyTorch 挺感兴趣的,也一直琢磨着搞个目标检测或者验证码识别的项目来练练手。但拖延症大家懂的,这个计划在我的收藏夹和 ToDo List 里静静躺了快一年,每次都以“最近太忙”或者“环境配置太麻烦”为借口搁置了。
直到前阵子,看到一个需要频繁人工点击的点选类验证码(就是那种给你一张大图和几个提示字,让你按顺序在图里把对应图标点出来的验证码),我终于下定决心:是时候把这个坑给填上了。
折腾了两周,踩了无数坑,成功把模型训练出来,并在本地部署了 FastAPI 推理接口,回测成功率达到了 90% 以上。在这里做个完整的复盘,记录一下这个从无到有的过程、踩过的坑以及心得体会。
思路与架构设计
点选类验证码的识别,核心在于解决两个问题:
- 图标在哪里?(目标检测问题:把大图里的候选图标全部框出来)
- 哪个图标对应哪种颜色或文字?(特征分类问题:对框出来的图标进行颜色或类别判定,决定点击的先后顺序)
经过一番调研,我设计了如下的工作流:
- 数据采集:通过 API 从某测试平台并发抓取原始的验证码图片和点击顺序指令。
- 数据标注:因为人工画框太痛苦,我先利用图像处理(HSV 色彩过滤)搞了一套自动标注脚本,再配合
X-AnyLabeling进行微调和清洗,事半功倍。 - 模型训练:使用目前非常成熟且开箱即用的 YOLOv8(Ultralytics)进行目标检测,训练出能精准框出大图中所有小图标的模型。
- 模型导出:把 PyTorch 的
.pt权重导出为.onnx格式,不仅能脱离 PyTorch 的庞大依赖,还能利用 ONNX Runtime 实现毫秒级的推理响应。 - 颜色匹配推理:YOLO 框出图标坐标后,我们利用 HSV 空间的颜色直方图特征做颜色匹配,对提取出来的图标和目标指令进行比对,最终输出精准的点击顺序坐标。
- API 服务部署:基于 FastAPI 搭建 HTTP 识别服务,实现秒级点选坐标输出。
从项目结构上看,我最后把它拆成了几个比较清晰的模块:
Collector:负责采集验证码图片和提示图。TrainerAgent:负责 YOLOv8 训练、指标提取和最佳权重管理。CaptchaInferencer:负责 ONNX/PT 模型加载、图标检测、颜色匹配和点击顺序输出。OrchestratorAgent:把采集、推理、回测串起来,方便单次识别和批量验证。FastAPI服务:提供 HTTP 接口,对外返回点击坐标。
这样拆完之后,训练、调参、压测和服务化就不会混成一锅粥,后面要替换模型或调整颜色匹配策略也比较方便。
从无到有的实现过程
1. 自动标注的妙用:解救画框奴隶
搞深度学习最头疼的就是数据标注。如果几百张图都要人眼看、鼠标拉框,我估计在这一步就直接放弃了。
观察验证码可以发现:大图里的背景虽然是大理石花纹,但图标的颜色饱和度非常高,呈现明显的单色状态。于是我尝试用 OpenCV 转换到 HSV 空间进行饱和度通道(S通道)的分割:
1 | # 核心思路:图标通常饱和度极高,背景饱和度极低 |
这套自动过滤脚本(项目中的 auto_label.py)不是只写死一个阈值,而是会动态尝试多个饱和度阈值;如果本地已有可用 YOLO 模型,还会优先用模型辅助标注,失败后再回退到 HSV 分割。它直接帮我自动完成了 95% 以上的画框工作!随后我只需要用 X-AnyLabeling 打开图片目录,花十几分钟微调一下极少数没对齐的框,并删除一些崩坏的数据,就完成了高质的标注数据集。
2. 训练 YOLOv8 模型
环境方面,我配置了 CUDA 12.8 和 PyTorch 2.11 运行库。在数据清洗完成后,运行划分训练集与验证集的脚本,生成 dataset.yaml 配置文件,就可以开始正式训练:
1 | from ultralytics import YOLO |
后面我把训练流程封装成了 TrainerAgent,默认训练 150 轮、imgsz=256、batch=32,并且在 Windows 下把 workers=0 固定下来,避免多进程数据加载引发虚拟内存或句柄相关问题。实际训练下来,模型在验证集上的 mAP50 很快就逼近了 0.99,定位图标的效果已经非常稳。
3. PT 模型导出为 ONNX
PyTorch 的模型文件虽然方便,但在部署到轻量服务器或不想安装复杂依赖的环境时比较笨重。我们将训练出来的 best.pt 导出为 best.onnx:
1 | # 导出为 ONNX 格式并启用简化的 ONNX 模型架构 |
实际推理时,项目会优先加载 models/best.onnx,ONNX Runtime 走 GPU Provider,失败时再回退 CPU;如果没有 ONNX 文件,则回退到 best.pt。使用 ONNX Runtime 后,单次图片的目标框提取耗时压到了 10ms 以内,速度非常惊艳。
踩坑记录与解决方案
在这个过程中,我也经历了数次调试和推倒重来,记录几个最典型的坑:
坑一:背景花纹干扰颜色判定,导致点击顺序错乱
现象:YOLO 目标检测框出图标的准确率接近 100%,但在并发回测时,经常把“黄色图标”判定为“蓝色”,或者反过来,导致点击顺序不对,整体成功率一度只有 60% 左右。
排查:我可视化了提取出来的图标切片,发现 YOLO 检测的边界框稍微偏大了一点点,把图标外圈大理石背景的蓝色/黄色线条也包了进去。我的色彩判定逻辑(ColorMatcher)在计算颜色均值和距离时,被外圈的背景噪点带偏了。
解决:
- 缩框限制:在提取图标切片用于色彩分析前,先对边界框进行收缩(收缩为中心 90% 的区域),只截取图标的内圈核心像素:
1
2
3# 边界框中心收缩
x1_shrink = cx - w_box * 0.45
x2_shrink = cx + w_box * 0.45 - 提高饱和度过滤:在颜色识别的像素提取逻辑中,提高 HSV 的饱和度下限,将
SAT_MIN从 30 直接拉高到 210。这样在比对颜色时,背景的大理石花纹像素会被全部过滤为背景色,只统计图标本身的高饱和度像素。
修改后,色彩判定的准确度直线上升,回测成功率瞬间飙到了 90%+。
下面是模型实际的目标检测预测效果:

坑二:YOLO 的训练缓存导致训练退化
现象:在微调和重新清理数据集后,重新开始训练时,经常发现模型的 Loss 异常大,或者在某些 epoch 后指标突然崩塌。
排查:后来发现 YOLO 在首次读取数据集后,会在 labels/train/ 和 labels/val/ 目录下生成同名的 .cache 缓存文件。当我在本地修改了数据集或手动删除了部分噪点 .txt 标注后,YOLO 依然在读取过期的旧缓存,里面包含了可能高宽为 0 的脏数据或已经被物理删除的样本指针。
解决:在我的 dataset_prepare.py 数据准备脚本中,加入了每次重分数据集时自动搜索并彻底物理删除 *.cache 文件的逻辑:
1 | # 清理已有的 YOLO .cache 缓存文件,防止 YOLO 缓存旧的坐标尺寸 |
坑三:Windows 下的字符集编码地狱
现象:为了方便一键启动,我写了几个 .bat 批处理文件。然而运行的时候,命令行频繁报错,甚至连 cd 命令都因为路径里的中文变成乱码而失效。
排查:Windows 的 CMD 默认使用 GBK 编码(活动代码页 936),而我们在现代编辑器中写出来的脚本全都是 UTF-8,导致 CMD 执行时对中文路径进行错字拼合或分词错误。
解决:在 .bat 开头加入 chcp 65001 >nul 切换为 UTF-8 代码页,并将 .bat 的文件保存编码强制转为 Windows 本地的 GBK/ANSI 编码,双击运行终于稳定。
最终效果与心得
在解决了上面几个关键问题后,我写了一个并发压测回测脚本(测试.py),对系统进行 100 次的连续实测,最终效果如下:

模型训练过程中产出的召回率曲线(Recall-Confidence Curve)也非常漂亮。在置信度为 0.85 以下时,Recall 召回率维持在完美的 1.00,这意味着我们完全不会遗漏任何一个候选图标:

个人心得:
- 不要过早优化算法,先优化数据:在这次项目中,我最开始花了很多时间去折腾各种复杂的聚类色彩判定和分类模型,结果成功率很低。后来才发现,仅仅是因为标注框稍微偏大引入了背景噪声。把框收紧、把数据洗干净,比换什么高级模型都有用。
- 写好工具链事半功倍:像
visualize_labels.py(随机画红线抽检标注)和dataset_prepare.py这种工具脚本,在反复迭代和微调数据的过程中省下了我极其庞大的时间。 - PyTorch 没想象中那么难:以前总觉得机器学习和深度学习门槛极高,但真正动手从零折腾一个项目、去解决现实中的具体问题时,你会发现一步步查文档、写脚本、调试模型,其实和普通业务搬砖一样,重点是“动手去做”。
开源地址
项目已在 GitHub 上开源,欢迎交流与学习:
(注:本项目及相关博文仅供学术交流与个人研究使用,不得用于任何非法或侵入性用途。)
如果你也在折腾 AI、深度学习、自动化识别或者工程实践,可以关注我的公众号,后面我会继续记录这些折腾过程。



