当工程师讨论视觉回归或网站变更监控时,像素级差异检测算法(例如 pixelmatch 或欧几里得 RGB 距离)通常是默认方案。

然而,在真实世界的网页环境中,逐像素比较在正常用户交互和动态渲染条件下从根本上失效

  1. 弹性布局偏移:页面顶部插入一个 20px 的动态横幅,会将后续所有 DOM 元素向下推动,导致 100% 的下游像素在 pixelmatch 测试中失败,即使内容本身并未改变。
  2. 子像素抗锯齿抖动:操作系统(macOS vs. Linux vs. Windows)渲染字体字形时存在细微的子像素抗锯齿差异,产生数千个误报像素差值。
  3. 语义变化 vs. 外观变化:段落中单个单词的改动应触发局部警报,但英雄图中的微小渐变偏移不应触发紧急通知。

PageWatch.tech,我们通过结合经典的 结构相似性指数(SSIM)ORB 特征对齐孪生神经网络(SNN) 来实现潜在空间的语义比较,从而解决了这一问题。

本文将深入探讨我们计算机视觉差异检测流程的数学原理、神经网络架构以及 TypeScript 实现。


🧮 1. 超越像素比较:结构相似性指数(SSIM)

与原始均方误差(MSE)不同,SSIM 基于人类感知从三个维度衡量视觉变化:亮度对比度结构

数学上,两幅图像窗口 $x$ 和 $y$ 之间的 SSIM 定义为:

$$\text{SSIM}(x, y) = \frac{(2\mu_x\mu_y + C_1)(2\sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1)(\sigma_x^2 + \sigma_y^2 + C_2)}$$

其中:

  • $\mu_x, \mu_y$ 是局部像素均值强度。
  • $\sigma_x^2, \sigma_y^2$ 是局部方差。
  • $\sigma_{xy}$ 是 $x$ 和 $y$ 之间的协方差。
  • $C_1, C_2$ 是稳定化常数。

SSIM 窗口滑动的 TypeScript 实现

以下是如何在截图画布缓冲区上实现 SSIM 局部窗口滑动的代码片段:

/**
 * Calculates localized Structural Similarity Index (SSIM) map 
 * across two image buffers using an 8x8 Gaussian sliding window.
 */
export function calculateSSIMMap(
  img1: Float32Array,
  img2: Float32Array,
  width: number,
  height: number,
  windowSize = 8
): { meanSSIM: number; ssimMap: Float32Array } {
  const C1 = (0.01 * 255) ** 2;
  const C2 = (0.03 * 255) ** 2;

  const numWindowsX = Math.floor(width / windowSize);
  const numWindowsY = Math.floor(height / windowSize);
  const ssimMap = new Float32Array(numWindowsX * numWindowsY);

  let totalSSIM = 0;

  for (let wy = 0; wy < numWindowsY; wy++) {
    for (let wx = 0; wx < numWindowsX; wx++) {
      let sumX = 0, sumY = 0, sumX2 = 0, sumY2 = 0, sumXY = 0;
      const count = windowSize * windowSize;

      for (let dy = 0; dy < windowSize; dy++) {
        for (let dx = 0; dx < windowSize; dx++) {
          const px = wx * windowSize + dx;
          const py = wy * windowSize + dy;
          const idx = py * width + px;

          const v1 = img1[idx];
          const v2 = img2[idx];

          sumX += v1;
          sumY += v2;
          sumX2 += v1 * v1;
          sumY2 += v2 * v2;
          sumXY += v1 * v2;
        }
      }

      const muX = sumX / count;
      const muY = sumY / count;
      const varX = sumX2 / count - muX * muX;
      const varY = sumY2 / count - muY * muY;
      const covXY = sumXY / count - muX * muY;

      const num = (2 * muX * muY + C1) * (2 * covXY + C2);
      const den = (muX * muX + muY * muY + C1) * (varX + varY + C2);
      const ssim = num / den;

      const windowIdx = wy * numWindowsX + wx;
      ssimMap[windowIdx] = ssim;
      totalSSIM += ssim;
    }
  }

  const meanSSIM = totalSSIM / (numWindowsX * numWindowsY);
  return { meanSSIM, ssimMap };
}

Enter fullscreen mode Exit fullscreen mode


🎯 2. 通过特征点对齐实现弹性布局补偿(ORB/SIFT)

当网页因新增顶部元素而向下偏移时,仅使用 SSIM 仍会标记偏移区域。

为了解决此问题,我们应用 定向 FAST 与旋转 BRIEF(ORB) 特征匹配来计算单应性矩阵,从而在差异检测前对齐动态布局偏移:

Baseline Image                 Shifted Image               Homography Corrected
┌──────────────┐              ┌──────────────┐              ┌──────────────┐
│  [ Header ]  │              │  (NEW BANNER)│              │  [ Header ]  │
│  [ Article ] │  ──Offset──> │  [ Header ]  │  ──Warp───>  │  [ Article ] │ (Aligned)
│  [ Footer ]  │              │  [ Article ] │              │  [ Footer ]  │
└──────────────┘              └──────────────┘              └──────────────┘

Enter fullscreen mode Exit fullscreen mode

  1. 提取关键点:识别稳定的几何兴趣点(按钮、Logo 角点、结构化文本边界)。
  2. 计算边界框平移向量:为各个布局区域计算 $(dx, dy)$ 平移偏移量。
  3. 刚性变换:在计算 SSIM 差值前,将候选图像回绕对齐到基线图像。

🧠 3. 通过孪生神经网络与 ONNX Runtime 实现语义嵌入

对于复杂的网页组件(如动态图表、变化的头像或带样式的排版),像素或 SSIM 比较仍可能过于敏感。

我们通过使用轻量级 孪生 ResNet-18 神经网络(运行于 ONNX Runtime)将截图区域投影到 128 维潜在特征空间 来解决这一问题。

          ┌─────────────────────┐
          │ Baseline Image Patch│ ────► [ ResNet-18 ] ────► Embedding Vector A (128d)
          └─────────────────────┘                                │
                                                                 ▼
                                                        Cos-Similarity Loss
                                                                 ▲
          ┌─────────────────────┐                                │
          │ Candidate Image Patch│ ───► [ ResNet-18 ] ────► Embedding Vector B (128d)
          └─────────────────────┘

Enter fullscreen mode Exit fullscreen mode

如果嵌入向量 A 与向量 B 之间的余弦距离小于阈值 $\epsilon$,系统将该变化视为外观/非语义(例如抗锯齿字体渲染或微小色彩平衡调整)。

ONNX Runtime 边缘推理代码片段

import * as orb from "onnxruntime-node";

let inferenceSession: orb.InferenceSession | null = null;

export async function getModelSession(): Promise<orb.InferenceSession> {
  if (!inferenceSession) {
    // Load quantized ResNet-18 model optimized for layout embedding
    inferenceSession = await orb.InferenceSession.create(
      "./models/visual_embedding_resnet18_quantized.onnx"
    );
  }
  return inferenceSession;
}

/**
 * Computes 128-dimensional latent space feature embeddings for a given visual patch.
 */
export async function computeSemanticEmbedding(
  patchFloat32Tensor: orb.Tensor
): Promise<Float32Array> {
  const session = await getModelSession();
  const feeds: Record<string, orb.Tensor> = { input: patchFloat32Tensor };

  const results = await session.run(feeds);
  const embedding = results.output.data as Float32Array;

  return embedding;
}

/**
 * Calculates Cosine Similarity between two 128d embeddings.
 */
export function cosineSimilarity(a: Float32Array, b: Float32Array): number {
  let dotProduct = 0;
  let normA = 0;
  let normB = 0;

  for (let i = 0; i < a.length; i++) {
    dotProduct += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }

  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

Enter fullscreen mode Exit fullscreen mode


⚡ 4. 多层级计算机视觉流程

以下是 PageWatch.tech 在比较两个快照时,完整计算机视觉流程的执行方式:

  1. 步骤 1:结构哈希检查(边缘) — 即时 HTML AST 检查。若相同,则提前中止(成本 = 0ms)。
  2. 步骤 2:全局 SSIM 映射生成 — 在视口 PNG 上计算滑动窗口 SSIM 映射。
  3. 步骤 3:ORB 布局对齐 — 若 SSIM 在局部区域下降,则运行 ORB 特征匹配以检测弹性页面布局偏移。
  4. 步骤 4:神经语义过滤(ONNX) — 对于剩余低 SSIM 的视觉补丁,将张量传入 ONNX ResNet-18 嵌入模型,以区分外观噪声与真实内容变化。
  5. 步骤 5:警报叠加层生成 — 仅以亮红色高亮显示已验证、高置信度的语义变化。

🏁 结论

结合经典的 SSIM 算法ORB 刚性特征对齐孪生神经网络,我们消除了 99% 以上的视觉警报误报,同时保持监控的即时性和可靠性。

若您有兴趣尝试智能、无噪的网站变更监控工具,请访问 PageWatch.tech

对我们的 SSIM 实现或 ONNX 模型量化有疑问?欢迎在下方评论! 🚀