Learning Goals
3 min- Load an image into a NumPy array; read its shape (H, W, 3).
- Understand RGB pixel values 0-255.
- Crop, flip, brighten, and grayscale using array operations.
- Display arrays as images with matplotlib.
Warm-Up · What a Pixel Is
5 minA colour image of height 400, width 600:
shape = (400, 600, 3)
└─ 3 colour channels: Red, Green, Blue
One pixel = [R, G, B], each 0-255:
[255, 0, 0] → pure red
[ 0, 255, 0] → pure green
[255, 255, 255] → white
[ 0, 0, 0] → blackpip install numpy pillow matplotlib
There's no magic in "computer vision". An image is a grid of numbers. Editing photos and training image models are both just array maths.
New Concept · Images as Arrays
14 minLoad & inspect
import numpy as np from PIL import Image img = Image.open("cat.jpg") arr = np.array(img) print(arr.shape) # (400, 600, 3) → height, width, channels print(arr.dtype) # uint8 (0-255) print(arr[0, 0]) # [R G B] of the top-left pixel
Display it
import matplotlib.pyplot as plt plt.imshow(arr) plt.axis("off") plt.show()
Crop = slice
# rows 50-250, cols 100-400, all channels crop = arr[50:250, 100:400, :] plt.imshow(crop); plt.show()
Flip = reverse an axis
flip_h = arr[:, ::-1, :] # mirror left-right flip_v = arr[::-1, :, :] # upside down
Brighten = add, then clip
# add 50 to every value, but keep within 0-255 bright = np.clip(arr.astype(int) + 50, 0, 255).astype(np.uint8)
We cast to int first so adding doesn't overflow the uint8 range, then np.clip caps at 255, then cast back.
Grayscale = weighted channel sum
# human eyes weight green most, blue least weights = np.array([0.299, 0.587, 0.114]) gray = (arr @ weights).astype(np.uint8) # shape (H, W) plt.imshow(gray, cmap="gray"); plt.show()
The @ operator (matrix multiply) collapses the 3 channels into 1 using the luminance weights. One line, real image processing.
Worked Example · A Mini Photo Editor
12 min# photo_edit.py — four edits, one figure import numpy as np from PIL import Image import matplotlib.pyplot as plt arr = np.array(Image.open("cat.jpg")) def grayscale(a): return (a @ np.array([0.299, 0.587, 0.114])).astype(np.uint8) def brighten(a, amount): return np.clip(a.astype(int) + amount, 0, 255).astype(np.uint8) def red_only(a): out = a.copy() out[:, :, 1] = 0 # zero green out[:, :, 2] = 0 # zero blue return out fig, axes = plt.subplots(2, 2, figsize=(8, 6)) axes[0, 0].imshow(arr); axes[0, 0].set_title("original") axes[0, 1].imshow(grayscale(arr), cmap="gray"); axes[0, 1].set_title("grayscale") axes[1, 0].imshow(brighten(arr, 60)); axes[1, 0].set_title("brighter") axes[1, 1].imshow(red_only(arr)); axes[1, 1].set_title("red channel") for ax in axes.flat: ax.axis("off") fig.tight_layout() fig.savefig("edits.png", dpi=150) plt.show()
Read the diff
Every effect is pure array maths: a weighted sum for grayscale, clipped addition for brightness, zeroing channels for the red filter. No image library "effects" — just NumPy. This is exactly the data a CNN receives in Lesson 27.
Try It Yourself
13 minLoad a photo of your own. Print its shape, dtype, and the RGB of the centre pixel.
Invert the colours: 255 - arr. Display the result.
Hint
negative = 255 - arr plt.imshow(negative); plt.show()
Shrink the image by taking every 10th pixel (arr[::10, ::10]), then display it large to get a blocky "pixelated" look. Compare to averaging 10×10 blocks.
Hint
small = arr[::10, ::10] # quick & dirty pixelate plt.imshow(small); plt.show()
Mini-Challenge · Green-Screen Swap
8 minGiven a photo with a roughly green background, replace the green pixels with a solid colour (or another image). Use a boolean mask: a pixel is "green" if its G channel is much higher than R and B.
Show one possible solution
import numpy as np from PIL import Image import matplotlib.pyplot as plt arr = np.array(Image.open("greenscreen.jpg")).astype(int) R, G, B = arr[:, :, 0], arr[:, :, 1], arr[:, :, 2] # mask: green dominates mask = (G > 100) & (G > R + 30) & (G > B + 30) out = arr.copy() out[mask] = [30, 30, 120] # paint background deep blue plt.imshow(out.astype(np.uint8)); plt.show()
Non-negotiables: a boolean mask combining channel comparisons, applied to recolour only the matching pixels. This is how video-call background replacement starts.
Recap
3 minAn image is a (H, W, 3) array of 0-255 values. Crop = slice; flip = reverse an axis; brighten = clipped addition; grayscale = weighted channel sum; recolour = boolean mask. Computer vision is array maths all the way down. Next we go back to tabular data and prep it for models.
Vocabulary Card
- channel
- One colour layer of an image — Red, Green, or Blue.
- RGB / uint8
- Pixel colour as three 0-255 integers; uint8 is the unsigned-8-bit type holding them.
- np.clip
- Cap values into a range so brightening doesn't overflow.
- cmap
- matplotlib colour map; use
cmap="gray"to show a single-channel array.
Homework
4 minBuild filters.py with five image effects as functions: grayscale, negative, brighten, mirror, and one of your own invention. Apply all five to one photo and save a single 2×3 grid PNG (original + 5 effects).
import numpy as np from PIL import Image import matplotlib.pyplot as plt arr = np.array(Image.open("photo.jpg")) effects = { "original": arr, "grayscale": (arr @ [0.299, 0.587, 0.114]).astype(np.uint8), "negative": 255 - arr, "brighter": np.clip(arr.astype(int) + 60, 0, 255).astype(np.uint8), "mirror": arr[:, ::-1], "sepia": np.clip(arr @ np.array( [[0.393,0.769,0.189], [0.349,0.686,0.168], [0.272,0.534,0.131]]).T, 0, 255).astype(np.uint8), } fig, axes = plt.subplots(2, 3, figsize=(11, 7)) for ax, (name, im) in zip(axes.flat, effects.items()): ax.imshow(im, cmap="gray" if im.ndim == 2 else None) ax.set_title(name); ax.axis("off") fig.tight_layout(); fig.savefig("filters.png", dpi=150)
Non-negotiables: 5 effects as functions/exprs, one combined grid PNG saved.