NumPy Image Arrays
Every image you have ever opened is a grid of numbers, and NumPy is how Python holds that grid. Once you know the shape convention and the right data type, creating a blank canvas, reading a pixel, or isolating a colour channel all become ordinary array operations.
Create a 210x210 black image with np.zeros((210, 210, 3), dtype=np.uint8). The height-width-channels rule, why uint8 matters, and how to address pixels and…
Part of the free Numpy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
We will start with the question people most often arrive at NumPy asking — how do I make a 210×210 black image? — and build out from there.
What You'll Learn in This Lesson
1 The Shape Rule: Height, Width, Channels
- Grayscale — (height, width) . One brightness value per pixel.
- RGB — (height, width, 3) . Red, green and blue per pixel.
- RGBA — (height, width, 4) . RGB plus an alpha transparency channel.
So a 210×210 image is (210, 210, 3) — square, so the swap does not bite here, which is exactly why square examples hide the bug until you try a rectangle.
2 Why uint8 — and the np.ones() Trap
Each channel stores a whole number from 0 (none) to 255 (full) . That is precisely one byte, which is what np.uint8 gives you — and what every image format and viewer expects.
The memory difference is not trivial. A 4K RGB frame is about 25 MB as uint8, and nearly 200 MB as the float64 NumPy would otherwise default to — eight times the memory for data that only ever holds whole numbers 0-255.
To make any flat colour, fill with a three-value list. Remember the order is red, green, blue — so pure red is [255, 0, 0] .
3 Pixels, Regions and Channels
Because it is just an array, ordinary indexing does everything:
- img[y, x] — one pixel, as a 3-value array.
- img[y0:y1, x0:x1] = colour — fill a rectangle.
- img[:, :, 0] — the whole red plane.
- img[::-1] — flip vertically; img[:, ::-1] flips horizontally.
Slicing returns a view , not a copy, so assigning into a slice edits the original image directly — which is exactly what you want when drawing.
4 Two Bugs That Turn Images Black
1. uint8 overflow. Brightening with img + 50 looks sensible until a channel already at 230 wraps past 255 and comes back near zero — bright areas turn dark and speckled. Do the arithmetic in a wider type and clip:
np.clip(img.astype(np.int16) + 50, 0, 255).astype(np.uint8)
2. Float-to-uint8 without scaling. Many libraries hand back floats in the range 0–1. Casting straight to uint8 truncates every one of them to 0 — a perfectly black image. Multiply by 255 first.
5 🎯 Your Turn
Build a small flag: a black canvas, a white horizontal band across the middle, and a red square in the top-left corner. Then confirm the shape and a couple of pixels.
❓ Frequently Asked Questions
Lesson complete — images are just arrays now!
You can create a canvas at any size, pick the right dtype, address pixels and channels by index, and recognise the two dtype bugs that turn an image unexpectedly black.
🚀 Up next: Array Data Types — go deeper on dtypes, precision and memory.
Practice quiz
What shape does a 210x210 colour image have as a NumPy array?
- (210, 210)
- (210, 210, 3)
- (3, 210, 210)
- (210, 3)
Answer: (210, 210, 3). Height, then width, then one value per colour channel — (210, 210, 3) for RGB.
Which call creates a 210x210 black RGB image?
- np.zeros((210, 210, 3), dtype=np.uint8)
- np.empty((210, 210))
- np.ones((210, 210, 3))
- np.zeros(210)
Answer: np.zeros((210, 210, 3), dtype=np.uint8). Black is zero in every channel, and uint8 is the standard image data type.
Why use dtype=np.uint8 for images?
- It is faster to type
- It matches the 0-255 range each channel uses, at one byte per value
- It supports negative numbers
- It is required by NumPy
Answer: It matches the 0-255 range each channel uses, at one byte per value. uint8 holds exactly 0-255, which is the range image formats and viewers expect.
How do you make a pure white RGB image?
- np.ones((h, w, 3), dtype=np.uint8)
- np.full((h, w, 3), 255, dtype=np.uint8)
- np.zeros((h, w, 3)) + 1
- np.white((h, w, 3))
Answer: np.full((h, w, 3), 255, dtype=np.uint8). White is 255 in every channel; np.ones would give you a near-black value of 1.
In img[50, 100], what does 50 refer to?
- The x coordinate
- The row (y coordinate)
- The colour channel
- The width
Answer: The row (y coordinate). NumPy indexes rows first, so img[y, x] — the opposite order to most graphics APIs.
What shape does a grayscale image have?
- (height, width)
- (height, width, 1) only
- (height, width, 3)
- (width, height)
Answer: (height, width). Grayscale needs one value per pixel, so it is a plain 2-D array.
What does img[:, :, 0] select in an RGB image?
- The first row
- The red channel of every pixel
- The first pixel
- The alpha channel
Answer: The red channel of every pixel. Take all rows, all columns, channel index 0 — the red plane.
With uint8, what is 250 + 10?
- 260
- 255
- 4
- An error
Answer: 4. uint8 wraps around: it overflows past 255 and comes back to 4. Cast to a wider type before arithmetic.
How do you convert a float image in the range 0-1 to uint8?
- img.astype(np.uint8)
- (img * 255).astype(np.uint8)
- np.round(img)
- img.tolist()
Answer: (img * 255).astype(np.uint8). Scale to 0-255 first — casting straight away would turn every value below 1.0 into 0.
How many channels does an RGBA image have?
- 3
- 4
- 2
- 1
Answer: 4. Red, green, blue plus alpha for transparency — shape (h, w, 4).
Continue this course
- Previous: Creating Arrays
- Next: Array Data Types (dtypes)