AI Compass
Compass

An image as an array of numbers

Pixels, channels, colour spaces and histograms: what is actually in memory when an image is loaded, and why BGR is the most common source of bugs.

·2 min read·By Fachredaktion Technik
DETAIL
3 sections

The idea

A greyscale image is a table: each cell holds a number between 0 (black) and 255 (white). A colour image is three such tables stacked, one each for red, green and blue. That is all it is.

What follows

Everything done to images is arithmetic on those numbers. Brightening is addition. Raising contrast is multiplication. Blurring is replacing each value with the average of its neighbours.

The colour spaces

SpaceChannelsUsed for
RGB / BGRRed, green, blueDisplay, standard format
GreyscaleBrightnessEdges, shapes, speed
HSVHue, saturation, valueColour filters, robust to brightness
LABLightness, two colour axesColour distances matching perception
YCrCbLuma, two differencesCompression, skin tones
import cv2, numpy as np

img = cv2.imread("shelf.jpg")            # CAREFUL: returns BGR, not RGB
print(img.shape, img.dtype)              # (3000, 4000, 3) uint8
print(img.nbytes / 1e6, "MB")            # 36.0

rgb  = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv  = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Find all red areas. In HSV red is the special case:
# the hue wraps past 0, so two ranges are needed.
m1 = cv2.inRange(hsv, (0, 120, 70), (10, 255, 255))
m2 = cv2.inRange(hsv, (170, 120, 70), (180, 255, 255))
mask = cv2.bitwise_or(m1, m2)
print("red area fraction:", round(float(mask.mean()) / 255, 4))
  • After imread, check for None. A wrong path raises no exception.
  • Settle the channel order before any display or model call.
  • Normalise to float32 and the 0 to 1 range for models, using the pre-training statistics.
  • Watch EXIF rotation: many phone photos are physically rotated in memory.

Greyscale conversion

Luminance per ITU-R BT.601

Y = 0.299 · R + 0.587 · G + 0.114 · B

Green is weighted most because the eye is most sensitive to it, blue least.

R, G, B
the three colour channels, each 0 to 255
Y
perceived brightness

The naive mean (R+G+B)/3 is therefore wrong: a saturated blue and a saturated green with the same arithmetic mean appear equally bright in the result although the eye perceives them very differently.

Histogram and exposure

Histogram

h(k) = |{ (x,y) : I(x,y) = k }|, k = 0 … 255

The histogram counts, for every possible brightness value, how often it occurs in the image.

h(k)
number of pixels with value k
I(x,y)
the value at position x, y

Three diagnoses from one glance:

  • Spike at 255, overexposed, information lost and unrecoverable.
  • Everything between 60 and 120, low contrast, equalisation helps.
  • Two separated humps: a natural threshold exists, see Otsu in Preprocessing with OpenCV.

Memory, worked through

Memory for a batch of images

M = b · h · w · c · s

Need grows linearly in every factor, and moving from uint8 to float32 quadruples it.

b
images per batch
h, w
height and width
c
channels
s
bytes per value: 1 for uint8, 4 for float32

A batch of 32 images at 1024 by 1024 with three channels takes 32 × 1024 × 1024 × 3 × 1 = 100.7 MB in uint8 and 402.7 MB in float32, and that is only the input, before any network activations. This is exactly where the memory pressure that forces a choice of image size comes from.

Was this page helpful?
An image as an array of numbers