Skip to content

Highlight structural image differences

CookbookMotion and matching

Compute a local SSIM map, threshold low similarity and highlight contiguous differences on the second image.

Try the recipe ↓

The pipeline

VISUAL WALKTHROUGHMotion and matching
Follow the images, then read what passes to the next algorithm.
STARTING IMAGEFirst frame448 × 320
The unchanged source image. All steps use this same example.

The unchanged source image. All steps use this same example.

STEP 01 · 1 / 1Local SSIM448 × 320
Local SSIM: Native values measure local structural similarity; the preview is normalized.

Native values measure local structural similarity; the preview is normalized.

STEP 01 / 03

Measure structural similarity

Explore the algorithm →
quality_QualitySSIM_compute

SSIM compares local means, contrast and structure.

Receives
Two aligned grayscale images.
Passes to the next step
A floating local SSIM map and a mean similarity score.

Why this step? SSIM compares local means, contrast and correlation. Its map tells you where neighbourhood structure agrees; the mean score alone would hide the location of differences. The lab retains the native values because display normalization changes their visual brightness.

Actual OpenCV 5.0.0 results on the illustrated sample, using the lab’s default algorithm settings. Masks, overlays and normalized fields are labelled previews; the data contracts above describe what the algorithms really exchange. Try this chain with your images ↓

Why this chain works

When local appearance changes matter more than raw per-pixel brightness error, compare neighbourhood structure first. Then localize low-similarity areas with the same mask-to-region machinery used for change detection.

  1. 01

    Measure structural similarity

    quality_QualitySSIM_compute

    SSIM compares local means, contrast and correlation. Its map tells you where neighbourhood structure agrees; the mean score alone would hide the location of differences. The lab retains the native values because display normalization changes their visual brightness.

    Receives
    Two aligned grayscale images.
    Passes on
    A floating local SSIM map and a mean similarity score.
  2. 02

    Select low-similarity pixels

    thresholdTHRESH_BINARY_INVMat.convertTo

    High values indicate greater similarity. Inverse thresholding selects values below the minimum similarity, then conversion to 8-bit gives morphology and component labelling the binary format they require.

    Receives
    The native SSIM map.
    Passes on
    An 8-bit mask of locally dissimilar pixels.
  3. 03

    Draw difference regions

    morphologyExMORPH_CLOSEconnectedComponentsWithStats

    Closing connects nearby fragments of one difference. Component statistics provide region bounds and area, so a minimum-area decision removes small isolated changes without discarding the numeric SSIM evidence.

    Receives
    The low-similarity mask.
    Passes on
    Outlined difference regions on the second image.

Tune and diagnose

Choose the parameters

Raising minimum SSIM marks more subtle differences. Lower it to focus on large structural disagreements. Tune cleanup and area only after the raw map identifies the differences you care about.

Read the result

Misregistration often produces differences along almost every edge. Align first. This grayscale chain can miss colour-only changes and does not know which differences are meaningful to a person.

Try it with your images

Choose an input image and a second image. Without uploads, the lab uses a labelled synthetic pair. Run the recipe, then use the stage buttons to inspect intermediate results without rerunning it.

YOUR IMAGE · REAL OPENCV

Experiment at pixel level

Open full lab ↗

Compute a local SSIM map, threshold low similarity and highlight contiguous differences on the second image.

The engine loads on your first run. Your images stay in this browser.

Input448 × 320
OutputWaiting for a result

Scroll over either image to zoom at the pointer. Use the scrollbars to pan both views over the same relative area. Zoom is relative to the input; pixel coordinates belong to each image. Warps can change scene correspondence.

Pixel inspector RGBA · native values · matched scale · 9 × 9 output pixels
Hover to inspect. Click to pin a pixel.
Input
Select a pixel

Output
Select a pixel

Sample models and licenses

Assumptions and limits

Images must already be aligned. SSIM is not a semantic assessment and colour-only changes may be missed in this grayscale recipe.

TypeScript core chain

Initialize the shared engine once with await initOpenCV(), then use these named imports. In a bundled browser app, pass the WASM URL as shown in the quickstart. image is an 8-bit BGR Mat from that same engine; paired recipes receive an equally sized nextImage. Region recipes use an in-bounds pixel rect. Read outputs before the using scope ends. See matrix ownership.

import {
  COLOR_BGR2GRAY,
  CV_8U,
  MORPH_CLOSE,
  MORPH_ELLIPSE,
  Mat,
  THRESH_BINARY_INV,
  cvtColor,
  getStructuringElement,
  morphologyEx,
  quality_QualitySSIM_compute,
  threshold
} from '@banou/opencv-wasm'

// The engine is already initialized; image is an 8-bit BGR Mat.
// nextImage is the equally sized second frame from the same engine.
// "using" releases native handles at scope exit; inspect or copy outputs before then.

// 1. Allocate grayscale images, a floating similarity map, and a decision mask.
using a = new Mat(), b = new Mat(), similarity = new Mat(), mask = new Mat()
// Use grayscale for the first image; this recipe does not compare colour alone.
cvtColor(image, a, COLOR_BGR2GRAY)
// Convert the second, already-aligned image to the same grayscale representation.
cvtColor(nextImage, b, COLOR_BGR2GRAY)
// SSIM compares local brightness, contrast and structure. similarity receives
// the local scores; score[0] is their overall grayscale-channel summary.
const score = quality_QualitySSIM_compute(a, b, similarity)
// 2. Mark similarity values at or below 0.8 white. The inverse threshold selects
// disagreement: a larger minimum similarity is stricter and marks more differences.
threshold(similarity, mask, 0.8, 255, THRESH_BINARY_INV)
// Thresholding kept the float depth; convert to an 8-bit mask for later region labelling.
mask.convertTo(mask, CV_8U)
// Choose a 3x3 ellipse so cleanup only bridges small spatial gaps.
using kernel = getStructuringElement(MORPH_ELLIPSE, { width: 3, height: 3 })
// 3. Closing reconnects fragmented differences before region labelling in the lab.
morphologyEx(mask, mask, MORPH_CLOSE, kernel)
// Keep the mean score as a summary, but inspect the map to see where changes occurred.
console.log('Mean grayscale SSIM:', score[0])

The snippets isolate the core operations. The complete runnable recipes also include validation, filtering, overlays, intermediate previews and resource cleanup.