ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OpenCV实战:三种方法精准识别腾讯滑块验证码缺口坐标

2026/9/15 3:31:35 拓冰建站 浏览量
OpenCV实战:三种方法精准识别腾讯滑块验证码缺口坐标 简介一套针对腾讯滑块验证码的缺口坐标识别方案基于OpenCV-Python实现适合计算机视觉方向的毕业设计、课程设计也适合刚接触图像处理的学习者动手实践。压缩包共44个文件包含4个Python脚本涉及阈值处理、轮廓查找、模板匹配等关键步骤、21张jpg和16张png测试图片以及README说明和License整体仅510KB轻量易用。目前已有475人学习下载。代码按流程组织配有多张不同场景下的验证码截图可直观对比缺口定位效果README对运行环境和使用方法做了说明便于快速跑通并二次改造。整体上既能帮助理解滑块验证码的检测原理也能为相关课题提供可复现的代码基础。1. 腾讯滑块验证码缺口坐标识别为什么先选 OpenCV 而不是目标检测模型腾讯滑块验证码的缺口识别几乎是 OpenCV 入门到实战之间最合适的一个项目背景图是现成的缺口特征清楚不需要标数据集一台没有 GPU 的笔记本也能跑。资源包里同时放了 threshold.py、findContours.py、matchTemplate.py 三个脚本分别对应三种不同的缺口定位思路样本图覆盖了亮背景、暗背景和带干扰线的场景用 opencv-python 就能直接跑通。这类问题换到深度学习框架里通常要标注上百张缺口框、再训练一轮目标检测模型而在验证码这类目标外观高度固定的场景中传统图像处理反而更快坐标精度也能做到像素级。下面直接拆这三个脚本各自解决哪一步参数怎么调以及实际踩过哪些坑。2. threshold、findContours、matchTemplate三份脚本的分工边界2.1 为什么同一个缺口要用三种思路各写一遍滑块验证码的缺口不是一个标准的实心色块。它的微观构成通常是原图被抠掉一块后留下的透明区域拼合到背景图上时部分素材会在缺口边缘压一圈高亮描边或投影导致二值化之后缺口变成环形甚至断裂成几段。这个特点决定了没有任何一种单一算法能覆盖所有图片。项目里三份脚本不是重复代码而是三个递进阶段threshold 负责快速圈出候选区域findContours 在候选区域上做形状筛选matchTemplate 用模板做最后一次精确定位。threshold.py 的处理路径最简单灰度、滤波、二值化然后用轮廓把大面积白色区域框出来。它把问题从在整张图里找缺口缩小成在一堆候选框里挑缺口运行速度最快适合先看一眼大概效果。findContours.py 的重点放在几何约束上。二值化之后前景里通常混着滑块本体、文字、噪点单靠面积过滤不够得组合使用宽高比、轮廓面积与外接矩形面积之比、轮廓层级关系这些条件。matchTemplate.py 的思路完全不一样它把缺口从目标变成了模板。只要模板截取得干净matchTemplate 对光照和复杂背景的容忍度比前两种高很多但它的前提是模板尺寸和待匹配目标几乎一致不然分数会显著下降。这也是为什么素材目录里会有 1_1.png、9_1.png 这类看起来成对的图片通常一张是原图一张是截出来的缺口模板。2.2 三种方案的参数与适用场景对比下面这张表是我在多个数据集上试过之后沉淀下来的参数起点读代码时可以对照查。方案关键函数核心参数适合场景最容易翻车的地方阈值法cv2.threshold / cv2.adaptiveThresholdTHRESH_BINARY_INV、THRESH_OTSU缺口与背景亮度差明显高光区域被误判成缺口轮廓法cv2.findContours boundingRect / minAreaRectRETR_EXTERNAL、RETR_TREE缺口是连通且闭合的块边缘断截导致缺口分裂模板匹配cv2.matchTemplate minMaxLocTM_CCOEFF_NORMED、TM_SQDIFF_NORMED有干净的缺口模板图模板缩放比与目标不一致THRESH_BINARY_INV 的作用是把比背景亮的缺口区域翻成白色前景比背景暗的缺口也能通过反二值化变白这样后续的轮廓查找只需要处理白色块。THRESH_OTSU 让 OpenCV 根据直方图自动寻找分割阈值它隐含的假设是图像直方图呈双峰分布一个峰是背景一个峰是目标和干扰。验证码背景一旦出现大面积渐变或者复杂纹理直方图变成单峰平滑形OTSU 算出的阈值就会往高偏移缺口可能被算成背景。遇到这种情况需要降级到固定阈值或者改用 adaptiveThreshold。RETR_EXTERNAL 只提取最外层轮廓好处是内部文字不会参与计算RETR_TREE 会把轮廓之间的父子关系也输出适合需要排除内嵌纹理的场景。轮廓模式的选择直接影响筛选条件后面第 4 章会展开讲。2.3 样本组织与运行方式项目根目录下既有 1.jpg、2.jpg 这种按数字命名的单张测试图也有 1_1.png、9_1.png 这种带下划线的成对文件wangyi 子目录里还放着另一组同类验证码素材。从命名看下划线版本大概率是配套的缺口模板或二值化中间图wangyi 那组说明这套逻辑换一个站点背景也能复用。运行方式很简单把 opencv-python 装好脚本和图片放同一目录直接执行python -m pip install opencv-python numpy python threshold.py python findContours.py python matchTemplate.py脚本里读的是固定文件名所以测其他图时打开脚本改一下 filename 变量或者复制脚本到对应目录再跑。输出文件建议统一放到 output 目录这样批量跑完后按文件名逐张核对失败原因不用临时再改脚本打印。3. threshold.py 粗定位缺口自适应阈值与候选区过滤3.1 灰度、滤波与反二值化threshold.py 虽然是整套流程里最简单的一个但它处理的方式决定了后面 findContours 看到的轮廓质量。直接对 RGB 图做二值化会有两个问题一是滑块验证码的背景经常是浅色渐变三个通道的亮度在缺口附近并不一致直接阈值会切出一堆杂点二是 JPEG 压缩会在缺口边缘留下振铃噪声轮廓面积和边缘形状都会被带偏。所以第一步一定要转到灰度空间并做一次高斯滤波。import cv2 import numpy as np img cv2.imread(1.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 5x5 高斯核sigmaX 传 0 表示由核大小自动推导 blur cv2.GaussianBlur(gray, (5, 5), 0) # 反二值化与 OTSU 自动阈值组合thresh 传 0 会被忽略 _, binary cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) cv2.imwrite(output/threshold_1.png, binary)cv2.threshold 返回两个值第一个是实际使用的阈值OTSU 模式下是自动计算出来的那个数值第二个是二值化结果图。THRESH_BINARY_INV 表示像素值大于阈值置 0小于等于阈值置 255正好把背景压成黑色把缺口和深色部分提成白色。核大小直接决定滤波强度5x5 已经能压掉大部分 JPEG 噪点如果图片分辨率更高比如超过 1000 像素宽可以换成 7x7但要注意核越大缺口边缘被模糊得越圆轮廓面积会略微变小。3.2 用轮廓外接矩形过滤候选区二值化图里白色块通常不止缺口一处。滑块本体、验证码文案、背景里的高亮物体都会变成白色区域。threshold.py 的粗定位阶段不追求唯一答案只要求候选数量能压缩到 3 个以内所以这一步的过滤条件以宽松为主。contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) area w * h # 腾讯滑块缺口常见像素尺寸宽 60~160高 40~160 if not (60 w 160 and 40 h 160): continue # 过滤掉过小噪点和过大的背景连通块 if area 3000 or area 20000: continue # 滑块本体在左侧缺口一般位于中间偏右 if x img.shape[1] * 0.25: continue candidates.append((x, y, w, h))w 和 h 的取值范围来自缺口的真实形态它是一个正方形偏矩形的空洞外接矩形不会是又窄又长的条。area 用外接矩形的宽高乘积而不是 cv2.contourArea是因为有些缺口的边缘带锯齿轮廓真实面积会上下浮动很大矩形面积在筛选阶段更稳定。x 小于图像宽度 25% 的候选区直接跳过是假设滑块本体几乎贴着左边界缺口出现在滑轨右半段这个假设对绝大多数腾讯滑块样本成立但如果遇到背景里右半部分有大块白色物体过滤条件会同时把缺口和它一起留下没关系粗定位只负责缩小范围最终取舍交给模板匹配。3.3 OTSU 失效时的补救自适应阈值资源包里不是每张图都是理想样本有些图背景是渐变色有些图里缺口边缘被阴影包裹直接跑 OTSU 会出现两种典型失败整个缺口区域在二值化后变成黑色或者缺口和背景连成一片白色。判断方法很简单把 output 里的二值图打开如果缺口位置不是一块轮廓清晰的白色区域说明阈值策略需要换成 adaptiveThreshold。binary cv2.adaptiveThreshold( blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 31, 10, )adaptiveThreshold 的第二个参数 255 是前景赋予的最大值blockSize 31 表示每个像素用一个 31x31 的邻域来估计局部阈值C 值 10 是最终的修正系数阈值减去这个偏移量后整体压低更容易把亮度只比背景高一点的缺口提出来。blockSize 越大局部估计越接近全局 OTSUC 值越大越倾向于把区域划为前景。实际调试时先固定 blockSize调整 C 从 5 到 20 逐档试每档把二值图保存下来看缺口边缘连续且没有粗黑边框的档位就是合适的。提示OTSU 对直方图双峰假设很强如果二值化结果里缺口区域不完整或者大面积连成一片优先切换 adaptiveThreshold不要先怀疑轮廓参数。4. findContours 与 matchTemplate 精修缺口坐标4.1 findContours.py 的形状约束与轮廓层级threshold.py 把候选框交付给下一级之后findContours.py 要解决的是候选框里有缺口也有干扰形状上怎么区分。缺口是一个被抠掉的矩形凹槽周围边缘可能有描边但整体轮廓的填充率比较稳定干扰块往往形态更随机。项目里 findContours.py 的定位就是用填充率和轮廓层级把随机形状剔除掉。kernel np.ones((5, 5), np.uint8) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations2) contours, hierarchy cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for i, cnt in enumerate(contours): x, y, w, h cv2.boundingRect(cnt) real_area cv2.contourArea(cnt) rect cv2.minAreaRect(cnt) box_area rect[1][0] * rect[1][1] if box_area 0: continue # 圆角矩形填充率集中在 0.5~0.9条形阴影通常小于 0.3 fill_ratio real_area / box_area if not (0.5 fill_ratio 0.95): continue # RETR_TREE 下顶层轮廓的 parent 是 -1有父轮廓的通常是内部文字 if hierarchy[0][i][3] ! -1: continue candidates.append((x, y, w, h))MORPH_CLOSE 先膨胀再腐蚀负责把二值化时断开的缺口边缘重新接上。iterations2 会让连接强度更高但如果缺口附近同时有高光膨胀后高光和缺口很可能连成一个整体填充率会接近 1。minAreaRect 返回的是 ((cx, cy), (w2, h2), angle)第 [1] 维就是旋转矩形宽高相乘得到 box_area。填充率 fill_ratio 用来衡量轮廓填充矩形框的程度缺口是圆角矩形凹槽内部一般没有大块纹理填充率在 0.5 到 0.9 之间长条光晕的填充率会低到 0.3 以下。hierarchy 的判断作用在 findContours 返回的层级数组上[i][3] 存的是第 i 个轮廓的父轮廓索引不等于 -1 说明它嵌套在某个大轮廓内部这类轮廓通常是缺口内残留的文字或噪点。4.2 matchTemplate.py 的滑动窗口匹配轮廓法在缺口被高光污染时会失效matchTemplate 是更稳妥的后手。它的原理可以理解为穷举滑动窗口把模板依次放到大图的每个位置计算归一化相关性系数最后保留分数最高的位置。前提是项目里必须有一张干净的缺口模板图比如素材中与 10.jpg 配套的 10_1.png。bg cv2.imread(10.jpg, cv2.IMREAD_GRAYSCALE) tpl cv2.imread(10_1.png, cv2.IMREAD_GRAYSCALE) th, tw tpl.shape[:2] bh, bw bg.shape[:2] if th bh or tw bw: raise ValueError(template should be smaller than image) # 相关性系数-1 到 1越大越相似 res cv2.matchTemplate(bg, tpl, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) # 归一化平方差0 表示完全一致作为交叉验证 res_sq cv2.matchTemplate(bg, tpl, cv2.TM_SQDIFF_NORMED) min_val, _, min_loc, _ cv2.minMaxLoc(res_sq)TM_CCOEFF_NORMED 在匹配前会分别对模板和滑动窗口做均值去零因此对整体亮度偏移不敏感适合验证码背景忽明忽暗的情况。max_loc 是响应图最大值的位置也就是模板左上角落在原图的坐标最终缺口中心需要再加上模板宽度和高度的一半。TM_SQDIFF_NORMED 取值越小越匹配正常样本里两个方法的结论应该落在相邻像素范围内。如果 CCOEFF 峰值很高但 SQDIFF 也很好说明匹配到的位置虽然相似度不错但和模板整体反差不同多半是匹配到了背景里的高亮装饰而不是缺口边缘需要检查模板是否截到了缺口旁边的阴影残留。4.3 模板缩放与匹配得分的取舍matchTemplate 对尺寸非常敏感模板比实际缺口大一两行像素分数就会从 0.9 跌到 0.6。验证码图片在浏览器里经过 CSS 缩放后再截图和本地保存的原始尺寸之间经常差一个比例所以项目里我会直接做一个多尺度版本的匹配函数。模板按 0.8 到 1.2 的区间分档缩放每个尺度独立跑一次保留分数最高的结果。found None for scale in np.linspace(0.8, 1.2, 9): nw max(1, int(round(tw * scale))) nh max(1, int(round(th * scale))) resized cv2.resize(tpl, (nw, nh), interpolationcv2.INTER_LINEAR) if nh bh or nw bw: continue res cv2.matchTemplate(bg, resized, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) if found is None or max_val found[0]: found (max_val, max_loc, scale)np.linspace(0.8, 1.2, 9) 生成 0.8、0.85、0.9 直到 1.2 共 9 个比例步长 0.05。tw、th 是模板长宽计算缩放后的尺寸必须用 int(round(...))因为 cv2.resize 不接受浮点数尺寸。取到的是 max_val 最大的那次对应的 max_loc 是缩放后模板的左上角坐标最终用 x / scale 换算回原图坐标系。多尺度计算量是单尺度的 9 倍但验证码原图通常在几百像素宽单次 matchTemplate 在毫秒级整体仍然远快于加载一个深度学习模型。5. 缺口坐标换算、批量验证与三个调试技巧5.1 从缺口中心换算滑动距离识别脚本输出的是像素坐标自动化执行时真正需要的是滑块移动距离。换算关系是 distance (缺口中心 x - 滑块初始中心 x) * 缩放比例其中缩放比例是图片宽度除以页面实际渲染宽度。滑块初始位置需要从页面量一次y 方向在滑块场景里很少需要处理最终把 distance 传给 yd_hk.py 这类动作脚本即可。这段换算单独放一个函数识别脚本只输出坐标不掺和页面逻辑。5.2 描边、分数阈值和中间结果三个细节第一个常见偏差来自描边。缺口的边缘经常带一圈高亮描边二值化之后描边和缺口会连成一个更大的环直接取轮廓外接矩形的中心会偏二到五个像素。我的做法是在候选矩形内再做一次最大连通域分析取缺口填充区域的质心而不是整体外接矩形的中心。第二个容易翻车的是匹配分数阈值。TM_CCOEFF_NORMED 的分数在不同光照和 JPEG 压缩强度下波动很大同一个缺口在干净背景上能到 0.95在复杂背景上只有 0.65。固定一个 0.8 会把正常样本误杀固定 0.5 又会放过高亮装饰所以先抽二十张图跑一遍按分数分布定阈值。第三个是中间结果必须落地。每次跑 threshold 或 findContours 都把二值化和轮廓绘制结果写进 output 目录批量失败时打开图片第一眼就能判断是预处理问题还是筛选规则问题比反复改参数盲试快得多。5.3 用最小回归集固定指标验证import csv, cv2 total pass_cnt 0 with open(labels.csv, encodingutf-8) as f: for row in csv.DictReader(f): img cv2.imread(samples/ row[name]) x, y detect_gap(img) dist ((x - int(row[gt_x])) ** 2 (y - int(row[gt_y])) ** 2) ** 0.5 total 1 if dist 3: pass_cnt 1 print(fpass_rate{pass_cnt / total:.2%})labels.csv 每行是 name、gt_x、gt_ydist 是预测点与标注点的欧氏距离3 像素以内算通过。每次调完参数跑一遍回归通过率下降就打印误差最大的前十条再配合 cv2.drawMarker 把检测点和标注点画在同一张图上一眼能看出是被描边带偏还是被高光干扰。这个回归集保持在 20 到 50 张就够用比反复人工目测靠谱得多。本文还有配套的精品资源点击获取