RGB Image Logic
Draw with colors to understand how images are composed of Red, Green, and Blue channels.
Draw with colors to understand how images are composed of Red, Green, and Blue channels.
RGB Pic Handling is a basic concept in computer vision. It's the basis for how computers "see" and manipulate color pics. Each colored pixel on your screen is a combination of three vals: Red, Green, and Blue.
Each value ranges from 0 (none of that colour) to 255 (max intensity). Mixing the three channels additively gives each colour the display can show: (255, 0, 0) is pure red, (0, 255, 0) is pure green, (255, 255, 0) is yellow, (255, 255, 255) is white and (0, 0, 0) is black. The total count of representable colours is 256³ — roughly 16.7 million.
In code, an pic is a 3D NumPy array of shape (height, width, 3). The first two axes are pixel coordinates (rows from top, columns from left). The third axis is the channel index: 0 for red, 1 for green, 2 for blue. Extracting a single channel (pic[:, :, 0]) gives a 2D grayscale array showing how much red each pixel holds.
OpenCV reads pics in BGR order rather than RGB — a historical accident. Forgetting to turn with cv2.cvtColor(img, cv2.COLOR_BGR2RGB) swaps the red and blue channels, giving pics that look wrong in ways that are obvious on faces (blue skin) but easy to miss on other subjects.
Other colour spaces — HSV, LAB, YCbCr — rearrange the same information for other tasks. HSV separates hue from brightness, making it easy to select all pixels of a particular colour no matter lighting. LAB nets perceptual distance. So numerically close colours look close to the human eye. All are computed from the underlying RGB vals.
Draw with colors to understand how images are composed of Red, Green, and Blue channels.
A deep explore how digital color is represented and manipulated. Read this, experiment with the tool, and solidify your understanding.
RGB Pic Handling is a basic concept in computer vision. It's the basis for how computers "see" and manipulate color pics. Each colored pixel on your screen is a combination of three vals: Red, Green, and Blue.
Each value ranges from 0 (none of that colour) to 255 (max intensity). Mixing the three channels additively gives each colour the display can show: (255, 0, 0) is pure red, (0, 255, 0) is pure green, (255, 255, 0) is yellow, (255, 255, 255) is white and (0, 0, 0) is black. The total count of representable colours is 256³ — roughly 16.7 million.
In code, an pic is a 3D NumPy array of shape (height, width, 3). The first two axes are pixel coordinates (rows from top, columns from left). The third axis is the channel index: 0 for red, 1 for green, 2 for blue. Extracting a single channel (pic[:, :, 0]) gives a 2D grayscale array showing how much red each pixel holds.
OpenCV reads pics in BGR order rather than RGB — a historical accident. Forgetting to turn with cv2.cvtColor(img, cv2.COLOR_BGR2RGB) swaps the red and blue channels, giving pics that look wrong in ways that are obvious on faces (blue skin) but easy to miss on other subjects.
Other colour spaces — HSV, LAB, YCbCr — rearrange the same information for other tasks. HSV separates hue from brightness, making it easy to select all pixels of a particular colour no matter lighting. LAB nets perceptual distance. So numerically close colours look close to the human eye. All are computed from the underlying RGB vals.
Think of a color pic not as a single flat picture. But as three split grayscale pics stacked on top of each other. Each of these "layers" is a channel, showing the intensity of Red, Green. Or Blue light for each pixel.
When mixed, their vals mix to create the full spectrum of colors we see. A value of (0, 0, 0) is black, (255, 255, 255) is white. And (255, 0, 0) is pure red.
In code, an RGB pic is a 3D NumPy array of shape (height, width, 3). The first axis is rows (top to bottom), the second is columns (left to right). And the third is the channel: index 0 for red, 1 for green, 2 for blue. Each value is often a uint8 integer from 0 to 255, giving 256 intensity levels per channel and 256³ ≈ 16.7 million possible colours per pixel.
Extracting a single channel is a slice: pic[:, :, 0] gives the red channel as a 2D grayscale array. Setting a channel to zero removes that colour fully: pic[:, :, 2] = 0 removes all blue. These steps are the foundation of colour manipulation: adjusting brightness means scaling all channels, adjusting contrast means stretching the range of vals. And turning to grayscale means replacing the three channels with a single weighted average (often 0.299R + 0.587G + 0.114B, the luminance formula that matches human perception).
OpenCV reads pics in BGR order rather than RGB. It is a historical accident from early camera hardware. Turning between the two is cv2.cvtColor(pic, cv2.COLOR_BGR2RGB). Forgetting this change is one of the most common bugs in computer vision code — the pic displays with swapped red and blue channels. It is obvious on faces (blue skin) but easy to miss on landscapes.
Other colour spaces — HSV (hue, saturation, value), LAB, YCbCr — rearrange the same information for other purposes. HSV separates colour (hue) from intensity (value), making it easier to select "all red pixels" no matter brightness. LAB is designed so that equal numerical distances correspond to equal perceived colour gaps. It makes it useful for colour comparison. All are computed from RGB.
A colour pic stores three vals per pixel — red, green and blue — each often 0 to 255. Mixing them additively gives each colour the display can show.
| R | G | B | Colour |
|---|---|---|---|
| 255 | 0 | 0 | Red |
| 0 | 255 | 0 | Green |
| 255 | 255 | 0 | Yellow |
| 255 | 255 | 255 | White |
| 0 | 0 | 0 | Black |
| 128 | 128 | 128 | Mid grey |
Equal vals give a shade of grey. Unequal vals give a hue. That simple fact is the basis of a few quick pic checks.
A 1920×1080 photograph is therefore 1920 × 1080 × 3 = 6.2 million vals, about 6MB uncompressed. In code it is an array of shape (height, width, 3) — and note the order: height first, then width. It is the opposite of how pic sizes are often quoted.
Two conventions cause endless confusion. OpenCV stores pics as BGR, while almost all else uses RGB. So an pic loaded with cv2.imread and displayed with matplotlib comes out with red and blue swapped. And PyTorch expects channels-first tensors, (3, H, W), while NumPy and PIL use channels-last, (H, W, 3).
RGB is how displays work, not how colour is best reasoned about. Separating colour from brightness is often far more useful.
HSV — hue (which colour, 0–360°), saturation (how vivid), value (how bright). Selecting "all the red pixels" in RGB means writing rules across three interacting channels. In HSV it is a range on one. This is why HSV is the standard choice for colour-based thresholding. And why colour-jitter augmentation is built in it.
LAB — lightness plus two colour axes, designed so that equal numeric distances correspond to roughly equal perceived gaps. Used for colour matching. And its L channel is what CLAHE is often applied to for contrast enhancement without shifting hues.
YCbCr — brightness plus two chroma channels. JPEG and video compression store chroma at less resolution than luma, since human vision is far more sensitive to brightness detail than to colour detail. That asymmetry is a large part of why pics compress as well as they do.
import cv2
img = cv2.imread("photo.jpg") # BGR
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (0, 120, 70), (10, 255, 255)) # reds
Three preprocessing steps, in this order.
Turn to the net's expected channel order. RGB for torchvision. And channels-first for the tensor.
Scale to 0–1 by dividing by 255.
Standardise per channel with the pretraining dataset's statistics — for ImageNet, mean [0.485, 0.456, 0.406] and standard deviation [0.229, 0.224, 0.225]. These are per channel since the channels genuinely have other distributions. Natural pics have more energy in red and green than in blue.
Skipping the standardisation is a silent error: the net trains and works, a few points below where it should be, with nothing to show why.
Use the tool above to build a strong mental net. Follow these steps:
#FF0000). Draw a line.#FFFF00). Draw something.#00FF00) shape. Click "Invert Colors". The shape turns magenta.When a computer applies a filter, it iterates through each pixel and applies a mathematical rule. For a brightness adjustment, it looks like this:
function adjust_brightness(image, factor):
for each pixel (x, y) in image:
original_color = image.get_pixel(x, y) // e.g., (R:50, G:100, B:200)
new_R = original_color.R * factor
new_G = original_color.G * factor
new_B = original_color.B * factor
// Clamp values to stay within the 0-255 range
new_R = min(255, new_R)
new_G = min(255, new_G)
new_B = min(255, new_B)
image.set_pixel(x, y, (new_R, new_G, new_B))
return imageThis is just what happens when you use the "Brightness" slider and click "Apply".
An RGB pic is three greyscale pics stacked. And almost each colour step is a question about how they relate. This works through channel arithmetic, the gap between per-channel and joint steps. And the two mistakes that cause most colour bugs.
The next block continues with the left demonstrations.
The next block continues with the left demonstrations.
The next block continues with the left demonstrations.
The next block continues with the left demonstrations.
The next block continues with the left demonstrations.
(Height x Width x 3).Colour is key when the task depends on it — ripeness, traffic signals, quality control on painted parts, medical staining, species identification. Discarding it there throws away the signal.
But colour is also the most common shortcut a net will latch onto. If each photograph of one class in your dataset was taken in a particular lighting. Or each pic of a defect happens to come from a camera with a other white balance, the net will learn the colour cast rather than the object. It will score well on your test split and fail at once on new data.
Three defences:
The tied failure is white balance: the same object photographed under daylight and under tungsten lighting has genuinely other RGB vals. Histogram equalisation on the L channel of LAB. Or a grey-world white-balance correction, normalises much of this away.
Why does my pic look blue-tinted? BGR loaded as RGB. Turn with cv2.cvtColor.
Should I turn to greyscale? Just if colour genuinely carries no information for the task. Test both — it is cheap to check.
What is alpha? A fourth channel for transparency, giving RGBA. Most nets expect three channels, so composite onto a background first.
Which colour space for thresholding? HSV, almost always — hue is one axis there and three interacting ones in RGB.
Do CNNs learn colour features? Yes — the first layer of trained networks holds colour-opponent filters alongside the edge detectors.
Why per-channel normalisation rather than one global mean? Since the three channels have genuinely other distributions in natural pics. And treating them identically leaves a systematic offset.
Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.
What does this module say about “Start here”?
What does this module say about “The Core Idea: Channels as Layers”?
What does this module say about “Three counts per pixel”?
Split a colour image into red, green and blue channels and see how three grids of numbers combine into every pixel.