Capstone: edit a photo with nothing but NumPy
Every idea from the track, applied to something you can see.
3-D arrayschannel axisslicingbroadcastingnp.whereastypeclipWatch it happen
Play it through, or step back and forth yourself.
photoA picture. Nothing special about it — except that as far as NumPy is concerned, it isn't a picture at all.
The idea
Everything so far has been arrays of numbers standing in for something. This time the array is the thing: photo is a picture, and every edit you make is one of the operations you already know.
There's no image library here. The photo above was generated by the setup code with mgrid, some broadcasting and an np.where — worth reading, because it's already using half the track.
The shape
photo.shape is (120, 160, 3) — rows, columns, channels, in that order. Height comes first, which trips up anyone used to saying "160 by 120". The last axis holds red, green and blue.
The dtype is uint8: whole numbers from 0 to 255, one byte each. That's the standard for images and it has teeth — see below.
The moves
photo[20:80, 40:120] # crop — slicing
photo[:, ::-1] # mirror — negative step
photo[::-1] # upside down — same, other axis
photo[:, :, 0] # red channel — 2-D, shape (120, 160)
photo[:, :, ::-1] # RGB -> BGR — reverse the channel axisThe uint8 trap
This is the one thing in the capstone that will genuinely catch you. uint8 wraps around:
photo + 60 # 250 becomes 54. Black holes.
np.clip(photo.astype(int) + 60, 0, 255).astype(np.uint8) # correctWiden to a bigger integer type, do the arithmetic, clamp the range, then convert back. Every brightness or contrast adjustment follows that pattern.
Going grayscale
Averaging the three channels works, but looks wrong — human eyes are far more sensitive to green than to blue. The standard weights are 0.299, 0.587, 0.114, and the neat way to apply them is a dot product across the channel axis:
gray = (photo @ np.array([0.299, 0.587, 0.114])).astype(np.uint8)
gray.shape # (120, 160) — the channel axis is goneWork through these
The exercises below build up in order. Use the playground freely between them — change a number, re-run, and see what happens to the picture. That feedback loop is the point of the whole lab.
Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedPrint the photo's shape and dtype. Then brighten the whole picture: cast to int so the arithmetic has room, add 55, np.clip back into 0–255, and cast to uint8 again. Leave that last expression as the result and it gets drawn.
Your turn
7 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Return the shape of photo. Read it carefully — which number is the height?
Crop to rows 20 up to 80 and columns 40 up to 120.
Mirror the photo left-to-right.
Pull out just the red channel as a 2-D array. It'll be drawn as a grayscale image.
Brighten the photo by 55 without wrapping. The result must still be uint8.
Convert to grayscale using the weights 0.299, 0.587, 0.114. Return a uint8 array of shape (120, 160).
Find the bright parts. Return a uint8 array, same shape as the grayscale image, that is 255 where the grayscale value is above 120 and 0 everywhere else.
