エンジニアがビジュアル回帰やウェブサイト変更監視について話すとき、ピクセルレベルの差分検出アルゴリズム(pixelmatchやユークリッドRGB距離など)が通常のデフォルトソリューションです。

しかし、現実のウェブ環境では、ピクセル単位の比較は通常のユーザー操作や動的レンダリング条件下で根本的に機能しません

  1. 弾性レイアウトシフト: ページ上部に20pxの動的バナーが1つ挿入されるだけで、後続のすべてのDOM要素が押し下げられ、コンテンツ自体が変更されていなくても下流の100%のピクセルがpixelmatchテストで不合格になります。
  2. サブピクセルアンチエイリアスジッター: オペレーティングシステム(macOS vs. Linux vs. Windows)は微妙なサブピクセルアンチエイリアスのバリエーションでフォントグリフをレンダリングし、数千の偽陽性ピクセルデルタを生成します。
  3. 意味的変更 vs. 外観的変更: 段落内の1語を変更すると局所的なアラートが発生するべきですが、ヒーロー画像のマイナーなカラグラデーションシフトは緊急通知をトリガーするべきではありません。

PageWatch.techでは、古典的なStructural Similarity (SSIM)ORB特徴量アライメントSiamese Neural Networks (SNN)を組み合わせ、潜在空間での意味的比較によりこの問題を解決しました。

この記事では、コンピュータビジョンの差分パイプラインの数学、神経ネットワークアーキテクチャ、TypeScript実装について深く掘り下げます。


🧮 1. ピクセル比較を超えて:Structural Similarity Index (SSIM)

生のMean Squared Error (MSE)とは異なり、SSIMは人間の知覚に基づいて3つの次元(輝度コントラスト構造)で視覚的変化を測定します。

数学的に、2つの画像ウィンドウ$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だけでもシフトした領域がフラグ付けされます。

これを修正するため、Oriented FAST and Rotated 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. キーポイント抽出: 安定した幾何学的興味点(ボタン、ロゴの角、構造テキスト境界)を特定します。
  2. バウンディングボックス平行移動ベクトル計算: 個々のレイアウト領域の$(dx, dy)$平行移動オフセットを計算します。
  3. 剛体変換: 候補画像をSSIMデルタ計算前にベースライン画像にワープしてアライメントします。

🧠 3. Siamese Neural NetworksとONNX Runtimeによる意味的埋め込み

複雑なウェブコンポーネント(動的グラフ、変化するアバター、スタイル付きタイポグラフィなど)では、ピクセルやSSIM比較でも過度に敏感になり得ます。

これを解決するため、軽量なSiamese ResNet-18 Neural Networkを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

Embedding Vector AとVector 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. 多層コンピュータビジョンパイプライン

以下は、2つのスナップショットを比較する際に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剛体特徴量アライメントSiamese Neural Networksを組み合わせることで、99%以上の偽陽性ビジュアルアラートを排除しつつ、監視を即時かつ信頼性の高いものにできました。

ノイズのないインテリジェントなウェブサイト変更監視ツールをお試しになりたい方は、PageWatch.techをご覧ください!

SSIM実装やONNXモデルの量子化についてご質問があれば、下のコメント欄にお寄せください! 🚀