news 2026/9/15 5:30:56

基于YOLOv9的验证码识别实战:从数据标注到模型部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv9的验证码识别实战:从数据标注到模型部署

简介:面向验证码识别与计算机视觉开发者,这是一套基于Python和YOLOv9的验证码识别设计源码,聚焦网站安全、自动登录、自动化测试等真实场景,可用于快速理解并搭建从数据处理、模型训练到端到端预测的完整流程。资源包共2000个文件,压缩包大小约186.29MB,其中包含1986个TXT标注与说明文件、10个Python脚本以及4个YAML配置文件;TXT文件提供样本标签和训练说明,py脚本覆盖训练、双模型训练、验证、预测等关键功能,YAML则用于配置模型与训练参数。项目代码结构清晰,内附完整标注样本,能帮助学习者掌握YOLOv9在验证码场景下的应用思路,并可直接基于现有标注体系和脚本开展二次训练与调优。目前已有313人学习浏览,适合具备一定Python基础、希望结合深度学习解决验证码识别问题的开发者参考。

1. 为什么用 YOLOv9 识别验证码,而不是传统 OCR

我第一次着手验证码识别时,思路直接跳到了 OCR。等到把真实样本接进来才发现,验证码根本不想让机器去读:字符扭曲、背景噪点、干扰线、字符粘在一起,传统 OCR 在干净印刷体上再漂亮,放到这种输入上也很难泛化。转成目标检测以后,每个字符就是一个检测目标,YOLOv9 的实时推理速度加上对密集小目标的特征表达能力,恰好点在验证码识别的痛点上。这个源码包围绕 40701 张 PNG 和 9270 个 TXT 标注搭建,从数据整理、YOLO 训练、验证评估到结果还原都有现成入口,对做自动化测试、数据采集和网站安全评估的工程师来说,属于拿到手就能直接改的完整工程。

2. 从 PNG 和 TXT 入手:验证码数据的组织与 YOLO 标签解析

2.1 文件构成与标签格式

压缩包解开后,文件数量非常吓人:40701 张 PNG、9270 个 TXT、9 个 Python 源码、6 个 YAML 配置,外加一个 JPG、一个 ZIP 和 gitattributes。文件规模归规模,真正需要关心的只是 PNG 和 TXT 的对应关系。在 YOLO 系列项目中,TXT 文件就是一张图片的标注文件,命名和图片保持一致,例如x8f2a.png对应x8f2a.txt。之所以 PNG 数量远多于 TXT,常见原因是仓库里同时保留了未标注的原始图片池,它们可以留作预测演示或之后的半监督扩充训练集。

TXT 的每一行代表一个检测框,格式固定为“类别编号 中心点x 中心点y 宽度 高度”,后四个数是相对于图片宽高的归一化坐标。一个四位验证码的标签文件内容通常长这样:

0 0.2365 0.4812 0.1003 0.1987 1 0.3958 0.5034 0.0987 0.2041 2 0.5691 0.4988 0.1025 0.2013 3 0.7412 0.5176 0.0994 0.2102

第一列的 0、1、2、3 是不同字符对应的类别索引,类别表由数据 YAML 里的 names 字段决定。后面四位是框的中心坐标和宽高,全部经过归一化处理。归一化格式对小字符检测任务尤其友好,因为验证码图像通常只有几百像素宽,字符高度经常低于 50 像素,直接使用像素坐标训练容易受输入尺寸变化影响,归一化后则没有这个问题。

2.2 按 8:2 切分训练集和验证集

拿到未整理的数据后,第一件该做的事是把它按标准目录结构切好。常见做法是划分成train/imagestrain/labelsval/imagesval/labels四个目录,并在切分时固定随机种子,保证之后每次对比实验都在同一个验证集上做。下面脚本只复制已标注的 PNG 和对应 TXT,未标注图片不参与训练划分:

import random import shutil from pathlib import Path random.seed(42) # 固定随机种子,保证实验可复现 img_dir = Path("datasets/images") # 40701 张验证码原图 lab_dir = Path("datasets/labels") # 9270 个 TXT 标注 base = Path("datasets/captcha") pairs = [] for img_path in sorted(img_dir.glob("*.png")): txt_path = lab_dir / (img_path.stem + ".txt") if txt_path.exists() and txt_path.stat().st_size > 0: pairs.append((img_path, txt_path)) random.shuffle(pairs) val_n = int(len(pairs) * 0.2) for split, items in [("train", pairs[val_n:]), ("val", pairs[:val_n])]: imgs_out = base / split / "images" labs_out = base / split / "labels" imgs_out.mkdir(parents=True, exist_ok=True) labs_out.mkdir(parents=True, exist_ok=True) for img_path, txt_path in items: shutil.copy2(img_path, imgs_out / img_path.name) shutil.copy2(txt_path, labs_out / txt_path.name) print(f"{split}: {len(items)} pairs")

脚本逻辑分三块:用glob收集所有 PNG,再同名拼接 TXT 路径并过滤掉不存在或长度为 0 的标注;用random.shuffle打乱顺序;最后按 8:2 的比例把成对文件复制到独立目录。这里特意用copy2而不是move,是为了保留原始目录,切分出错可以直接重跑且不用反复解压。val_n变量计算验证集样本数,如果你希望验证集更小,把 0.2 改成 0.1 即可。

验证集比例要不要改成 9:1?我建议看标注总量。9270 个标注样本属于中等规模,20% 验证集会留下 1854 张图,足够观察 mAP 波动;如果后续做交叉验证,把切分脚本直接改成 K 折循环会更方便。

2.3 数据 YAML 的类别对齐

切分完成之后,还要检查数据 YAML。6 个 YAML 配置文件里,一类是数据集定义,一类是模型结构定义。数据集 YAML 负责告诉训练脚本图片放哪、标签属于哪些类别。如果验证码只包含数字加英文大写字母,常规定义是 36 类:

path: datasets/captcha train: train/images val: val/images nc: 36 names: 0: "0" 1: "1" 2: "2" 5: "5" 10: "A" 35: "Z"

path是基于 YAML 文件位置的相对根目录,trainval填切分出来的图片目录,ncnames数量必须严格对应。类别顺序只要保持自洽即可,但一旦模型训好,predict.py读取的 names 必须和训练时完全相同,否则类别索引会错位。改完 YAML 可以先跑一个 epoch 的短训练看类别加载日志,确认输出里能看到明确的命名标签,而不是class index out of range

3. train.py / train_dual.py / train_triple.py:训练入口的选择与参数调优

3.1 三个训练脚本的差异

YOLOv9 的训练入口在这个项目里有三份:train.pytrain_dual.pytrain_triple.py。很多人刚打开会以为只是复制粘贴的备份,实际上它们对应不同的监督策略。train.py是标准 YOLO 单分支训练,使用常规检测头;train_dual.py在训练阶段增加一条辅助监督路径,让骨干网络同时接收两份标签信号,能缓解梯度信息在深层网络中的丢失;train_triple.py则再增加一路标签匹配,适合字符重叠、互相遮挡严重的验证码。

验证码字符几乎没有“大目标”,绝大多数框的宽度不到原图的 15%。这种情况下常规train.py也能收敛,但字符之间一旦出现轻微粘连,单条监督路径的召回率会明显下滑。我通常先跑train.py建基线,再用train_dual.py跑第二轮对比,如果验证集 mAP50 能提升 1~2 个点,就继续试train_triple.py。dual 和三路分支的显存占用大约是单分支的 1.2~1.5 倍,对小显存显卡不太友好。

3.2 训练命令与关键参数

进入训练前,先确认 Python 环境已经装齐依赖。这个项目对 Python 版本不挑,3.8~3.10 都可以,把 YOLOv9 要求的依赖通过pip install -r requirements.txt装上即可。训练本身的启动命令不复杂,最核心的部分是这样:

python train.py \ --data datasets/captcha/captcha.yaml \ --weights yolov9-c.pt \ --epochs 200 \ --batch-size 16 \ --imgsz 640 \ --device 0

train_dual.pytrain_triple.py时,命令结构完全一样,只需把第一行的train.py换成对应脚本名称。参数里--data指向上一章准备好的数据集 YAML;--weights是预训练权重路径,第一次训练建议用yolov9-c.pt,字符类任务从 COCO 预训练权重起步比随机初始化快得多;--imgsz是输入图片统一缩放的边长,验证码原图并非正方形,这个值会先等比缩放再 padding 补边。

下面列出验证码任务中改动最频繁的一组参数及建议值:

参数默认含义验证码场景建议
--imgsz输入图像边长640;字符过于密集可降到 480 或 512
--batch-size单次迭代图片数显存 8G 用 16,11G 以上用 32
--epochs训练轮数200 起步,验证码收敛较快,尽量不超过 300
--workers数据加载进程数Linux 设 4~8,Windows 设 0~2
--patience验证集不再提升时早停轮数30 左右
--cos-lr是否启用余弦学习率衰减建议打开,字符类小目标对学习率敏感

3.3 训练过程中的关注点

训练跑起来之后,别只盯着 loss。对验证码识别而言,train/box_lossval/box_loss的差距才是判断过拟合的直接指标。9270 多个标注量在目标检测里属于小规模,但如果每个 epoch 结束后 mAP50 持续上升而 mAP50-95 停滞在较低水平,大概率是标签框对字符边缘的贴合度不够,需要回到数据侧检查 TXT 是否把干扰线、背景纹理也框了进去。

断点续训用--resume指定权重路径即可,不必重头再跑。形态上验证码模型通常在第 60 个 epoch 前后开始输出可读的字符串,后续提升比较缓慢。如果训练到第 150 轮 mAP 还在小幅爬升,直接让它跑完,不要因为看到几个错误预测就提前中断;验证码字符类别少但相似字符多,0 和 O、1 和 I 非常容易混淆,这些错误往往在靠近训练末期的细粒度特征调整阶段才被修正。

4. val.py 与 predict.py 的实战:mAP 评估和验证码字符还原

4.1 用 val.py 评估模型

训练完的权重一般存在runs/train/exp/weights/best.pt。评估环节不要直接用 predict 看几张图凭感觉下结论,先跑一遍完整验证集:

python val.py \ --data datasets/captcha/captcha.yaml \ --weights runs/train/exp/weights/best.pt \ --batch-size 16 \ --conf 0.25 \ --iou 0.45

输出里重点看 mAP50 和 mAP50-95 两列。验证码识别属于字符级检测,字符框重叠一般不严重,mAP50 能稳定到 98% 以上,模型才算基本可用;mAP50-95 对这个任务的意义更多是衡量框边缘的紧致程度,评分如果低于 85%,大概率是框偏大或偏小导致字符边界抓不准,这时候去调--imgsz或检查标签框的贴合度,比单纯增加训练轮数更有效。

4.2 predict.py 的推理入口

predict.py负责把单张图片送进模型,得到框坐标和类别编号。验证码图片尺寸小,推理时有一个容易被忽略的点是保持图片原始宽高比缩放,不要直接拉伸成正方形,字符变形会让置信度下降很多。命令行调用方式:

python predict.py \ --weights runs/train/exp/weights/best.pt \ --source datasets/captcha/val/images/x8f2a.png \ --conf 0.45

--conf在验证码任务里我习惯设置得比通用目标检测更高,因为验证码字符边界清晰,小于 0.45 置信度的预测框大多数是噪声背景产生的误检,留着只会干扰后续拼接。项目文件列表里的val_dual.py对应 dual 训练权重的验证入口,预测逻辑和标准predict.py完全一致,不需要为 dual 权重单独写推理代码。

4.3 把检测框还原成验证码字符串

predict 输出的框坐标是像素绝对值,类别编号是整数。要还原验证码字符串,必须按字符在图片中的从左到右顺序排序,而不是按置信度排序:

import numpy as np def detections_to_captcha(det, idx2char): boxes = det[:, :4] # x1 y1 x2 y2 scores = det[:, 4] classes = det[:, 5].astype(int) order = np.argsort(boxes[:, 0]) # 按左边界 x 升序排列 captcha = "".join(idx2char[c] for c in classes[order]) return captcha, float(scores.max())

这里的det是 NMS 后的张量,一行对应一个检测框。用argsort(boxes[:, 0])按左边界 x 升序排列,得到的就是验证码字符串从左到右的顺序。idx2char是从数据 YAML 的 names 字段生成的类别索引到字符的映射表。排序之外还要处理“一个字符预测出两个框”的情况,如果两个框的左边界横坐标差小于字符平均宽度的四分之一,保留置信度高的那个,否则字符串会多出字符。

5. 用定长约束、切片推理和 ONNX 导出收尾线上流程

5.1 定长验证码的置信度筛选

验证码业务里最常见的形态是固定长度,例如 4 位或 6 位。检测模型天然不知道“只能有 4 个字符”这个规则,它只会输出置信度达标的所有框。所以要加一个后处理函数,先按置信度截断,再做定长校验:

def normalize_captcha(boxes, scores, classes, idx2char, max_len=4): keep = np.argsort(scores)[::-1][:max_len] # 先按置信度取前 4 个 boxes = boxes[keep] classes = classes[keep] order = np.argsort(boxes[:, 0]) # 再按 x 坐标还原顺序 return "".join(idx2char[c] for c in classes[order])

先取置信度最高的 4 个框,再按 x 坐标排序,比先排序再截断更稳。因为在字符重叠场景下,模型可能在同一个字符上给出两个高置信度框,先按 x 截断会直接把某个位置的字符挤掉,先按置信度筛选则能保留每个位置最可靠的框。

5.2 重叠字符的切片推理

如果验证码字符间隙非常小,甚至发生笔画交叉,整图一次性推理时 NMS 会误杀相邻字符。我一般会切分图片:把宽度超过 300 像素的高密度验证码按 50% 重叠切成两半分别推理,再把两边的坐标映射回原图坐标,合并时把 NMS 的 iou 阈值从 0.45 降到 0.3。切片推理等于把字符区域放大了一倍,召回率的提升通常比调模型参数来得更明显。

5.3 导出 ONNX 接入部署

验证码识别经常要嵌进自动化流程,Python 直出推理性能足够,但部署到独立服务时我习惯把权重导成 ONNX 再做加速推理:

python export.py --weights runs/train/exp/weights/best.pt --include onnx --imgsz 640

导出后要重新验证一次输出是否和 PyTorch 动态图一致,重点看上采样层和 Dense 层是否被正确解析。线上调用保留 5.1 节里的normalize_captcha函数,把 ONNX 输出的 1xNx6 张量按相同顺序重组,即可直接返回用户输入校验结果。定长约束、切片推理和 ONNX 导出这三个步骤接入线上流程后,验证码识别才算从实验脚本变成了真正可用的服务。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 5:30:21

云安全管理新范式:从最小权限到配置核查的落地指南

每次行业大会的消息一出,总有人问我“值不值得跑一趟”。说实话,如果只能挑一个会去,我一般会优先看看CDIE这类偏“数字化转型应用”的场子——因为这里聊的不是PPT里的概念,而是企业真金白银踩出来的落地路径。今年收到新钛云服的…

作者头像 李华
网站建设 2026/9/15 5:30:17

STC8G1K17A音乐灯条控制器:从硬件电路到DRV驱动隔离的设计复盘

简介:一套基于STC8G1K17A单片机实现的音乐幻彩灯条控制器完整项目,适合单片机课程设计、毕业设计以及各类电子设计竞赛等应用场景。项目在DRV目录中封装了底层硬件驱动,修改相关代码即可移植到其他型号单片机,便于二次开发与学习。…

作者头像 李华
网站建设 2026/9/15 5:29:01

手机碎屏应急处理与维修避坑指南

1. 屏幕碎裂后的第一反应:冷静评估损伤程度当手机从手中滑落撞击地面的瞬间,大多数人的第一反应都是心跳加速、呼吸停滞。但此刻最需要的是立即执行"损伤三步评估法":触控功能测试:在碎屏表面滴几滴水珠(注意…

作者头像 李华
网站建设 2026/9/15 5:26:29

Shopify撤离React Native真相:从跨端回迁原生的真实成本

去年 Shopify 官宣把移动端主 App 从 React Native 逐步撤回 Swift/Kotlin 的时候,圈子里讨论声很大。有人把这解读成“跨端已死”,也有人觉得这是“大厂终于认清了现实”。但真正从头到尾跟过这类迁移的人,大概率不会说得这么简单——因为从…

作者头像 李华
网站建设 2026/9/15 5:25:30

形态分量分析实战:基于Python的混合信号稀疏分解指南

简介:形态分量分析是一种源于数学形态学的图像处理技术,擅长将复杂图像拆解为若干基本形态单元,适用于医学影像、工业检测和生物图像识别等场景。针对该技术提供的代码包,面向需要快速上手形态学算法的Matlab用户与图像分析初学者…

作者头像 李华
网站建设 2026/9/15 5:24:08

LabVIEW调用UDS安全访问服务VI详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华