Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Thresholds and histograms

After this lesson: I can use a histogram to choose a threshold that separates objects from the background.

60 min lesson3 commandsComputer vision lab: pixels, thresholds and counting objects in JupyterLab

A threshold separates objects from the background

Image thresholding is the simplest way to separate an object from its background. Pick a grey level and call every pixel brighter than it part of the object. A histogram, a count of how many pixels have each grey level, tells you where to put that level.

In this lesson you read a histogram, choose a threshold and count what it keeps.

By the end of this lesson

Note

I can use a histogram to choose a threshold that separates objects from the background.

Key words

WordWhat it meansExample
Histograma count of the pixels at each grey level
Thresholda grey level that splits pixels in two100
Backgroundthe part of a picture that is not an object
Foregroundthe objects you want to find
Maska picture of 0 and 1 that marks the pixels you keep

The cells students fill in are left blank in this preview.

A histogram counts pixels at each grey level

Go through every pixel and tally its grey level. The result is a histogram. Usually the levels are grouped into bands, such as 0 to 63 and 64 to 127, so the counts are easy to read.

A picture with a dark background and bright objects has two clear heaps: one at the dark end and one at the bright end.

Worked example: a histogram of twelve pixels

Band of grey levelsPixels in the bandWhich pixels
0 to 63410, 60, 30, 20
64 to 127290, 70
128 to 1913130, 190, 140
192 to 2553200, 250, 255

The cells students fill in are left blank in this preview.

Count the road picture in bands of 50

Command
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; a = np.array(Image.open('images/road.png')); print(np.bincount(a.ravel() // 50).tolist())"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Two heaps in a histogram show two groups

The Run reads road.png, a grey road with white lane lines. It divides every grey level by 50, so each pixel lands in a band of 50 levels, and counts them.

The six numbers are [0, 28819, 44273, 0, 3403, 305]. Almost every pixel is in the second and third bands: the dark verge and the road. A few thousand are in the last two: the bright lane lines. The empty band between them is where a threshold belongs.

A threshold turns a picture into a mask

A threshold asks one question of every pixel: is it brighter than this level? If yes, the mask holds 1. If no, it holds 0.

The mask is a binary image. Counting its 1s tells you how much of the picture is object.

Worked example: the same twelve pixels, threshold 100

RowGrey levelsMask
010, 200, 60, 1300, 1, 0, 1
1250, 30, 190, 901, 0, 1, 0
270, 140, 20, 2550, 1, 0, 1

The cells students fill in are left blank in this preview.

Count the pixels above a threshold

Code: python
grid = [
    [10, 200, 60, 130],
    [250, 30, 190, 90],
    [70, 140, 20, 255],
]
level = 100

kept = 0
for row in grid:
    for pixel in row:
        if pixel > level:
            kept = kept + 1

print("pixels above", level, ":", kept, "of 12")
# Change level to 60 and press Run.
# Then try 255. Why does nothing pass?

Try two thresholds on the road picture

Command
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; a = np.array(Image.open('images/road.png')); print(int((a > 200).sum()), int((a > 90).sum()))"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Choose the level in the gap between heaps

The Run prints 3708 48240. A level of 200 keeps only the lane lines, 3708 pixels. A level of 90 keeps the road and the lines, 48240 pixels, because the road is brighter than 90.

Neither is wrong. They answer different questions, so the level must match what you want to find.

Let scikit-image pick a level for the coins

Command
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; from skimage import filters; a = np.array(Image.open('images/coins.png').convert('L')); print(int(filters.threshold_otsu(a)))"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Otsu picks the level that splits two heaps

Otsu's method tries every level and keeps the one that separates the pixels into two groups most cleanly. For the coin picture it prints 63.

That sits in the empty stretch between the dark background and the bright coins, so it is a sound choice. It still only helps when the histogram really has two heaps.

A common mistake

Warning

Many people think one threshold works for a whole picture. It does only if the light is even. On a page lit from one side, the dark ink at the bright edge can be brighter than the plain paper at the dark edge. Look at the mask before you trust a count.

Going further

Open scan.png in a notebook and print a.min(), a.max() and the mean of the left half and the right half.

The light is uneven across the page. Predict what a single threshold would do, then check it by drawing the mask.

About this lab and its source: Computer vision lab: pixels, thresholds and counting objects in JupyterLab

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.