Task 4 of 6

Erode and Dilate: the Sweep, With Min and Max

A fresh mask is never clean. Stray single pixels where a highlight caught the sensor; single missing pixels where a shape had a dark fleck. Morphology is the repair kit, and it is built out of two operations you have, in a real sense, already written.

In Convolution & Filters the 3×3 window read nine samples, multiplied them by nine weights and added them up. Keep the window, keep the nine reads, keep the clamped edges — and replace the weighted sum with a minimum. That is erosion: a pixel survives only if every one of its neighbours is foreground, so shapes lose a one-pixel rind and lone specks vanish. Replace it with a maximum and you have dilation: a pixel lights up if any neighbour does, so shapes gain a rind and small holes close over.

Same access pattern, different reduction operator. That is worth saying out loud, because it generalises: a neighbourhood sweep is a shape, and what you do with the nine values you gathered is a separate decision. Sum them and you have a filter; take their extreme and you have morphology.

The window has a name — the structuring element — and a 3×3 square is the plainest one there is. Edges: this module clamps, so a sample that falls off the frame reuses the nearest in-bounds cell, exactly as the box blur did. Treating out-of-bounds as background is just as defensible, and it is a different answer: a shape lying flush against the frame erodes away along that edge instead of surviving it. Four of the shapes in mask run to the frame, so the tests can tell which rule you picked.

gather the same nine samples, then decide what to do with them
Goal: two kernels over the same clamped 3×3 sweep — an eroder that returns the smallest sample in the window, then a dilator that returns the largest.

Requirements

Hint 1 — the same nine reads

Copy the box blur's double loop verbatim, clamps and all. Replace the three channel sums with one accumulator, and replace += with Math.min or Math.max.

Hint 2 — the accumulator

Start the minimum at the largest value a mask can hold and the maximum at the smallest, so the first sample always wins:

let lo = 1;
// … inside the loops …
lo = Math.min(lo, mask[sy][sx]);

and the mirror image — let hi = 0; with Math.max — for dilation.

Hint 3 — which way round?

Say it as a sentence. Erosion: "I stay foreground only if all of my neighbours are" — that is an AND over the window, and the AND of 1s and 0s is their minimum. Dilation: "I become foreground if any neighbour is" — an OR, which is their maximum. If your shapes are growing when you asked them to shrink, these two are the wrong way round.

Same idea elsewhere

Morphology is a first-class citizen everywhere: NVIDIA's NPP has nppiErode/nppiDilate, Metal Performance Shaders has MPSImageAreaMin and MPSImageAreaMax, and every WGSL post-process chain grows one eventually. The optimisation is the same as for a box blur — a rectangular structuring element is separable, so an n×n erosion is a horizontal pass followed by a vertical one.

All tasks in Thresholding & Morphology

  1. One Number for the Whole Image
  2. Let the Histogram Pick the Number
  3. A Threshold Per Neighbourhood
  4. Erode and Dilate: the Sweep, With Min and Max
  5. Opening and Closing: Order Is the Answer
  6. Payoff: Clean the Mask, Count What Is Left

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.