吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 570|回复: 8
上一主题 下一主题
收起左侧

[Web逆向] 顶象滑块的x问题!

[复制链接]
跳转到指定楼层
楼主
guxing666 发表于 2026-4-10 23:48 回帖奖励

问题

案例:B 站UP主 蛋蛋yong闯小B站 的实战案例

对于同一组图片,本地识别出来的图片获得 x 距离,和网页提交的距离不一样!

本地识别:得到距离是 165
*

网页上:得到的距离是 128

解决方法

研究了好几天,一直解决不了,最后还是靠 AI 解决的,所以下面直接给出 AI 的分析

结论

本地图片识别得到的值,更接近“图上缺口的视觉位置”。

网页最终提交给验证码校验接口的 dx,并不是这个视觉位置本身,而是经过以下因素换算后的值:

  • 滑块小图在图内有固定初始偏移 left: 10px
  • 前端拖动时,图上小滑块的位置使用了 speed 比例
  • speed 在代码中会随机取 0.91.11.2 之一

所以:

图上缺口视觉位置 = 10 + dx * speed
dx = (图上缺口视觉位置 - 10) / speed

这意味着:

  • 165128 不一致,不一定是识别错了
  • 更可能是“视觉坐标”和“提交坐标”本来就不同

speed 会参与校验上报

前端上报轨迹数据时,也会带上 speed

{
    "x": B,
    "y": T,
    "speed": t.speed,
    "dt": _dx.dt
}

这进一步说明 speed 是这个验证码逻辑里的有效参数,不是纯前端展示细节。

对 165 和 128 的解释

如果本地图片识别得到的 165 是图上的视觉位置,那么根据上面的关系:

dx = (165 - 10) / speed

若本次 speed = 1.2,则:

dx = (165 - 10) / 1.2
   = 155 / 1.2
   = 129.17

这个结果和网页上的 128 已经非常接近。

考虑到下面这些因素,出现 1 像素左右的差值是正常的:

  • 图片识别算法本身会有 1 到数像素误差
  • 页面上取值可能经过 Math.roundparseInt
  • 人眼在截图中量出来的位置不一定是精确像素
  • 浏览器渲染和截图时可能存在轻微缩放

因此,165128 之间的差距,主要不是因为图片识别失败,而是因为坐标系不同。

最终判断

本地识别出来的 165 更像是图上的视觉位置;
网页实际校验使用的 128 更像是经过 left: 10pxspeed 反算后的拖动距离。

所以,这个问题的本质更接近:

  • 坐标换算问题

而不是:

  • 单纯的图片识别错误

小结

  • 其他参数按照视频基本都可以搞出来。
  • 这个站点的轨迹校验不严格,网上随便生成都可以
  • 若是出现二次验证则是 IP 问题

识别代码也是 B 站 UP 主提供的

# ============================================================
# 最优滑块识别算法(FFT加速版)
# ============================================================
# 基于测试结果:
#   - 准确度:与原始方法完全一致(212px, score=0.5516)
#   - 速度:10.3倍提升(381ms -> 37ms)
#   - 结论:单纯FFT加速是最优方案,多尺度反而降低准确度
# ============================================================

import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
from scipy.ndimage import gaussian_filter, sobel
from scipy.signal import fftconvolve

# ============================================================
# 辅助函数:兼容PNG/WebP的图像加载
# ============================================================
def load_image(img_path):
    """
    加载图像,优先使用matplotlib,失败时回退到Pillow。

    这样可以兼容“扩展名是.png,但实际内容是WebP”这类情况,
    同时把整型图像统一归一化到0-1范围,尽量保持原有PNG读取行为。
    """
    try:
        return plt.imread(img_path)
    except FileNotFoundError:
        raise
    except Exception:
        try:
            from PIL import Image
        except ImportError as exc:
            raise RuntimeError(
                "当前图片无法由matplotlib直接读取,请安装Pillow以兼容PNG/WebP读取:pip install pillow"
            ) from exc

        with Image.open(img_path) as img:
            img_array = np.array(img)

        if np.issubdtype(img_array.dtype, np.integer):
            max_value = np.iinfo(img_array.dtype).max
            img_array = img_array.astype(np.float32) / max_value

        return img_array

# ============================================================
# 辅助函数:RGB/RGBA转灰度图
# ============================================================
def rgb2gray(img):
    """
    将RGB或RGBA图像转换为灰度图,并提取Alpha透明度通道

    Args:
        img: numpy数组格式的图像

    Returns:
        (gray, alpha): 灰度图像数组和透明度通道(如果有)
    """
    if len(img.shape) == 3:
        if img.shape[2] == 4:
            gray = np.dot(img[..., :3], [0.2989, 0.5870, 0.1140])
            alpha = img[..., 3]
            return gray, alpha
        else:
            return np.dot(img[..., :3], [0.2989, 0.5870, 0.1140]), None
    return img, None

# ============================================================
# 核心优化:FFT加速的NCC计算
# ============================================================
def compute_ncc_scores_fft(edge_bg, edge_template_masked, mask):
    """
    使用FFT加速计算所有位置的NCC分数

    原理:
        传统方法:for循环逐个位置计算,O(N*M) 复杂度
        FFT方法:利用卷积定理一次性计算所有位置,O(N*logN) 复杂度
        结果:完全一致,但速度提升10倍以上

    Args:
        edge_bg: 背景图的边缘特征
        edge_template_masked: 应用掩码后的模板边缘特征
        mask: 掩码(标记有效区域)

    Returns:
        ncc_scores: 每个x位置的NCC分数(1D数组)
    """
    # --------------------------------------------------------
    # 步骤1:计算掩码内的有效像素数和模板统计量
    # --------------------------------------------------------
    masked_pixels = edge_template_masked[mask]

    if len(masked_pixels) == 0:
        return np.zeros(edge_bg.shape[1] - edge_template_masked.shape[1] + 1)

    # 模板的均值和方差
    mean_t = np.mean(masked_pixels)
    template_centered = edge_template_masked - mean_t * mask.astype(float)
    sum_sq_t = np.sum((masked_pixels - mean_t) ** 2)

    if sum_sq_t == 0:
        return np.zeros(edge_bg.shape[1] - edge_template_masked.shape[1] + 1)

    # --------------------------------------------------------
    # 步骤2:使用FFT计算互相关(分子部分)
    # --------------------------------------------------------
    # fftconvolve利用快速傅里叶变换加速卷积计算
    # mode='valid'表示只计算完全重叠的部分
    # 翻转模板是因为卷积需要翻转操作
    cross_corr = fftconvolve(edge_bg, template_centered[::-1, ::-1], mode='valid')

    # --------------------------------------------------------
    # 步骤3:使用FFT计算滑动窗口的局部统计量(分母部分)
    # --------------------------------------------------------
    # 计算每个窗口位置的像素和与平方和
    edge_bg_sq = edge_bg ** 2

    # 使用卷积快速计算滑动窗口的和(等价于积分图方法)
    window_sum = fftconvolve(edge_bg, mask[::-1, ::-1].astype(float), mode='valid')
    window_sq_sum = fftconvolve(edge_bg_sq, mask[::-1, ::-1].astype(float), mode='valid')

    # 计算每个窗口的像素数(掩码内的有效像素)
    n_pixels = np.sum(mask)

    # --------------------------------------------------------
    # 步骤4:计算NCC分数
    # --------------------------------------------------------
    # 计算窗口的均值和方差
    window_mean = window_sum / n_pixels
    window_var = window_sq_sum / n_pixels - window_mean ** 2
    window_var = np.maximum(window_var, 0)  # 避免浮点误差导致的负数
    sum_sq_p = window_var * n_pixels

    # 计算分母(标准差的乘积)
    denominator = np.sqrt(sum_sq_p * sum_sq_t)

    # 计算NCC分数(避免除以0)
    ncc_scores = np.zeros_like(cross_corr)
    valid_mask = denominator > 1e-8

    # 只在有效位置计算NCC
    if np.any(valid_mask):
        # 如果是2D结果,取每列的最大值(假设高度对齐)
        if len(cross_corr.shape) == 2:
            cross_corr_1d = np.max(cross_corr, axis=0)
            denominator_1d = np.max(denominator, axis=0)
            valid_mask_1d = denominator_1d > 1e-8
            ncc_scores = np.zeros_like(cross_corr_1d)
            ncc_scores[valid_mask_1d] = cross_corr_1d[valid_mask_1d] / denominator_1d[valid_mask_1d]
        else:
            ncc_scores[valid_mask] = cross_corr[valid_mask] / denominator[valid_mask]

    return ncc_scores

# ============================================================
# 亚像素插值(高斯拟合)
# ============================================================
def subpixel_refinement(ncc_scores, best_int):
    """
    使用高斯拟合进行亚像素插值,提高精度

    Args:
        ncc_scores: NCC分数数组
        best_int: 整数精度的峰值位置

    Returns:
        best_subpixel: 亚像素精度的位置
    """
    positions = len(ncc_scores)

    # 检查是否有左右邻居点(边界位置无法插值)
    if 0 < best_int < positions - 1:
        # 获取三个点的分数
        s_prev = ncc_scores[best_int - 1]
        s_curr = ncc_scores[best_int]
        s_next = ncc_scores[best_int + 1]

        # 使用二次拟合(与原算法保持一致)
        denom = 2 * (s_prev - 2 * s_curr + s_next)

        if denom != 0:
            # 计算亚像素偏移量
            frac = (s_prev - s_next) / denom
            # 限制偏移量在合理范围内
            frac = np.clip(frac, -0.5, 0.5)
            return best_int + frac

    return float(best_int)

# ============================================================
# 主函数:最优滑块距离识别算法
# ============================================================
def detect_slider_distance(bg_path, slider_path, axis=0, sigma=1, alpha_thresh=0.5):
    """
    最优滑块距离识别算法(FFT加速版)

    特点:
        ✅ 准确度:与原始方法完全一致
        ✅ 速度:10倍以上提升(381ms -> 37ms)
        ✅ 稳定性:经过实际测试验证

    Args:
        bg_path: 背景图路径(带有缺口的完整图片)
        slider_path: 滑块图路径(需要匹配的小块图片)
        axis: 边缘检测方向,0表示垂直边缘(默认)
        sigma: 高斯模糊的标准差(默认1)
        alpha_thresh: Alpha通道阈值(默认0.5)

    Returns:
        (distance, score):
            distance - 滑块应该移动到的x坐标位置(整数)
            score - 最佳匹配位置的NCC分数(0-1之间)
    """

    # ============================================================
    # 步骤1:加载图像并转换为灰度图
    # ============================================================
    bg_img = load_image(bg_path)
    template_img = load_image(slider_path)

    bg, _ = rgb2gray(bg_img)
    template, alpha = rgb2gray(template_img)

    # ============================================================
    # 步骤2:创建掩码
    # ============================================================
    if alpha is not None:
        mask = alpha > alpha_thresh
    else:
        mask = np.ones(template.shape, dtype=bool)

    # ============================================================
    # 步骤3:边缘检测
    # ============================================================
    # 高斯模糊降噪
    bg_blur = gaussian_filter(bg, sigma=sigma)
    template_blur = gaussian_filter(template, sigma=sigma)

    # Sobel边缘检测(垂直边缘)
    edge_bg = np.abs(sobel(bg_blur, axis=axis))
    edge_template = np.abs(sobel(template_blur, axis=axis))

    # 应用掩码到模板
    edge_template_masked = edge_template * mask.astype(float)

    # ============================================================
    # 步骤4:FFT加速的NCC匹配(核心优化)
    # ============================================================
    ncc_scores = compute_ncc_scores_fft(edge_bg, edge_template_masked, mask)

    # ============================================================
    # 步骤5:找到最佳匹配位置
    # ============================================================
    best_int = np.argmax(ncc_scores)
    max_score = ncc_scores[best_int]

    # ============================================================
    # 步骤6:亚像素插值
    # ============================================================
    best_subpixel = subpixel_refinement(ncc_scores, best_int)

    # ============================================================
    # 步骤7:返回结果
    # ============================================================
    return int(best_subpixel), max_score

# ============================================================
# 对外封装:只返回滑块移动距离
# ============================================================
def get_slider_move_distance(bg_path, slider_path, axis=0, sigma=1, alpha_thresh=0.5):
    """
    传入背景图路径和滑块图路径,只返回滑块移动距离。

    Args:
        bg_path: 背景图路径
        slider_path: 滑块图路径
        axis: 边缘检测方向,默认0
        sigma: 高斯模糊标准差,默认1
        alpha_thresh: Alpha通道阈值,默认0.5

    Returns:
        distance: 滑块移动距离(整数像素)
    """
    distance, _ = detect_slider_distance(
        bg_path,
        slider_path,
        axis=axis,
        sigma=sigma,
        alpha_thresh=alpha_thresh,
    )
    return distance

# ============================================================
# 可视化辅助函数
# ============================================================
def draw_result(bg_path, distance, output_path='result.png', color='red', linewidth=2):
    """
    在背景图上绘制识别结果

    Args:
        bg_path: 背景图路径
        distance: 识别出的距离
        output_path: 输出图片路径
        color: 线条颜色
        linewidth: 线条宽度
    """
    bg = plt.imread(bg_path)
    fig, ax = plt.subplots(
        figsize=(bg.shape[1] / 100, bg.shape[0] / 100),
        dpi=100,
    )
    fig.subplots_adjust(0, 0, 1, 1)
    ax.imshow(bg)
    ax.axvline(x=distance, color=color, linewidth=linewidth)
    ax.axis('off')
    plt.savefig(output_path, dpi=100, pad_inches=0)
    plt.close()
    print(f"结果图已保存: {output_path}")

# ============================================================
# 使用示例
# ============================================================
if __name__ == '__main__':
    import time

    base_dir = Path(__file__).resolve().parent
    bg_path = base_dir / "result.png"
    slider_path = base_dir / "slide.png"
    output_path = base_dir / "result_marked.png"

    print("=" * 70)
    print("最优滑块识别算法(FFT加速版)")
    print("=" * 70)

    try:
        # 计时
        start_time = time.time()

        # 识别距离
        distance, score = detect_slider_distance(bg_path, slider_path)

        # 计算耗时
        elapsed_time = (time.time() - start_time) * 1000

        # 输出结果
        print("\n识别成功")
        print(f"   背景图: {bg_path.name}")
        print(f"   滑块图: {slider_path.name}")
        print(f"   滑块移动距离: {distance} px")
        print(f"   匹配分数: {score:.4f}")
        print(f"   识别耗时: {elapsed_time:.2f} ms")

        # 可视化结果(可选)
        draw_result(bg_path, distance, output_path)
        print(f"   标注结果: {output_path.name}")

        print("\n" + "=" * 70)
        print("说明")
        print("  已使用脚本所在目录中的 result.png 和 slide.png 完成识别")
        print("  为避免覆盖原图,标注图输出为 result_marked.png")
        print("=" * 70)

    except FileNotFoundError as exc:
        print("\n错误:找不到图片文件")
        print(f"   缺失文件: {exc.filename}")
        print(f"   当前脚本目录: {base_dir}")
    except Exception as e:
        print(f"\n错误:{e}")

免费评分

参与人数 3威望 +1 吾爱币 +21 热心值 +3 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
段亚栋 + 1 我很赞同!
helian147 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
ahyun 发表于 2026-4-14 08:33
这算是我注册以来最有用的一次了。滑块验证、文字点选等等,之前我老想做一个自动化签到在别的社区,一直行不通。(腾讯的滑块)这么一看又有机会了。感谢分享&#128077;
3#
Xandor 发表于 2026-4-14 15:22
4#
mysega 发表于 2026-4-14 15:29
5#
chendipang 发表于 2026-4-15 23:49
现在的AI真强大
6#
段亚栋 发表于 2026-4-18 10:16
很详细,感谢楼主分享!!!
7#
段亚栋 发表于 2026-4-18 10:17
评分给楼主
8#
Bloomed 发表于 2026-4-18 23:19
感谢分享
9#
fzlte0 发表于 2026-4-20 21:03
顶象滑块,收藏了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-14 05:47

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表