An edge is where grey level changes fast
Edge detection finds the places where brightness changes sharply, such as the outline of a shape or a lane line on a road. Edges are a compact summary of a picture, because they keep the outlines and drop the flat areas.
In this lesson you measure the change between neighbouring pixels and mark the places where it is large.
By the end of this lesson
Note
I can find the edges in a picture by measuring how fast the grey level changes.
Key words
| Word | What it means |
|---|---|
| Gradient | how fast the grey level changes from one pixel to the next |
| Edge | a place where the grey level changes sharply |
| Edge strength | the size of the change at one pixel |
| Edge map | a picture of 0 and 1 that marks the edge pixels |
| Canny | a standard method that finds thin, clean edges |
The cells students fill in are left blank in this preview.
A gradient is the step between neighbouring pixels
Subtract one pixel from the next. In a flat area the answer is near 0. At the border of a bright shape on a dark page it is large.
That difference is the gradient. Its size, ignoring whether it goes up or down, tells you how sharp the change is.
Worked example: steps along one row
| Place | Pixels | Working | Size of the step |
|---|---|---|---|
| 0 to 1 | 10 then 10 | 10 - 10 | 0 |
| 1 to 2 | 10 then 10 | 10 - 10 | 0 |
| 2 to 3 | 10 then 200 | 200 - 10 | 190 |
| 3 to 4 | 200 then 200 | 200 - 200 | 0 |
The cells students fill in are left blank in this preview.
Find the biggest step in the grey ramp
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; a = np.array(Image.open('images/gradient.png')).astype(int); print(np.abs(np.diff(a, axis=1)).max(), np.abs(np.diff(a, axis=0)).max())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A smooth ramp has no edge at all
The Run takes the differences along each row, then down each column, and prints the biggest of each. It prints 1 0.
The ramp rises by 1 grey level per pixel across and not at all down, so its largest step is 1. It changes all the way along, but never fast, so there is no edge.
Measure the steps along a row
row = [10, 10, 10, 200, 200]
for place in range(len(row) - 1):
step = row[place + 1] - row[place]
print("between", place, "and", place + 1, ":", abs(step))
# Change the 200 at the end of the row to 10 and press Run.
# How many edges are there now?
See why differences need whole numbers
docker exec jupyter python3 -c "import numpy as np; a = np.array([10, 200], dtype=np.uint8); print((a[:1] - a[1:]).tolist(), (a[:1].astype(int) - a[1:]).tolist())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Subtracting in uint8 wraps round and gives nonsense
A uint8 number cannot go below 0. NumPy wraps it round instead. The Run prints [66] [-190].
The first is 10 minus 200 in uint8: it wraps to 66, which is wrong. The second changes to ordinary integers first and gives the true -190. Always use astype(int) before you subtract pixels.
Edge strength adds the steps across and down
An edge can run in any direction, so look both ways. At each pixel, find the step to the pixel on its right and the step to the pixel below. Add their sizes.
That sum is the edge strength. A pixel whose strength is over a chosen limit is marked 1 in the edge map.
Worked example: edge strength on a grid
| Pixel | Step right | Step down | Strength |
|---|---|---|---|
| Row 0, column 0 | 0 | 0 | 0 |
| Row 0, column 1 | 190 | 0 | 190 |
| Row 1, column 1 | 190 | 0 | 190 |
| Row 1, column 2 | 0 | 190 | 190 |
The cells students fill in are left blank in this preview.
Count the big steps along the road
docker exec jupyter python3 -c "import numpy as np; from PIL import Image; a = np.array(Image.open('images/road.png')).astype(int); g = np.abs(np.diff(a, axis=1)); print(g.shape, int((g > 60).sum()), int(g.max()))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A limit keeps strong steps only
The Run prints (240, 319) 1236 153. A picture 320 wide has 319 steps along a row, so the array is 240 by 319.
Of those, 1236 are bigger than 60, mostly the sides of the lane lines and the road. The biggest is 153. The small steps from noise stay under the limit and are dropped.
Find the edges of the shapes picture
docker exec jupyter python3 steps/edges.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Canny smooths first and then keeps thin edges
The Canny method is what scikit-image offers. It blurs the picture first, measures the strength, keeps only the thin ridge of each edge, and drops the weak ones.
The Run finds 1326 edge pixels in a picture of 60000: a small share, because most of the page is flat. The blur is set by a number called sigma, so more sigma means fewer false edges and softer outlines.
A common mistake
Warning
Many people think an edge map shows whole objects. It shows only the change at their borders, and a border can have gaps or extra bits from noise. Smooth first, choose the limit by looking at the map, and treat the result as a clue about shapes, not a shape.
Going further
In a notebook, run feature.canny(a, sigma=1) and then sigma=3 on road.png, and print int(edges.sum()) for each. Use from skimage import feature.
Which sigma finds more edge pixels, and which gives you cleaner lane lines?