Computer Vision Chapter 3

Color spaces

The same scene can be described with different coordinate systems for color. Choosing the right space makes thresholds, segmentation, and learning easier. Here: RGB/BGR, grayscale, HSV, LAB, YCrCb—and how to convert in OpenCV.

Why color spaces matter

A color space defines how we encode color as numbers. RGB (red, green, blue) is natural for displays: we mix light. For algorithms, RGB channels are correlated—changing illumination shifts all three, which makes simple thresholding on “red” unstable. Spaces like HSV separate hue (what color) from brightness, which helps picking a colored object under varying light. LAB is designed to be more perceptually uniform: equal numeric steps are closer to equal visual differences than in RGB.

RGB and OpenCV’s BGR

Each pixel stores three channel values. In theory RGB orders channels R–G–B. OpenCV loads color images as BGR by default, so img[y, x] returns [B, G, R]. Matplotlib and most deep-learning stacks expect RGB—always convert when mixing tools.

import cv2

bgr = cv2.imread("photo.jpg")
if bgr is not None:
    rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
    # rgb ready for matplotlib: plt.imshow(rgb)

Grayscale from color

Grayscale collapses three channels into one luminance value. A common weighted sum (ITU-R BT.601, used in many libraries) is:

Y ≈ 0.299·R + 0.587·G + 0.114·B — green gets the largest weight because human vision is most sensitive to mid-green.

import cv2
import numpy as np

bgr = cv2.imread("photo.jpg")
gray_cv = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)  # same idea as luminance formula

# Manual on a float RGB array in [0,1] (illustrative):
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
y = 0.299 * rgb[...,0] + 0.587 * rgb[...,1] + 0.114 * rgb[...,2]

OpenCV’s COLOR_BGR2GRAY uses fixed integer coefficients suited for 8-bit images; results match the standard luma idea closely.

HSV: hue, saturation, value

Hue is the color wheel angle (often 0–179 in OpenCV’s 8-bit HSV). Saturation is “how vivid” the color is (gray → 0). Value is brightness. Separating hue from value makes it easier to threshold a specific color range (balls, fruits, signs) under modest lighting changes.

import cv2

bgr = cv2.imread("photo.jpg")
hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)

# Example: keep pixels with hue between h0 and h1 (tune per scene)
h0, h1 = 20, 35
mask = (hsv[:, :, 0] >= h0) & (hsv[:, :, 0] <= h1)
# combine with saturation/value thresholds for cleaner masks

In OpenCV, H is 0–179 for 8-bit images (half OpenCV’s 0–360 convention). Always visualize masks; lighting and white balance change optimal ranges.

LAB and YCrCb

LAB (L*a*b*)

L* is lightness; a* and b* are color-opponent channels. Useful for perceptual distance and some segmentation tasks. OpenCV: cv2.COLOR_BGR2LAB.

YCrCb

Separates luma (Y) from chrominance (Cr, Cb). Common in video and JPEG. Skin-color heuristics sometimes use Cr/Cb. OpenCV: cv2.COLOR_BGR2YCrCb.

import cv2

bgr = cv2.imread("photo.jpg")
lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
ycrcb = cv2.cvtColor(bgr, cv2.COLOR_BGR2YCrCb)

Conversion tips

  • Prefer cv2.cvtColor over hand-rolled formulas unless you need custom behavior—rounding and ranges are handled consistently.
  • After conversion, check dtype and value ranges (HSV and LAB ranges differ from 0–255 per channel in OpenCV).
  • For ML, many models expect RGB, NCHW or NHWC normalized floats; convert once in the dataset pipeline.

Takeaways

  • OpenCV color images are BGR; convert for RGB consumers.
  • HSV helps separate hue from brightness for simple color-based masks.
  • LAB and YCrCb separate lightness from chroma for perceptual or video-style processing.

Quick FAQ

For 8-bit HSV, OpenCV stores hue as half degrees (0–179 maps to 0°–358°) so values fit in a byte.

No—choose by task. CNNs often learn in RGB; classical segmentation may benefit from HSV or LAB. Experiment and validate on your data.