Skip to content

Find changed regions between frames

CookbookMotion and matching

Compare two frames, suppress noise, clean the difference mask and draw connected changed regions.

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 / 1Smoothed frame pair896 × 320
Smoothed frame pair: Both grayscale frames receive the same Gaussian blur before comparison. The second frame is on the right.

Both grayscale frames receive the same Gaussian blur before comparison. The second frame is on the right.

STEP 01 / 03

Suppress image noise

Explore the algorithm →
GaussianBlur

Gaussian smoothing reduces isolated pixel differences.

Receives
Two equally sized grayscale frames.
Passes to the next step
Two smoothed frames on the same coordinate grid.

Why this step? Small sensor fluctuations and subpixel changes would create many isolated differences. Applying the same 5×5 Gaussian blur to both images suppresses some of that variation before subtraction, so thresholding is less sensitive to individual noisy pixels.

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

For aligned views, a pixel that changes enough is evidence of a changed region. The chain first suppresses small fluctuations, then turns differences into a mask, and finally turns that mask into objects you can count or box.

  1. 01

    Suppress image noise

    GaussianBlur

    Small sensor fluctuations and subpixel changes would create many isolated differences. Applying the same 5×5 Gaussian blur to both images suppresses some of that variation before subtraction, so thresholding is less sensitive to individual noisy pixels.

    Receives
    Two equally sized grayscale frames.
    Passes on
    Two smoothed frames on the same coordinate grid.
  2. 02

    Threshold frame differences

    absdiffthreshold

    Absolute difference measures the magnitude of change without cancelling brightening against darkening. Thresholding converts that continuous evidence into a decision mask: white means the change exceeds your chosen intensity threshold.

    Receives
    The smoothed grayscale frames.
    Passes on
    An 8-bit binary changed-pixel mask, plus an inspectable difference image.
  3. 03

    Group changed pixels

    morphologyExMORPH_CLOSEconnectedComponentsWithStats

    Closing joins small gaps within changed regions. Connected components assign a label to each contiguous region and compute its area and bounds. Filtering by area removes remaining tiny regions before drawing boxes.

    Receives
    The changed-pixel mask.
    Passes on
    Boxes and component IDs for changed regions large enough to retain.

Tune and diagnose

Choose the parameters

Raise the difference threshold to ignore smaller intensity changes. Increase the cleanup kernel only enough to reconnect fragmented regions; large kernels merge nearby objects. Minimum area is measured after preprocessing and mask cleanup.

Read the result

If most of the scene becomes white, inspect camera movement or lighting changes first. Align the images before this chain when the camera moves. A moving object can create two changed regions, where it was and where it is now; this recipe does not estimate a motion vector.

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 ↗

Compare two frames, suppress noise, clean the difference mask and draw connected changed regions.

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

Camera movement and lighting changes also appear as motion. Use aligned frames for object-motion analysis.

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,
  GaussianBlur,
  MORPH_CLOSE,
  MORPH_ELLIPSE,
  Mat,
  THRESH_BINARY,
  absdiff,
  connectedComponentsWithStats,
  cvtColor,
  getStructuringElement,
  morphologyEx,
  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 frames, an intensity-difference image and a binary mask.
using a = new Mat(), b = new Mat(), difference = new Mat(), mask = new Mat()
// Represent the first frame as brightness instead of three colour channels.
cvtColor(image, a, COLOR_BGR2GRAY)
// Use the same representation for the second frame; they must already align.
cvtColor(nextImage, b, COLOR_BGR2GRAY)
// Smooth the first frame with a 5x5 kernel and sigma 1 pixel to suppress small noise.
GaussianBlur(a, a, { width: 5, height: 5 }, 1)
// Apply the identical smoothing to the second frame before comparing them.
GaussianBlur(b, b, { width: 5, height: 5 }, 1)
// 2. Measure absolute brightness change, so brightening and darkening both count.
absdiff(a, b, difference)
// Changes greater than 25 intensity levels become 255 (white); others become 0.
// This makes a decision mask from continuous difference evidence.
threshold(difference, mask, 25, 255, THRESH_BINARY)
// Choose a small elliptical 3x3 neighbourhood for cleaning that mask.
using kernel = getStructuringElement(MORPH_ELLIPSE, { width: 3, height: 3 })
// Closing dilates then erodes, joining small gaps within changed regions.
morphologyEx(mask, mask, MORPH_CLOSE, kernel)
// 3. Prepare one label per pixel, per-region statistics, and x,y centroids.
using labels = new Mat(), stats = new Mat(), centres = new Mat()
// Group connected white pixels. stats rows contain left, top, width, height, area;
// row/label 0 is background. The full lab filters small areas and draws region boxes.
connectedComponentsWithStats(mask, labels, stats, centres)

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