← Python
ex02June 21, 2026 · 8 min read

Images Are Just Arrays: Loading RGB Pixels

An image is a 3D array of numbers. Learn how height, width and color channels map onto a NumPy shape, and how to load a JPEG into pixel data safely.

pythonnumpyimagespillow

Exercise 02 is the moment the piscine clicks: you discover that a photograph is nothing but a 3D array of integers. Once you see an image as numbers, every later exercise — zoom, rotate, color filters — becomes plain array manipulation.

The shape of an image

Load a JPEG and print its shape and you get something like (257, 450, 3). Read it the same way as any array:

  • 257 — axis 0, the height (number of pixel rows).
  • 450 — axis 1, the width (pixels per row).
  • 3 — axis 2, the color channels: Red, Green, Blue.

So a single pixel is a triple like [19, 42, 83] — a little red, more green, a lot of blue. Each channel is usually an 8-bit integer from 0 to 255 (dtype uint8).

Note the order: it is (height, width), rows before columns — the same axis-0-is-rows convention from the 2D-array exercise. This trips up everyone at least once, because we say "width × height" in everyday language but arrays are indexed the other way.

Loading the file

The subject allows any image library; Pillow (PIL) is the standard choice, and NumPy turns the image object into an array:

import numpy as np
from PIL import Image

def ft_load(path: str):
    """Load an image, print its format, return its RGB pixel array."""
    img = Image.open(path)
    img = img.convert("RGB")        # force 3 channels
    array = np.array(img)
    print("The shape of image is:", array.shape)
    return array

Two details matter:

  • convert("RGB") normalizes the channel count. A PNG might carry a 4th alpha channel (RGBA); a scan might be grayscale (1 channel). Converting guarantees the (H, W, 3) shape the exercise expects.
  • np.array(img) is the bridge from "image object" to "pixel matrix". From here it is all NumPy.

Handle JPG and JPEG (and errors)

The subject explicitly requires JPG/JPEG support and a clear message on failure. The single most common error is "file not found", and a piscine rule says an uncaught exception fails the exercise. So wrap it:

def ft_load(path: str):
    try:
        img = Image.open(path).convert("RGB")
    except FileNotFoundError:
        print("Error: file not found:", path)
        return None
    except Exception as e:
        print("Error:", e)
        return None
    array = np.array(img)
    print("The shape of image is:", array.shape)
    return array

Pillow already recognizes JPG and JPEG (same format, two extensions) out of the box, plus PNG, BMP and more — you do not branch on the extension yourself.

Why this representation is so powerful

Because the image is "just numbers", every transformation is arithmetic:

  • Crop / zoom → slice the array (next exercise).
  • Rotate / flip → transpose the axes.
  • Brighten → add a constant to every pixel.
  • Invert → compute 255 - pixel.
  • Grayscale → average the three channels.

That is the whole roadmap of the Array module, and it all rests on the idea you just unlocked here.

Takeaways

  • An RGB image is a (height, width, 3) array of uint8 values 0–255.
  • Axis order is rows (height) first, columns (width) second — not the spoken "width × height".
  • Use Pillow to open and convert("RGB"), then np.array to get pixels.
  • Always guard file loading with a clear error message.