
简介基于 Python 的滑块验证码 Yolo 识别算法新版源码包附有说明文档主要面向计算机、数学、电子信息等专业学生可支撑课程设计、期末大作业、毕业设计也适合新手通过实际项目完成从数据处理到模型推理的完整演练。压缩包内共 29 个文件包含 4 个 Python 脚本、YAML 推理配置、模型权重分卷、README 说明、多张 JPG 样例图片及 PNG 示例图等整体大小约 165.67MB目录结构清楚方便按需查阅。目前已有 186 人学习该源码包。源码模块划分明确涵盖预处理、推理、滑块推理和可视化等环节配合模型参数分卷与验证码截图样本可帮助读者理解 Yolo 识别思路并快速复现工程文件中还保留了 .gitignore、模型目录等细节适合作为毕设或课程设计的基础框架在读懂代码的前提下继续扩展功能或在此基础上做针对性调优。1. 滑块缺口检测为什么从 OpenCV 迁移到 YOLO先算出缺口坐标再谈拖动滑块验证码识别有个迷惑性很强的表象它看起来是一道 OpenCV 题。找背景图上被挖掉的缺口模板匹配、边缘检测、霍夫变换三件套一上demo 一个小时就能跑通。但把同一套代码换到第二个网站准确率立刻断崖式下跌。问题不在算法实现而在背景纹理、渐变和干扰线让手工特征失去了泛化能力。基于 Python 的滑块验证码 YOLO 识别把这个任务换了个解法不再手写特征而是把缺口当成一个目标检测对象用 YOLO 输出缺口边框坐标再换算成滑块要拖动的像素距离。它适合做自动化测试、合规数据采集和安全评估的工程师——被滑块卡住流程又不想依赖高延迟打码平台最合理的路径就是自己训一个缺口检测模型。YOLO 在这里承担的是“找缺口”这一步后面怎么拖、怎么避开风控是另一条链路。这个源码包的价值就是把“找缺口”从传统图像处理的黑匣子变成了一个可训练、可迭代、可换背景风格的数据驱动模型。2. 训练数据从哪来合成与标注决定模型上限的第一步2.1 滑块验证码的视觉特征YOLO 眼里的“缺口”不是洞市面上滑块验证码服务形态很多自研的、商业组件的比如开源社区里常见的 tianai-captcha视觉规律大同小异。找缺口这件事在 YOLO 看来是一个小目标检测问题。缺口通常只有一个尺寸占整张图不到十分之一视觉线索要么是镂空后露出的背景色差要么是边缘一圈内阴影和描边。有些场景滑块本体也要检测模型输出就变成两个类别缺口和滑块把手。干扰才是重点。渐变背景、斜向纹理、随机干扰线、仿缺口的装饰性图形都是专门用来骗传统算子的。合成训练数据时不能只生成“干净的缺口”要按真实验证码的复杂度加噪声。你给模型的样本越接近它上线后会遇到的东西后面踩坑就越少。这也是这种项目里最容易被新手跳过的一步——拿到源码先看数据集而不是先看训练代码。2.2 合成数据脚本用背景图批量生成缺口样本真实缺口样本要人工标注成本高、量也上不去。常见做法是写一个合成脚本拿若干张干净背景图随机位置“抠”出一个缺口再叠阴影和干扰线同时直接把 YOLO 的 txt 标注写出来。这样一分钟能生成几百张足够把模型先跑起来。import random from PIL import Image, ImageDraw def synthesize(bg_path, out_path, label_path, gap_radius(10, 18), size(400, 240)): 用一张干净背景图随机生成一张带缺口的验证码样本 img Image.open(bg_path).convert(RGB).resize(size) W, H size # 缺口位置随机但离边缘至少留一个半径避免标注框越界 r random.randint(*gap_radius) cx random.randint(r, W - r) cy random.randint(r, H - r) draw ImageDraw.Draw(img) # 内层填充模拟缺口底部露出的背景色块 fill random.randint(90, 170) draw.ellipse((cx - r, cy - r, cx r, cy r), fill(fill, fill, fill)) # 边缘阴影叠三圈灰色轮廓制造凹陷感 for offset in (3, 2, 1): draw.ellipse((cx - r - offset, cy - r - offset, cx r offset, cy r offset), outline(0, 0, 0)) # 干扰线只在缺口周围出现避免覆盖缺口本体 for _ in range(random.randint(1, 4)): x1 random.randint(max(0, cx - r * 4), min(W, cx r * 4)) y1 random.randint(max(0, cy - r * 4), min(H, cy r * 4)) draw.line((x1, y1, x1 random.randint(-40, 40), y1 random.randint(-20, 20)), fill(random.randint(60, 220),) * 3, width2) img.save(out_path) # YOLO格式标注class x_center y_center width height全部归一化 with open(label_path, w) as f: f.write(f0 {cx / W:.6f} {cy / H:.6f} {2 * r / W:.6f} {2 * r / H:.6f}\n)逻辑说明画布固定 400x240 是为了让归一化坐标一致也避免训练时反复 resize 引入额外误差缺口中心就是标注框中心宽高取直径2 * r因为 YOLO 的框用中心点加宽高表示不是用半径。缺口位置用random.randint(r, W - r)限定了边界否则缺口中心一旦贴边标注框就会超出图像范围训练时 loss 会莫名抖动。参数说明gap_radius(10, 18)对应常见小拼图缺口。如果你要处理的站点缺口明显偏大或偏小要按真实截图量一下缺口直径再改。干扰线限制在cx ± r*4范围内是故意把干扰放在缺口附近逼模型学会区分“真缺口”和“仿缺口线条”而不是靠位置先验偷懒。合成脚本跑完至少准备 5 种纹理差异明显的背景图分别生成再混入一部分完全不扣缺口的纯背景图作为负样本txt 留空。负样本比例在 10% 左右即可太少模型容易“没有缺口也硬框一个”。2.3 从 labelme JSON 到 YOLO txt转换脚本与坐标越界如果你手里已经有一批人工标注好的真实截图最常见的是 labelme 导出的 JSON。YOLO 训练不吃 JSON需要转成每个图片同名的 txt 文件。import json import glob import os CLASS_MAP {gap: 0} def convert_labelme(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) W, H data[imageWidth], data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue xs [pt[0] for pt in shape[points]] ys [pt[1] for pt in shape[points]] x_min, y_min, x_max, y_max min(xs), min(ys), max(xs), max(ys) # 过滤掉描点异常的多边形比如只有两三个点的情况 if (x_max - x_min) 5 or (y_max - y_min) 5: continue cx (x_min x_max) / 2 / W cy (y_min y_max) / 2 / H bw (x_max - x_min) / W bh (y_max - y_min) / H # 越界截断YOLO训练时越界框会让loss数值异常 cx max(0.0, min(cx, 1.0)) cy max(0.0, min(cy, 1.0)) lines.append(f{CLASS_MAP[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: base os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines)) for j in glob.glob(labels_json/*.json): convert_labelme(j, labels_txt)逻辑说明labelme 的多边形往往只描了缺口弧线外接矩形会比实际缺口大一圈所以脚本里按面积做了过滤小于 5x5 像素的标注直接丢掉避免把噪声当目标。越界截断是这步最容易被忽略的坑缺口在画布边缘时外接矩形会超出[0,1]范围不截断会导致训练时 Anchor 匹配异常。数据转完后按 7:2:1 分成 train、val、test 三个目录图片和 txt 一一对应。这一步做完目录结构就满足 YOLO 训练输入的要求了。3. 训练配置与参数把自定义数据集喂进 YOLOv83.1 模型选型为什么验证码场景默认先试 YOLOv8nYOLOv8 是目前处理这类小目标检测最稳的选择。很多源码包会同时给 v5 和 v8 两份配置我建议直接用 v8 系列训练命令和部署接口都更现代。选哪个体量看你的推理硬件和缺口尺寸。模型参数量量级batch16、imgsz640 时显存占用适用场景YOLOv8n约 3.2M约 4-6 GB验证码首选CPU 也能推理YOLOv8s约 11.2M约 6-9 GB缺口小于 10px、背景极度复杂YOLOv8m约 25.9M约 10-16 GB一般用不上目标太小大模型收益有限验证码缺口是小目标模型变深反而容易过拟合到训练集里的背景纹理。v8n 的参数量在 n 和 s 之间差了三倍多但 mAP 差距通常不到 2 个点推理速度快一倍以上。生产环境里验证码检测链路是串联截图、推理、拖动三件事的推理环节每多 10 毫秒都会直接影响整体通过率。如果不是缺口小到连人都要放大看我一般不会上 s。3.2 data.yaml 与训练命令一行命令跑起来自定义数据集训练前先写 data.yaml。这里有一个很多新手会忽略的点nc只写你自己的类别数不要照抄 COCO 的 80 类。# data.yaml path: /home/user/datasets/slider # 数据集根目录 train: images/train val: images/val nc: 1 names: [gap]yolo detect train \ dataslider.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ patience20 \ device0 \ projectruns/slider \ nameexp1参数说明epochs120对合成数据足够验证码样本量通常只有几百到几千张训练超过 150 轮几乎必过拟合imgsz640是默认值缺口占比小时可以提到 768但显存占用和推理耗时都会涨batch16以 8G 显存为基准OOM 就减半patience20是早停耐心值验证集 loss 连续 20 轮不下降就自动停这个参数值得调低一点省时间。还有一个跟其他目标检测任务很不一样的地方建议把 mosaic 增强关掉在训练命令里加mosaic0.0。ultralytics 默认开 mosaic它会把四张图拼成一张缺口正好被切成碎块模型学到的全是“半个缺口”的特征部署时遇到完整缺口反而框不准。这是滑块验证码训练里最典型的一个参数翻车点。3.3 训练结果怎么看mAP50 之外还要看框中心偏差训练结束后runs/slider/exp1目录里会有results.png和confusion_matrix.png。先看 val 集上的box_loss、cls_loss、dfl_loss三条曲线正常应该是持续下降并走平如果有明显反弹就是过拟合回训练命令里把 epochs 降到 80或者把数据增强里的色彩扰动加大。mAP50 对验证码场景只是参考。缺口检测的最终产物是“中心点坐标”所以真正决定能否拖准的指标是检测框中心和真实缺口中心的像素偏差。我习惯训练完跑一个统计脚本把所有验证集图片喂给模型逐个算框中心和标注中心的欧氏距离求平均值。mAP50 到 0.95 不难难的是这个平均偏差稳定在 5px 以内。如果偏差大于 5px先别调模型回合成脚本查标注对不对。这个“中心点偏差”指标在 YOLO 原生的训练日志里看不到但它是验证码场景验收的硬标准。4. 部署推理从检测框到可拖动的像素距离4.1 最小推理代码加载 best.pt 输出缺口中心训练完从runs/slider/exp1/weights/里拿best.pt推理代码很短。from ultralytics import YOLO model YOLO(best.pt) def detect_gap_center(image_path): results model(image_path, imgsz640, conf0.45, verboseFalse) if results[0].boxes is None or len(results[0].boxes) 0: return None box results[0].boxes[0] # 置信度最高的框 x1, y1, x2, y2 box.xyxy[0].tolist() conf box.conf[0].item() cx (x1 x2) / 2 cy (y1 y2) / 2 print(fgap confidence{conf:.2f} center({cx:.1f}, {cy:.1f})) return cx, cy逻辑说明box.xyxy返回的是像素坐标这个坐标是模型内部把输入图缩放到 640 之后又映射回原图的所以直接可以当原始像素用。conf0.45是置信度阈值验证码场景建议卡在 0.4-0.5太低会把干扰线框进来太高会漏检小缺口。如果页面截图里同时有滑块和缺口而你的模型只训了 gap 一类这里只取置信度最高的框就够了。4.2 坐标换算最容易“差几像素”的缩放逻辑模型输出的坐标是“截图像素”拖动是“浏览器渲染像素”。两者之间隔着一个缩放比这是整个链路里最容易出偏差的地方。def to_drag_distance(gap_center_x, page_captcha_width, img_width): 把模型输出坐标换算成浏览器内的拖动距离 # page_captcha_width 是验证码区域在页面里的实际渲染宽度要从DOM里读 scale page_captcha_width / img_width gap_screen_x gap_center_x * scale # slider_center_x 是滑块把手中心在页面里的x坐标也要从DOM里读 slider_center_x get_slider_center_from_dom() return gap_screen_x - slider_center_x参数说明img_width是你输入给模型的截图宽度page_captcha_width是浏览器里验证码区块的渲染宽度。如果截图时用的是整页截图而验证码只是其中一块这两个值完全不同不换算的话每次偏差都是固定比例。滑块中心不能写死不同窗口宽度下同一张验证码的滑块把手位置会变必须每次从 DOM 里取。这一段的血泪经验是训练和推理的imgsz必须一致。如果训练用 640推理时手滑写了 480YOLO 内部会先把图缩到 480 再还原所有框都会朝图像中心偏移偏差量还跟目标位置有关不是线性误差。4.3 拖拽轨迹拖动距离对了也会失败的最后一环坐标算对了直接mouse.move一步拖过去验证码大概率不通过。因为服务端风控在看的不是终点而是鼠标轨迹。import random import time from playwright.sync_api import sync_playwright def drag_slider(page, distance): handle page.locator(#slider_handle).bounding_box() x0 handle[x] handle[width] / 2 y0 handle[y] handle[height] / 2 # 分段移动先快后慢模拟人看到缺口后减速定位的过程 steps 10 moves [] remaining distance for i in range(steps): seg remaining / (steps - i) random.uniform(-2, 2) moves.append(seg) remaining - seg page.mouse.move(x0, y0) page.mouse.down() for dx in moves: x0 dx page.mouse.move(x0, y0, stepsrandom.randint(2, 4)) time.sleep(random.uniform(0.008, 0.03)) page.mouse.up()逻辑说明分段移动把一次大位移拆成 10 小段每段距离逐渐变小因为真实人手在靠近目标时会减速。random.uniform(-2, 2)给每段加一点抖动避免轨迹过于平滑。用时控制在 400-900 毫秒太快像机器太慢会触发超时。按下位置必须落在滑块把手图标内部这是新手最容易忽视的细节起点都错了轨迹再拟人也没用。5. 避坑与排查滑块验证码 YOLO 识别最容易翻车的 5 个地方5.1 坑一模型老把干扰线或阴影当缺口现象模型在验证集上 mAP50 很高但部署到目标站点后检测框经常落在阴影、干扰线或者仿缺口装饰上置信度还不低。原因合成数据时缺口边缘太“干净”和背景的区分度太高模型学到的是“那块颜色不一样”而不是“缺口有边缘和阴影结构”。干扰线的颜色、粗细如果和真实站点差异大模型就分不清。解决在合成脚本里给缺口边缘叠 2-3 层真实感阴影模仿像素抠图后的凹陷效果同时准备一批“无缺口”背景图做负样本让模型学会没有目标时输出空。负样本占比 10%训练后如果还误检把负样本提到 20% 再训一轮。5.2 坑二坐标稳定偏移几个像素拖拽永远对不齐现象检测框中心总是落在真实缺口中心的右下方偏差在 5-10px 之间而且每次方向一致。原因三个可能——训练和推理的imgsz不一致截图到推理之间的resize没有保持宽高比图片被拉伸了或者合成脚本里画缺口用的坐标和写标注用的坐标不是同一个坐标系。解决先把训练和推理的imgsz锁死成同一个值。再写一个标定脚本拿一张已知缺口中心的合成图走完整推理链路打印出预测中心和真实中心的差值。偏差大于 3px 就逐层查先查模型输出坐标再查截图尺寸最后查 DOM 里读到的页面宽度。这个环节九成问题出在单位换算不是模型。5.3 坑三验证码提示“拖动失败”不是位置问题现象检测框中心对得很准距离也换算正确但松手后仍然提示滑块拼图没对准。原因拖动轨迹暴露了自动化特征。线段式移动、匀速直线、每段间隔完全一致服务端风控把这些都当作评分特征。还有一个常见原因拖动总时长太短人在真实场景里看到缺口到开始拖至少有 100ms 以上的反应时间你三五十毫秒就拖完了直接被判异常。解决轨迹生成改成先快后慢并加入随机停顿。在按下滑块前加一个 50-150ms 的随机等待模拟人眼定位松手前加一个 20-50ms 的悬停模拟手抖确认。总时长控制在 400-900ms不要超过 1.2 秒。5.4 坑四GPU 显存不够换 CPU 训练又慢到失眠现象batch16训练几次后直接torch.cuda.OutOfMemoryError显卡是 6GB 以下显存的常见。原因YOLOv8 默认开了一堆数据增强其中 mosaic 和部分拼接增强会把显存峰值拉得很高。batch 设太大、imgsz设太高也会叠加问题。解决训练命令里加mosaic0.0再降batch8或batch4。还不行就把imgsz降到 480验证码缺口就算缩小到 10px480 分辨率也够识别。如果只有 CPU换yolov8n.pt并把epochs减到 60先用小模型验证数据是否正常再决定要不要租 GPU 跑完整训练。5.5 坑五测试集效果很好换一个新风格网站就全挂现象模型在自己数据集上召回率 95%换到另一个站点背景变成插画风格缺口框开始乱飞甚至把装饰图形当成缺口。原因这是典型的域漂移。训练集背景全是同一种类型——比如全是渐变底色模型学到的是“这个纹理范围内有一块颜色突变”而不是通用的“缺口结构”。解决训练集至少混合三种以上背景风格纯色渐变、真实风景图、卡通插画。合成样本的缺口位置分布要均匀不能总出现在图片中间。如果新站点缺口形态差异实在大收集 50-100 张真实截图用 2.3 节的转换脚本标好在现有模型基础上finetune30-50 轮比从零训练省时得多。6. 从能跑到跑得快导出 ONNX 做 INT8 量化再加一个标定脚本推理环节在验证码场景里是延迟敏感路径。best.pt在 CPU 上首次加载就要几百毫秒接口一并发就排队。常见做法是导出成 ONNX再进一步转成 TensorRT 引擎跑 INT8 量化这是 yolo 在部署阶段提升吞吐最直接的路径。yolo export modelbest.pt formatonnx opset12 imgsz640 simplifyTrue导出后用trtexec转引擎。INT8 量化需要准备一个校准集拿 50 张验证集图片跑一遍 PTQ精度损失通常在 1-3 个百分点。对缺口检测来说这个损失换来的推理速度提升非常划算单帧推理耗时能压到原来的五分之一以下TensorRT 引擎常驻显存后多路并发也不会成为瓶颈。# 最值得写的标定脚本验证从截图到坐标的整条链路误差 def calibrate(): img, true_cx load_labeled_sample(calib1.png) # 已知缺口中心 pred_cx detect_gap_center(img) print(bias:, round(pred_cx - true_cx, 2), px) if abs(pred_cx - true_cx) 3: print(check: resize? page_width? imgsz?)我第一次在这种项目里贪省事直接用yolov8m跑 CPU单帧推理毛 300 毫秒接口一并发就排队。后来花了一个周末做 ONNX 导出和 INT8 量化才把单帧压到几十毫秒真正敢上生产。还有一个更疼的教训从 YOLOv5 迁到 YOLOv8 后忘了同步推理尺寸所有坐标偏了 6 个像素排查了一下午。这类项目的玄学绝大部分不是模型玄学是尺寸和单位没换算对。建议你把上面那个标定脚本放在项目里每次改代码都跑一遍能省下大量排错时间。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。