首页
社区
课程
招聘
[原创]腾讯验证码识别之“点击运动最慢的元素”分析
发表于: 2026-9-10 13:10 382

[原创]腾讯验证码识别之“点击运动最慢的元素”分析

2026-9-10 13:10
382

验证码中有一类动态题型:画面包含多个运动元素,要求点击运动最慢的一个。此类问题属于典型的时序分析任务,可拆解为前景检测、多目标跟踪与速度比较三个环节。本文基于 Python + OpenCV 给出一种实现方案,重点说明前景提取、坐标计算与轨迹速度统计的方法构成。
一、问题定义
输入:一段 WebM 短视频,画面中存在若干近似圆形的运动元素,其中可能混入完全静止的干扰元素。输出:运动最慢元素的中心坐标 (x, y)。需要处理的难点:
完全静止元素的速度恒为 0,必须在速度比较之前剔除;
元素存在交错、短暂消失等情况,跨帧关联必须保证一对一匹配;
输出坐标必须对应提交时刻的元素位置,而非历史某一帧的位置。
二、总体流程
视频读取 → 背景建模 → 前景提取 → 轮廓筛选 → 中心坐标计算
→ 跨帧轨迹关联 → 速度统计 → 静止元素剔除 → 最慢目标输出
三、视频读取
使用 cv2.VideoCapture 逐帧读取视频,获取总帧数 N 与帧率 fps。轨迹参与统计需要满足最少观测帧数下限 min_frames,该值由总帧数推导(经验公式形如 max(a, b·N),a、b 为常量)。观测帧数不足的目标不参与判定,避免小样本统计失真。视频处理完毕后,下载到本地的临时文件需要及时删除。
四、背景建模与前景提取
4.1 背景建模
直接在原图上搜索轮廓会引入大量背景干扰,因此先建立背景模型。采用自适应背景差分:为每个像素维护随时间缓慢更新的背景模型 B_t(x, y),当前帧与模型比较后,变化明显的区域即为运动目标。相比取固定帧的中值或均值作为背景,自适应模型对光照缓慢变化和画面轻微抖动更稳定。OpenCV 的 MOG2 类算法即属于此类实现。
4.2 前景提取链路
对当前帧 frame_t 的处理顺序:
灰度化 → 高斯模糊 → 背景差分 → 阈值二值化 → 形态学开运算与闭运算
差分结果 fg_t = |gray_t − B_t|,经阈值二值化得到前景掩码 mask_t;开运算清除孤立噪点,闭运算填补目标内部空洞。mask_t 中高亮区域即候选运动元素。
五、候选轮廓筛选
对前景掩码使用 cv2.findContours 提取外部轮廓,随后进行几何筛选。
5.1 面积筛选
轮廓面积记为 A,满足
min_area ≤ A ≤ max_area
面积过小的轮廓视为噪点,过大的视为背景误检。
5.2 圆度筛选
对轮廓求最小外接圆,得到圆心与半径 (cx, cy, r),圆度定义为
circularity = A / (π·r²)
即轮廓面积占外接圆面积的比例,越接近 1 越接近圆。低于圆度下限 min_circularity 的轮廓直接剔除。两类典型干扰在此阶段或后续阶段处理:细线条纹类干扰形状细长、圆度低,在几何筛选阶段排除;完全静止的大圆形状合格但速度为零,由速度统计阶段剔除。
六、中心坐标计算
通过筛选的轮廓直接采用最小外接圆的圆心 (cx, cy) 作为元素中心。选择该方案的原因:
对近似圆形目标,外接圆圆心与质心基本重合;
圆心与半径一次计算同时得到,半径可复用于圆度判定;
对轻微遮挡和边缘缺损,外接圆圆心比质心更稳定。
单帧坐标计算链路:
前景掩码 → 轮廓提取 → 面积筛选 → 圆度筛选 → 最小外接圆 → 中心坐标 (cx, cy, r)
七、跨帧轨迹关联
7.1 轨迹表示
轨迹定义为元素在多帧中的位置序列:
track = [(f1, x1, y1), (f2, x2, y2), ...]
7.2 关联策略
相邻帧之间构造距离矩阵:
cost(i, j) = ||p_i − q_j||
p_i 为已有轨迹的末位置,q_j 为当前帧检测点。关联规则:
距离超过关联阈值 link_distance 视为不可匹配;
循环取全局最小距离,完成一对一贪心配对;
未匹配的检测点创建新轨迹;未匹配的旧轨迹丢失计数加 1,超过 lost_limit 后删除。
采用全局配对而非逐目标最近邻搜索,是为了避免元素交错时两条轨迹争夺同一检测点导致串号。
关联阈值 link_distance 与丢失容忍 lost_limit 决定轨迹连续性:阈值过大容易串号,过小容易断线,需要根据实际素材调整。
八、速度统计与最慢目标判定
8.1 轨迹平均速度
对轨迹内相邻帧位置累计路程:
total_distance = Σ ||p_k − p_{k−1}||
avg_speed = total_distance / len(track)
采用累计路程而非首尾直线距离,原因在于元素可能折返或绕圈运动,直线距离无法反映实际运动量。
8.2 静止目标剔除
满足
net_distance < ε1 且 total_distance < ε2
的轨迹判定为完全静止(ε1、ε2 为小量阈值,net_distance 为起点到当前点的直线距离),不参与最慢判定。
8.3 最慢目标输出
有效轨迹按 avg_speed 升序排序,取第一条,返回其末位置坐标 (last_x, last_y)。返回末位置的原因:提交答案时元素仍在运动,末位置对应当前时刻的位置,与题目要求一致。
九、核心代码
实例代码...
9.1 单帧候选提取
for frame in frames:
mask = preprocess(frame)

灰度 → 模糊 → 差分 → 阈值 → 形态学
detections = []
for cnt in find_contours(mask):
area = contour_area(cnt)
if not (min_area <= area <= max_area):
continue
(cx, cy, r) = min_enclosing_circle(cnt)
if circularity(area, r) < min_circularity:
continue
detections.append((cx, cy, r))
detections_by_frame.append(detections)
9.2 跨帧轨迹关联
tracks = {}
for frame_index, detections in enumerate(detections_by_frame):
if tracks and detections:
cost = distance_matrix(track_tails, detections)
for (i, j) in greedy_one_to_one_match(cost, link_distance):
tracks[i].append(detections[j])

未匹配的旧轨迹:lost += 1,超过 lost_limit 删除

未匹配的新检测点:创建新轨迹

9.3 最慢目标判定
valid = [t for t in tracks
if len(t) >= min_frames and not is_completely_static(t)]
if not valid:
return None
valid.sort(key=lambda t: avg_speed(t))
slowest = valid[0]
return slowest.last_pos
9.4 对外接口
def get_slowest_point(url: str):
webm_path = download_webm(url)
try:
slowest = detect_slowest_dot(webm_path)
return slowest.last_pos if slowest else None
finally:
remove(webm_path)
十、工程要点
临时文件通过 try/finally 兜底清理,识别失败也不残留;
识别失败返回空值,由上层决定重试或放弃,不输出无效坐标;
识别模块只暴露单一接口(视频 URL → 坐标),与验证码整体流程解耦,便于与其它题型共用接入模式。
十一、方法特点与局限
方法特点:
无需训练样本,不依赖深度学习模型;
计算开销小,可毫秒级完成;
对元素颜色与背景形态不敏感;
处理环节独立,可逐环节调试与可视化验证。
局限性:目标遮挡严重、轨迹密集交叉、运动突变明显的场景下,基于距离的简单关联容易串号,需要引入运动估计与全局优化类跟踪方法。
结语
该方法将“谁在动”转化为“谁动了多少”,以前景差分负责目标发现、几何筛选负责干扰剔除、轨迹关联负责身份保持、速度统计负责排序判定,最终输出的坐标具有明确的计算来源与时序语义。

公众号:JavaScript逆向笔记
来源:2e4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7q4)9J5k6i4N6W2K9i4S2A6L8W2)9J5k6i4q4I4i4K6u0W2j5$3!0E0i4K6u0r3M7#2)9J5c8U0q4V1y4%4b7@1L8e0y4W2K9o6u0v1c8h3&6Y4f1p5)9H3h3i4y4J5K9@1p5`.


冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
点赞 ・0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回