Task 5 of 6
Erosion on its own is a blunt instrument: it kills the specks and takes a rind off everything else. Dilation on its own is the same mistake in reverse. Run them back to back and the size change cancels while the repair survives — and which repair you get depends entirely on which one goes first.
Opening is erode then dilate. The erosion wipes anything thinner than the structuring element, the dilation grows the survivors back to size: small bright specks are gone for good and everything else ends up roughly where it started. Closing is dilate then erode: the dilation swallows small dark holes, the erosion pulls the outlines back in, so pinholes fill and the specks stay exactly where they were.
They are not inverses and they are not interchangeable. Opening removes; closing fills. Ask for one and write the other and you get precisely the opposite of what you wanted — which is the single most reliable way to lose an afternoon to morphology.
Both kernels are given below, so this task is about the plumbing: chain them, and chain them twice. Two erosions followed by two dilations is an opening with a radius-2 element — it clears out the 3×3 clumps that a single pass is too gentle to touch.
await dilate(await erode(mask)); closing is await erode(await dilate(mask))removed kernel: 1 where before is foreground and after is notA kernel's result is an ordinary 2D array, so it goes straight back into
another kernel: await dilate(await erode(mask)) is the whole opening. Every pass is
a separate launch, which is exactly how a real pipeline does it (and Pipelines &
Textures shows how to keep the intermediate on the GPU).
Read the name outwards. An opening opens gaps up: it must start by shrinking, so erosion goes first. A closing closes gaps: it starts by growing. If your "opening" is filling holes instead of clearing specks, you have written a closing.
const removed = gpu.createKernel(function (before, after) {
if (before[this.thread.y][this.thread.x] > after[this.thread.y][this.thread.x]) return 1;
return 0;
}, { output: [128, 128] });
Feed it (mask, opened) to see what the opening threw away, and
(closed, mask) to see what the closing filled in.
MORPH_OPEN, MORPH_CLOSE) and in every medical- and
satellite-imaging toolchain. On a GPU each is a fixed chain of launches with no readback
in between — the ping-pong between two buffers that WebGPU and CUDA pipelines are built
around.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.