Computer vision

Image Representation

To a computer an image is just a grid of numbers. Here is how OpenCV and NumPy store, read and change them.

What is a digital image?

A digital image is a 2-D array (a matrix) of pixels.

Grayscale

(H, W)

A 2-D array: each pixel is one intensity value, 0 to 255.

Colour

(H, W, 3)

A 3-D array with three channels, one each for blue, green and red.

Data type

uint8

Usually an unsigned 8-bit integer, so every value lies in 0 to 255.

Image coordinates

(0, 0)
img[0, 0]
→ [0, 0, 0]
gray = 0

Move over the photo to read any pixel, in B, G, R order.

The origin (0, 0) is the top-left corner.

First index = row (y), second = column (x).

Write it as image[row, col] or image[y, x].

Pixel intensity values

Grayscale: one number per pixel.

0 black 128 middle gray 255 white

Colour (BGR in OpenCV): three numbers per pixel.

An image is a NumPy array


          import cv2
          import numpy as np

          # images are numpy arrays
          img = np.array([[0, 50, 100],
                          [150, 200, 255]], dtype=np.uint8)
          # ans: a 2x3 grayscale image
          print(img.shape)
          # ans: (2, 3): 2 rows, 3 columns
          print(img.dtype)
          # ans: uint8: values 0-255
        
050100 150200255

2 rows × 3 columns

Press → and the numbers become pixels.

Loading images


        # load a colour image
        img = cv2.imread('image.jpg')
        # ans: loads in BGR order (not RGB!)
        print(img.shape)
        # ans: (height, width, channels)

        height, width, channels = img.shape
        # ans: height = rows, width = cols, channels = 3

        # load as grayscale
        gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
        # ans: loads a single-channel image
        print(gray.shape)
        # ans: (height, width): no channel dimension
      

Accessing pixels


          # accessing individual pixels
          img = cv2.imread('image.jpg')
          pixel = img[100, 50]
          # ans: the pixel at row 100, col 50
          print(pixel)
          # ans: [B, G, R] values (3 numbers)

          # modify a pixel
          img[100, 50] = [255, 0, 0]
          # ans: sets the pixel to blue (BGR order)
        
(0, 0) row 100 ↓ col 50 →

Row first, then column:
img[y, x]

Creating blank images


          # create a blank black image
          black = np.zeros((480, 640, 3), dtype=np.uint8)
          # ans: 480x640 black image (all zeros)

          # create a white image
          white = np.ones((480, 640, 3), dtype=np.uint8) * 255
          # ans: 480x640 white image (all 255s)

          # create a coloured image
          red = np.zeros((480, 640, 3), dtype=np.uint8)
          red[:, :, 2] = 255
          # ans: red image (3rd channel = red in BGR)
        
black
white
red

Image memory size

B G R 1920 wide 1080 high

height × width × channels × bytes per value

1080 × 1920 × 3 × 1 = 6,220,800 bytes

≈ 6.2 MB for one uncompressed Full-HD frame.

Saves memory

1 byte per value instead of 4 for int32.

Enough levels

The eye can't tell apart more than about 256 intensity levels.

Standard

It is what most image formats (JPEG, PNG) store.

Exercises

Answer first, then press → to check.

Exercises 1 · concepts

What is the shape?


            img = np.zeros((300, 400, 3))
          

(300, 400, 3): 300 rows, 400 columns, 3 channels.

How many pixels in total?

300 × 400 = 120,000 pixels.

How many values in total?

300 × 400 × 3 = 360,000 values.

What colour is the pixel [255, 255, 0] in BGR?

Cyan: blue = 255, green = 255, red = 0. (Yellow would be [0, 255, 255].)

Exercises 2 · concepts

Why use uint8 instead of int?

It saves memory, and 0 to 255 is enough range for images.

What is the difference?


            gray = np.zeros((100, 100))
            color = np.zeros((100, 100, 3))
          

gray is 2-D; color is 3-D with 3 channels.

img.shape returns (480, 640, 3). Which number is the height?

480: the first dimension is the rows.

What happens with the value 300 in uint8?


            img = np.array([[300]], dtype=np.uint8)
          

It overflows: 300 % 256 = 44 (older NumPy wraps it; NumPy 2 raises an OverflowError instead).

Exercises 3 · coding

Create a 100×100 grayscale image.


            gray = np.zeros((100, 100), dtype=np.uint8)  # ans:
            # ans: a black grayscale image
          

Create a 100×100 colour image.


            color = np.zeros((100, 100, 3), dtype=np.uint8)  # ans:
            # ans: a black colour image with 3 channels
          

Access the pixel at row 10, column 20.


            img = np.random.randint(0, 256, (50, 50, 3),
                                    dtype=np.uint8)
            pixel = img[10, 20]  # ans:
            # ans: an array of 3 BGR values
          

Exercises 4 · coding

Create a green image (BGR).


            green = np.zeros((100, 100, 3), dtype=np.uint8)
            green[:, :, 1] = 255  # ans:
            # ans: the green channel (index 1) set to 255
          

Create a half-white, half-black image.


            img = np.zeros((100, 100), dtype=np.uint8)
            img[:50, :] = 255  # ans:
            # ans: top 50 rows white, bottom 50 rows black
          

Create a solid cyan image (blue + green).


            cyan = np.zeros((100, 100, 3), dtype=np.uint8)
            cyan[:, :, 0] = 255  # ans:
            cyan[:, :, 1] = 255  # ans:
            # ans: blue + green: every pixel is [255, 255, 0]
          

Exercises 5 · mixed

How do you get the image dimensions?


            height = img.shape[0]
            width = img.shape[1]
            channels = img.shape[2]
            # ans: shape is (height, width, channels)
          

What is the memory size of a 1024×768 RGB image?

1024 × 768 × 3 = 2,359,296 bytes ≈ 2.4 MB.

What colour is this image?


            img = np.full((100, 100, 3), [255, 0, 0], dtype=np.uint8)
            # ans: blue: every pixel is [255, 0, 0] in BGR
          

What is img.ndim for a grayscale image?


            gray = np.zeros((100, 100))
            # ans: 2 (a 2-dimensional array)