Connected Components
A mask tells us which pixels were selected. To count regions, we need a definition of connected and a way to give each reachable group its own label.
13 sections · 4 questions · 2 coding challenges
The arrow keys move between sections too.
Following Regions Through a Digital Picture
BOTIE
Modern connected-component routines expose the same essential choice: which neighbouring positions may connect? We will use a simple flood fill so every step is visible. It illustrates the task, rather than reproducing the historical paper's algorithm.
How Many Seeds Did We Find, Rather Than How Many Pixels?
Botie's mask contains several bright patches. Counting its ones gives an area, but he wanted to count seeds. We need to group pixels that belong together before counting the groups.
A first rule is to put neighbouring foreground pixels in the same region. That sounds straightforward until two pixels touch only at a corner. Let's decide what counts as a neighbour, then follow one region through the mask.
The example masks isolate the effect of the chosen neighbour rule.
Step 1 of 3
Count positions first
These diagonal foreground positions occupy three pixels. We have not yet defined how many regions they form.
Mask
Step 2 of 3
Allow only shared edges
With four-connectivity, a step may go up, down, left, or right. None of the three positions can reach another.
Three region labels
Step 3 of 3
Also allow shared corners
With eight-connectivity, diagonal steps are allowed. All three positions become one reachable region.
One region label
Connectivity defines which foreground pixels belong together
Labelling counts groups connected by the chosen neighbour rule. Consecutive positive IDs make the maximum equal the group count, including zero for an empty mask.
Are those pixels one region or two?
Two bright pixels touching at a corner may be connected under one neighbour rule and separate under another. Compare the supplied masks and inspect the labels before trusting the region count.
BOTIE
Number of connected foreground regions
3
Follow the operations
1. Seed foreground mask
1 0 0 0 1 0 0 0 1 2. Neighbour rule: 4 or 8
4
3. Label connected foreground regions
1 0 0 0 2 0 0 0 3 4. Flatten a list
- 01
- 10
- 20
- 30
- 42
- 50
- 60
- 70
- 83
5. Largest value
3
6. Result
3
Connected regions are not necessarily physical objects. Touching seeds can merge, and a fragmented seed can split.
1. Choose the Neighbour Rule
Four-connectivity uses the four edge-sharing neighbours. Eight-connectivity includes the four diagonal neighbours as well. Two foreground pixels belong to the same component if a path of allowed foreground steps joins them.
The choice depends on what adjacency means for the task. Neither convention can tell us that two touching seeds are physically separate objects.
Four-neighbour offsets (row, column):
(-1, 0), (1, 0), (0, -1), (0, 1)A path may turn and may be long. Connected does not mean every pixel directly touches every other pixel.
2. Start One Region and Follow Its Reachable Pixels
Scan the grid until you find an unlabelled foreground pixel. Give it a new positive label and put its address in a queue. A queue holds positions waiting to be explored.
Remove one address, inspect its allowed neighbours, and add any unlabelled foreground neighbours. Label a position when you add it, so several paths cannot add it repeatedly. When the queue is empty, that connected region is complete.
Start a region
→ label and queue its first pixel
→ visit queued pixels and add eligible neighbours
→ stop when no queued positions remainIn Python, a tuple can store a row and column. A deque provides efficient removal from the front when implementing this traversal.
3. Continue Until Every Foreground Pixel Has a Label
Now resume scanning the mask. A labelled foreground pixel already belongs to a region. An unlabelled foreground pixel begins the search for another region. We leave background at zero.
Our workshop scans each row from left to right, then moves down, assigning consecutive labels as it discovers regions. Those numbers let us refer to the groups. A different scan order could give the same groups different numbers without changing the segmentation.
Labelled mask:
1 1 0 2
0 1 0 2
0 0 0 0
Region count = 2For our consecutive numbering, the maximum label equals the region count. That shortcut does not apply to arbitrary externally supplied label IDs.
4. Measure Each Region Separately
Once pixels carry region labels, count how many positions have each positive label. That gives areas in pixels. Taking the minimum and maximum row and column positions of one label also gives a surrounding rectangle.
Do not include label zero when reporting foreground regions. An area cutoff may remove small regions, but it encodes an assumption about object size and should be checked against real examples.
A region's area and its enclosing rectangle describe different things. A hollow or irregular region does not fill every position inside its rectangle.
Count area by label
bincount counts occurrences of each non-negative integer ID. Position zero belongs to the background.
import numpy as np
labels = np.array([[1,1,0,2], [0,1,0,2], [0,0,0,0]])
areas = np.bincount(labels.ravel())
print(areas[1:].tolist())[3, 2]The two regions have different areas, regardless of the numbers chosen as their labels.
5. Know When Region Count Stops Being Object Count
Two touching seeds can form one component. One damaged mask can split a single seed into several components. Labelling answers a question about connectivity in the supplied mask, not the true number of physical objects.
Inspect uncertain cases and revisit segmentation when that distinction matters. In Botie's workshop, switch the neighbour rule and check a diagonal example before trusting the count.
The full pipeline now has distinct responsibilities: intensities describe pixels, thresholding selects them, morphology changes mask geometry, and labelling groups reachable foreground positions.
6. Measure Where a Region Is, Not Only How Large
Once one region has a label, its row and column positions supply a centroid and an enclosing rectangle. The centroid averages pixel coordinates; it is not necessarily a foreground pixel, especially for a curved or hollow region.
The rectangle needs exclusive lower and right boundaries to match array slicing. Adding one to the largest included pixel index performs that conversion. This is different from adding one to already continuous box coordinates.
Describe one labelled seed region
Label one identifies the seed; zero is background.
import numpy as np
labels = np.array([[0,1,1,0],[0,1,0,0],[0,0,0,0]])
rows, cols = np.nonzero(labels == 1)
print([round(float(rows.mean()), 3), round(float(cols.mean()), 3)])
print([int(rows.min()), int(cols.min()), int(rows.max()+1), int(cols.max()+1)])[0.333, 1.333]
[0, 1, 2, 3]The rectangle is expressed as row-start, column-start, row-stop, column-stop. Keep that convention explicit when converting to x/y boxes.
7. Two Correct Labellers May Use Different Numbers
One traversal may call a seed region one while another calls it three. Compare membership rather than the raw label numbers: pixels grouped together by one result should be grouped together by the other, with background handled consistently.
Test diagonal contact, thin bridges, holes, an empty mask and an object touching the boundary. These reveal the connectivity and border conventions. A component count alone can conceal an incorrect partition whose number of groups happens to match.
Check yourself
Questions on Parts 1 to 7
Question 1 of 4
How many foreground components are present?
Use the information below.
Mask:
1 0
0 1
Connectivity: four-neighbour.Try it with Botie
Build with Botie
Write and run Python