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.cvtColorover hand-rolled formulas unless you need custom behavior—rounding and ranges are handled consistently. - After conversion, check
dtypeand 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.