Cleaning Masks With Morphology
A threshold can leave speckles and gaps. We can test a small shape against the mask to remove or connect particular structures, provided we understand what else that operation will change.
13 sections · 4 questions · 2 coding challenges
The arrow keys move between sections too.
Measuring Shapes in Mineral Images
BOTIE
The shape used to probe an image is a structuring element, also called a footprint. Its geometry determines what an operation considers small, connected, or wide enough. Our seed mask is an invented example of that idea, not a claim that one footprint can distinguish all noise from all objects.
Sources for this history
Which Marks Belong to a Seed, and Which Are Just Speckles?
The threshold selected Botie's seed, but it also selected a tiny bright speck on the tray. We could erase it by hand. Instead, can we describe a local rule that keeps a broad seed region and removes isolated marks?
Think of placing a small stencil over the mask. We can ask whether every selected position under the stencil is foreground, or whether any of them is. Those two questions give us the operations we need.
The small binary mask contains an illustrative seed region and isolated mark.
Step 1 of 3
Notice both kinds of foreground
The centre contains a broad region. The upper-left corner contains a single isolated pixel.
Threshold mask
Step 2 of 3
Ask where the stencil fits completely
A three-by-three square fits entirely inside the central region only at its centre. Erosion keeps that position.
After erosion
Step 3 of 3
Expand what survived
Dilation expands the remaining centre using the same square. The isolated speck has no surviving centre to expand.
After opening
The footprint decides which structures survive
Only structures with surviving erosion positions can expand again. The broad square survives; the isolated speck does not.
Can we remove the speck without losing the seed region?
The footprint sets the shape that must fit inside the mask. Follow the erosion first, then see which surviving positions can grow back during dilation.
BOTIE
Mask after opening
| Column | |||||
|---|---|---|---|---|---|
| Row | 0 | 1 | 2 | 3 | 4 |
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 | 1 | 0 |
| 2 | 0 | 1 | 1 | 1 | 0 |
| 3 | 0 | 1 | 1 | 1 | 0 |
| 4 | 0 | 0 | 0 | 0 | 0 |
Follow the operations
1. Threshold mask
1 0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 2. Symmetric footprint
1 1 1 1 1 1 1 1 1 3. Erode a binary mask
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 4. Dilate a binary mask
0 0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 5. Result
0 0 0 0 0 0 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0
Both operations use zero outside the image and centrally symmetric odd-sized footprints. Opening can remove real thin objects too.
1. Choose What the Footprint Examines
A footprint is a small binary array with an anchor. Ones select offsets that participate in the operation; zeros ignore offsets. A square examines diagonal and straight neighbours, while a cross ignores its corners.
We use odd-sized, centrally symmetric footprints anchored at their centre. That keeps the erosion and dilation examples easy to compare. At the image boundary, positions outside our grid count as background.
Square footprint:
1 1 1
1 1 1
1 1 1The border convention is part of the algorithm. A foreground region touching the frame can change differently under another padding rule.
2. Erosion Asks Whether Every Required Position Is Foreground
Place the footprint's anchor on a pixel. If every selected offset lands on foreground, write one at that output position. Otherwise write zero. Repeat using the original input mask at every position.
This tends to remove exposed boundaries and thin structures. It does not ask whether a pixel is semantically noise. A real thin stem can disappear for the same reason as an unwanted mark.
Required values under one square = [1,1,1,1,1,1,1,1,1]
All are foreground: output 1
Required values include a zero: output 0Do not update the input mask in place while scanning. Later positions must see the same input that earlier positions saw.
Erode the broad region
Sliding windows expose one neighbourhood per output position. The final two axes belong to each neighbourhood.
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
mask = np.array([[0,0,0,0,0], [0,1,1,1,0], [0,1,1,1,0], [0,1,1,1,0], [0,0,0,0,0]], dtype=bool)
windows = sliding_window_view(np.pad(mask, 1, constant_values=False), (3, 3))
eroded = windows.all(axis=(-2, -1))
print(eroded.astype(int).tolist())[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]Only the centre has a full square of foreground neighbours.
3. Dilation Asks Whether Any Required Position Is Foreground
For our symmetric footprint, write one when any selected position in the neighbourhood is foreground. This expands a region and can join regions separated by a small gap.
Dilation is not an undo button for erosion. Once erosion removes an isolated object entirely, no trace remains for dilation to restore. For asymmetric footprints, the offset reflection convention also matters; the workshop restricts footprints to central symmetry.
A larger region after dilation is not evidence that the actual object grew. We changed the mask according to a spatial rule.
4. Put the Operations in the Intended Order
Erosion can remove the speck, but it also shrinks the seed. We can follow it with dilation using the same footprint to expand the surviving region. This pair of operations is called opening. Reversing the order gives closing, which can fill small gaps or connect nearby parts.
Look at the intermediate mask as well as the result. A thin real object might disappear during opening. Closing might connect two seeds that we wanted to count separately. The footprint's size and shape therefore need to match what we are trying to keep.
Opening = dilate(erode(mask, footprint), footprint)
Closing = erode(dilate(mask, footprint), footprint)The same footprint can produce very different results when we reverse the order.
5. Check More Than the Convenient Example
Our central seed happens to match the square footprint. Real seeds may have thin tips, holes, or irregular boundaries. Test masks containing those shapes before choosing a cleaning rule.
Botie's construction should remove the isolated speck while preserving the central square. It must also show what happens to a thin foreground line and an empty tray. Those cases expose the assumption that meaningful regions are broad enough to survive.
The next lesson labels regions in the resulting mask. Incorrect cleaning can change that object count before the labelling algorithm even begins.
6. The Image Boundary Is Part of the Rule
A footprint placed near the image edge reaches positions we did not photograph. Treating those positions as background can erode a seed that touches the boundary. Reflecting or repeating edge values creates a different assumption about the unobserved area.
State the padding convention and test objects touching each edge. Cropping can turn an ordinary object into a boundary case, so a cleaning rule that worked in the full photograph may behave differently after a crop.
7. Opening Can Remove an Object We Wanted to Keep
Erosion asks whether the entire footprint fits inside foreground. Dilation then expands the surviving positions. If a thin seed leaves no survivors during erosion, the later dilation has nothing from which to rebuild it.
Use a footprint whose shape and physical scale match the nuisance being removed. A three-pixel footprint represents a different physical size after changing the camera resolution.
Check whether a square footprint fits a thin region
The foreground is a one-pixel horizontal line. The test uses a three-by-three square footprint.
import numpy as np
mask = np.array([[0,0,0,0,0],[0,1,1,1,0],[0,0,0,0,0]], dtype=bool)
padded = np.pad(mask, 1, constant_values=False)
eroded = np.zeros_like(mask)
for row in range(mask.shape[0]):
for col in range(mask.shape[1]):
eroded[row,col] = padded[row:row+3,col:col+3].all()
print(eroded.astype(int).tolist())[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]The line disappears because the footprint cannot fit anywhere. Removing small regions is an assumption about what matters, not an automatically beneficial cleanup.
Check yourself
Questions on Parts 1 to 7
Question 1 of 4
What does erosion write at this position?
Use the information below.
An erosion footprint selects five values: [1,1,0,1,1]. Outside-image values count as zero.Try it with Botie
Build with Botie
Write and run Python