Color Spaces

Color Spaces

Overview

In computer vision, a color space is a method of representing colors. OpenCV uses the BGR color space by default, but other color spaces such as HSV and LAB are more effective for specific tasks. In this document, we'll learn about the characteristics of various color spaces, conversion methods, and color-based object tracking.

Difficulty: ⭐⭐ (Beginner-Intermediate)

Learning Objectives: - Understand the difference between BGR and RGB - Learn the principles and applications of HSV color space - Use cv2.cvtColor() for color space conversion - Perform channel splitting/merging - Implement color-based object tracking


Table of Contents

  1. BGR vs RGB
  2. cv2.cvtColor() and Color Conversion Constants
  3. HSV Color Space
  4. LAB Color Space
  5. Grayscale Conversion
  6. Channel Splitting and Merging
  7. Color-Based Object Tracking
  8. Practice Problems
  9. Next Steps
  10. References

1. BGR vs RGB

OpenCV's Default Color Order

┌─────────────────────────────────────────────────────────────────┐
│                    BGR vs RGB Comparison                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   OpenCV (BGR)                 Most Libraries (RGB)             │
│   ┌─────────────┐              ┌─────────────┐                 │
│   │ B │ G │ R │               │ R │ G │ B │                   │
│   │[0]│[1]│[2]│               │[0]│[1]│[2]│                   │
│   └─────────────┘              └─────────────┘                 │
│                                                                 │
│   Pure red:                    Pure red:                        │
│   [0, 0, 255]                  [255, 0, 0]                      │
│                                                                 │
│   Pure blue:                   Pure blue:                       │
│   [255, 0, 0]                  [0, 0, 255]                      │
│                                                                 │
│   OpenCV libraries:            RGB libraries:                   │
│   - cv2.imread()               - matplotlib                     │
│   - cv2.imshow()               - PIL/Pillow                     │
│   - cv2.imwrite()              - Tkinter                        │
│                                - Web browsers (CSS/HTML)        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Why BGR is Used

It's for historical reasons. Early cameras and display hardware stored data in BGR order, and OpenCV followed this convention.

BGR ↔ RGB Conversion

import cv2
import numpy as np

# Read image (BGR)
img_bgr = cv2.imread('image.jpg')

# BGR → RGB conversion
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

# RGB → BGR conversion
img_bgr_back = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)

# Direct conversion with NumPy (slicing)
img_rgb_np = img_bgr[:, :, ::-1]  # Reverse channel order
img_rgb_np = img_bgr[..., ::-1]   # Same result

# Channel-wise swap
b, g, r = cv2.split(img_bgr)
img_rgb_split = cv2.merge([r, g, b])

Using with matplotlib

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('image.jpg')

# Wrong display (BGR as-is → colors are swapped)
plt.figure(figsize=(12, 4))

plt.subplot(1, 3, 1)
plt.imshow(img)  # BGR as-is → red and blue swapped
plt.title('Wrong (BGR)')
plt.axis('off')

# Correct display (convert to RGB)
plt.subplot(1, 3, 2)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.title('Correct (RGB)')
plt.axis('off')

# Grayscale
plt.subplot(1, 3, 3)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray, cmap='gray')
plt.title('Grayscale')
plt.axis('off')

plt.tight_layout()
plt.show()

2. cv2.cvtColor() and Color Conversion Constants

Basic Usage

import cv2

img = cv2.imread('image.jpg')

# cv2.cvtColor(src, code) - color space conversion
dst = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

Major Conversion Codes

┌─────────────────────────────────────────────────────────────────┐
│                     Major Color Conversion Codes                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   BGR ↔ Other Color Spaces                                      │
│   ├── COLOR_BGR2RGB / COLOR_RGB2BGR                             │
│   ├── COLOR_BGR2GRAY / COLOR_GRAY2BGR                           │
│   ├── COLOR_BGR2HSV / COLOR_HSV2BGR                             │
│   ├── COLOR_BGR2LAB / COLOR_LAB2BGR                             │
│   ├── COLOR_BGR2YCrCb / COLOR_YCrCb2BGR                         │
│   └── COLOR_BGR2HLS / COLOR_HLS2BGR                             │
│                                                                 │
│   RGB ↔ Other Color Spaces                                      │
│   ├── COLOR_RGB2GRAY / COLOR_GRAY2RGB                           │
│   ├── COLOR_RGB2HSV / COLOR_HSV2RGB                             │
│   ├── COLOR_RGB2LAB / COLOR_LAB2RGB                             │
│   └── COLOR_RGB2HLS / COLOR_HLS2RGB                             │
│                                                                 │
│   Special Conversions                                           │
│   ├── COLOR_BGR2HSV_FULL  (H: 0-255)                            │
│   ├── COLOR_BGR2HSV       (H: 0-179)                            │
│   └── COLOR_BayerBG2BGR   (Bayer → BGR)                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Conversion Examples

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# Convert to various color spaces
conversions = {
    'Original (RGB)': img_rgb,
    'Grayscale': cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
    'HSV': cv2.cvtColor(img, cv2.COLOR_BGR2HSV),
    'LAB': cv2.cvtColor(img, cv2.COLOR_BGR2LAB),
    'YCrCb': cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb),
    'HLS': cv2.cvtColor(img, cv2.COLOR_BGR2HLS),
}

fig, axes = plt.subplots(2, 3, figsize=(12, 8))
axes = axes.flatten()

for ax, (name, converted) in zip(axes, conversions.items()):
    if len(converted.shape) == 2:
        ax.imshow(converted, cmap='gray')
    else:
        ax.imshow(converted)
    ax.set_title(name)
    ax.axis('off')

plt.tight_layout()
plt.show()

3. HSV Color Space

What is HSV?

HSV represents colors using Hue, Saturation, and Value.

┌─────────────────────────────────────────────────────────────────┐
│                      HSV Color Space                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   H (Hue) - Color                                               │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  0°    60°   120°   180°   240°   300°   360°          │   │
│   │  Red   Yellow Green  Cyan   Blue  Magenta Red          │   │
│   │  ├──────┼──────┼──────┼──────┼──────┼──────┤            │   │
│   │  0     30     60     90    120    150    179            │   │
│   │      (OpenCV H range: 0-179)                            │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   S (Saturation) - Saturation (0-255)                           │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  0 (grayscale/gray)  ──────────────▶  255 (pure color)  │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   V (Value) - Brightness (0-255)                                │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  0 (black)  ──────────────────▶  255 (bright)           │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│                        V (Brightness)                           │
│                          ▲                                       │
│                          │    White                              │
│                          │   /                                   │
│                          │  /                                    │
│                          │ /     Pure color                      │
│                          │/───────●                              │
│                          │        ╲                              │
│                          │         ╲  S (Saturation)             │
│                          │          ╲                            │
│                          ●───────────╲───▶ H (Hue, circular)     │
│                        Black                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

HSV Conversion and Channel Inspection

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('image.jpg')

# BGR → HSV conversion
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Split channels
h, s, v = cv2.split(hsv)

# Visualization
fig, axes = plt.subplots(2, 2, figsize=(10, 10))

axes[0, 0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
axes[0, 0].set_title('Original')

axes[0, 1].imshow(h, cmap='hsv')  # Use hsv colormap for Hue
axes[0, 1].set_title('H (Hue)')

axes[1, 0].imshow(s, cmap='gray')
axes[1, 0].set_title('S (Saturation)')

axes[1, 1].imshow(v, cmap='gray')
axes[1, 1].set_title('V (Value)')

for ax in axes.flatten():
    ax.axis('off')

plt.tight_layout()
plt.show()

Advantages of HSV

import cv2
import numpy as np

# RGB/BGR is sensitive to lighting changes
# In HSV, only the V channel is affected → favorable for color detection

# Example: Red detection
img = cv2.imread('red_objects.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Define red range (Hue near 0 or 180)
# Red is at both ends of the Hue range
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([179, 255, 255])

# Create masks
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = mask1 | mask2  # Combine two masks

# Display result
result = cv2.bitwise_and(img, img, mask=mask)

cv2.imshow('Original', img)
cv2.imshow('Mask', mask)
cv2.imshow('Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

HSV Ranges for Common Colors

┌─────────────────────────────────────────────────────────────────┐
│                    Common Color HSV Ranges (OpenCV)             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Color      H (Hue)        S (Saturation)   V (Value)          │
│   ──────────────────────────────────────────────────────────    │
│   Red        0-10, 160-179   100-255         100-255            │
│   Orange     10-25           100-255         100-255            │
│   Yellow     25-35           100-255         100-255            │
│   Green      35-85           100-255         100-255            │
│   Cyan       85-95           100-255         100-255            │
│   Blue       95-130          100-255         100-255            │
│   Magenta    130-160         100-255         100-255            │
│                                                                 │
│   White      0-179           0-30            200-255            │
│   Black      0-179           0-255           0-50               │
│   Gray       0-179           0-30            50-200             │
│                                                                 │
│   Note: Ranges need adjustment based on lighting conditions     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4. LAB Color Space

What is LAB?

LAB (or CIELAB) is a color space based on human color perception.

┌─────────────────────────────────────────────────────────────────┐
│                      LAB Color Space                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   L (Lightness) - Brightness                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│     0 (black)  ──────────────────────▶  255 (white)           │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   A - Green(-)  Red(+)                                         │
│   ┌─────────────────────────────────────────────────────────┐   │
│     0 (green)  ────── 128 (neutral) ──────  255 (red)         │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   B - Blue(-)  Yellow(+)                                       │
│   ┌─────────────────────────────────────────────────────────┐   │
│     0 (blue)  ────── 128 (neutral) ──────  255 (yellow)       │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│                     +B (Yellow)                                  │
│                                                                │
│                                                                │
│            -A ◀────────┼────────▶ +A                            │
│          (Green)               (Red)                           │
│                                                                │
│                                                                │
│                     -B (Blue)                                    │
│                                                                 │
│   Advantages:                                                   │
│   - Color distance calculation similar to human vision          │
│   - Brightness and color are separated                          │
│   - Useful for color correction and color transfer              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

LAB Conversion and Application

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('image.jpg')

# BGR → LAB conversion
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

# Split channels
l, a, b = cv2.split(lab)

# Adjust L channel for brightness correction
l_adjusted = cv2.add(l, 30)  # Increase brightness
l_adjusted = np.clip(l_adjusted, 0, 255).astype(np.uint8)

# Merge back
lab_adjusted = cv2.merge([l_adjusted, a, b])
result = cv2.cvtColor(lab_adjusted, cv2.COLOR_LAB2BGR)

# Visualization
fig, axes = plt.subplots(2, 3, figsize=(12, 8))

axes[0, 0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
axes[0, 0].set_title('Original')

axes[0, 1].imshow(l, cmap='gray')
axes[0, 1].set_title('L (Lightness)')

axes[0, 2].imshow(a, cmap='RdYlGn_r')
axes[0, 2].set_title('A (Green-Red)')

axes[1, 0].imshow(b, cmap='YlGnBu_r')
axes[1, 0].set_title('B (Blue-Yellow)')

axes[1, 1].imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
axes[1, 1].set_title('Brightness Adjusted')

for ax in axes.flatten():
    ax.axis('off')
axes[1, 2].axis('off')

plt.tight_layout()
plt.show()

CLAHE for LAB Brightness Correction

import cv2

img = cv2.imread('dark_image.jpg')

# LAB conversion
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)

# Apply CLAHE (Contrast Limited Adaptive Histogram Equalization)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
l_clahe = clahe.apply(l)

# Merge back
lab_clahe = cv2.merge([l_clahe, a, b])
result = cv2.cvtColor(lab_clahe, cv2.COLOR_LAB2BGR)

cv2.imshow('Original', img)
cv2.imshow('CLAHE Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

5. Grayscale Conversion

Conversion Principle

┌─────────────────────────────────────────────────────────────────┐
                   Grayscale Conversion Principle                
├─────────────────────────────────────────────────────────────────┤
                                                                 
   BGR  Grayscale conversion formula:                           
                                                                 
   Gray = 0.114 × B + 0.587 × G + 0.299 × R                     
                                                                 
   ┌─────────────────────────────────────────────────────────┐   
      Why not simple average?                                  
                                                               
      Human eyes are most sensitive to green and least to blue 
      Therefore, green (G) has the highest weight (0.587)     
   └─────────────────────────────────────────────────────────┘   
                                                                 
   Color image                     Grayscale                     
   ┌───────────────┐              ┌───────────────┐             
    B  G  R                     Gray                   
   200100 50    ───▶             121                   
   └───────────────┘              └───────────────┘             
   0.114×200 + 0.587×100 + 0.299×50 = 121.45                    
                                                                 
└─────────────────────────────────────────────────────────────────┘

Grayscale Conversion Methods

import cv2
import numpy as np

img = cv2.imread('image.jpg')

# Method 1: cvtColor (recommended)
gray1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Method 2: Read directly with imread
gray2 = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# Method 3: Manual calculation with NumPy (for learning)
b, g, r = cv2.split(img)
gray3 = (0.114 * b + 0.587 * g + 0.299 * r).astype(np.uint8)

# Method 4: Simple average (not recommended - visually unnatural)
gray4 = np.mean(img, axis=2).astype(np.uint8)

# Compare results
print(f"cvtColor result: {gray1.shape}")
print(f"Manual calculation result: {gray3.shape}")
print(f"Max difference: {np.max(np.abs(gray1.astype(int) - gray3.astype(int)))}")

Grayscale → Color (Pseudo Color)

import cv2

gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# Grayscale → 3 channels (still grayscale)
gray_3ch = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)

# Apply colormap (heatmap, etc.)
# COLORMAP_JET, COLORMAP_HOT, COLORMAP_RAINBOW, etc.
colormap = cv2.applyColorMap(gray, cv2.COLORMAP_JET)

cv2.imshow('Grayscale', gray)
cv2.imshow('Colormap', colormap)
cv2.waitKey(0)
cv2.destroyAllWindows()

6. Channel Splitting and Merging

cv2.split() and cv2.merge()

import cv2
import numpy as np

img = cv2.imread('image.jpg')

# Split channels
b, g, r = cv2.split(img)

# Or use NumPy indexing (faster)
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]

# Merge channels
merged = cv2.merge([b, g, r])  # BGR order

# Change channel order when merging (BGR → RGB)
rgb = cv2.merge([r, g, b])

# Combine with empty channels (display single channel only)
zeros = np.zeros_like(b)
only_blue = cv2.merge([b, zeros, zeros])
only_green = cv2.merge([zeros, g, zeros])
only_red = cv2.merge([zeros, zeros, r])

Channel Visualization

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('image.jpg')
b, g, r = cv2.split(img)

fig, axes = plt.subplots(2, 3, figsize=(12, 8))

# Original
axes[0, 0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
axes[0, 0].set_title('Original')

# Each channel (as grayscale)
axes[0, 1].imshow(r, cmap='gray')
axes[0, 1].set_title('Red Channel')

axes[0, 2].imshow(g, cmap='gray')
axes[0, 2].set_title('Green Channel')

axes[1, 0].imshow(b, cmap='gray')
axes[1, 0].set_title('Blue Channel')

# Each channel (in color)
zeros = np.zeros_like(b)
axes[1, 1].imshow(cv2.merge([zeros, zeros, r]))  # RGB order
axes[1, 1].set_title('Red Only')

axes[1, 2].imshow(cv2.merge([zeros, g, zeros]))
axes[1, 2].set_title('Green Only')

for ax in axes.flatten():
    ax.axis('off')

plt.tight_layout()
plt.show()

Channel Manipulation Examples

import cv2
import numpy as np

img = cv2.imread('image.jpg')

# 1. Boost specific channel
b, g, r = cv2.split(img)
r_boost = np.clip(r.astype(np.int16) + 50, 0, 255).astype(np.uint8)
warm = cv2.merge([b, g, r_boost])  # Warm tone

# 2. Swap channels
b, g, r = cv2.split(img)
swapped = cv2.merge([r, g, b])  # Swap R and B

# 3. Grayscale by channel average
b, g, r = cv2.split(img)
gray_avg = ((b.astype(np.int16) + g + r) // 3).astype(np.uint8)

# 4. Keep only specific channel (rest to 0)
b, g, r = cv2.split(img)
only_r = cv2.merge([np.zeros_like(b), np.zeros_like(g), r])

7. Color-Based Object Tracking

Color Filtering with inRange()

┌─────────────────────────────────────────────────────────────────┐
│                   Color-Based Object Tracking Pipeline          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Input image (BGR)                                             │
│        │                                                        │
│        ▼                                                        │
│   HSV conversion                                                │
│        │                                                        │
│        ▼                                                        │
│   cv2.inRange(hsv, lower, upper) ──▶ Binary mask               │
│        │                                                        │
│        ▼                                                        │
│   Noise removal (morphological operations)                      │
│        │                                                        │
│        ▼                                                        │
│   Contour detection                                             │
│        │                                                        │
│        ▼                                                        │
│   Extract object position/size                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Color Tracking Implementation

import cv2
import numpy as np

def track_color(img, lower_hsv, upper_hsv):
    """Track objects in a specific color range"""
    # HSV conversion
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # Create mask
    mask = cv2.inRange(hsv, lower_hsv, upper_hsv)

    # Remove noise
    kernel = np.ones((5, 5), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

    # Detect contours
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
                                    cv2.CHAIN_APPROX_SIMPLE)

    # Draw results
    result = img.copy()
    for contour in contours:
        area = cv2.contourArea(contour)
        if area > 500:  # Minimum area filter
            x, y, w, h = cv2.boundingRect(contour)
            cv2.rectangle(result, (x, y), (x+w, y+h), (0, 255, 0), 2)

            # Center point
            cx, cy = x + w//2, y + h//2
            cv2.circle(result, (cx, cy), 5, (0, 0, 255), -1)

    return result, mask


# Example usage: Track blue
img = cv2.imread('blue_objects.jpg')

lower_blue = np.array([100, 100, 100])
upper_blue = np.array([130, 255, 255])

result, mask = track_color(img, lower_blue, upper_blue)

cv2.imshow('Original', img)
cv2.imshow('Mask', mask)
cv2.imshow('Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Real-Time Color Tracking (Webcam)

import cv2
import numpy as np

def nothing(x):
    pass

# Create trackbars
cv2.namedWindow('Trackbars')
cv2.createTrackbar('H_Low', 'Trackbars', 0, 179, nothing)
cv2.createTrackbar('H_High', 'Trackbars', 179, 179, nothing)
cv2.createTrackbar('S_Low', 'Trackbars', 100, 255, nothing)
cv2.createTrackbar('S_High', 'Trackbars', 255, 255, nothing)
cv2.createTrackbar('V_Low', 'Trackbars', 100, 255, nothing)
cv2.createTrackbar('V_High', 'Trackbars', 255, 255, nothing)

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # Read trackbar values
    h_low = cv2.getTrackbarPos('H_Low', 'Trackbars')
    h_high = cv2.getTrackbarPos('H_High', 'Trackbars')
    s_low = cv2.getTrackbarPos('S_Low', 'Trackbars')
    s_high = cv2.getTrackbarPos('S_High', 'Trackbars')
    v_low = cv2.getTrackbarPos('V_Low', 'Trackbars')
    v_high = cv2.getTrackbarPos('V_High', 'Trackbars')

    lower = np.array([h_low, s_low, v_low])
    upper = np.array([h_high, s_high, v_high])

    # HSV conversion and mask
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, lower, upper)
    result = cv2.bitwise_and(frame, frame, mask=mask)

    cv2.imshow('Frame', frame)
    cv2.imshow('Mask', mask)
    cv2.imshow('Result', result)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

Multi-Color Tracking

import cv2
import numpy as np

# Define multiple colors
colors = {
    'red': {
        'lower1': np.array([0, 100, 100]),
        'upper1': np.array([10, 255, 255]),
        'lower2': np.array([160, 100, 100]),
        'upper2': np.array([179, 255, 255]),
        'color': (0, 0, 255)
    },
    'green': {
        'lower': np.array([35, 100, 100]),
        'upper': np.array([85, 255, 255]),
        'color': (0, 255, 0)
    },
    'blue': {
        'lower': np.array([100, 100, 100]),
        'upper': np.array([130, 255, 255]),
        'color': (255, 0, 0)
    }
}

def track_multiple_colors(img, colors):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    result = img.copy()

    for name, params in colors.items():
        # Create mask
        if 'lower1' in params:  # For colors like red with two ranges
            mask1 = cv2.inRange(hsv, params['lower1'], params['upper1'])
            mask2 = cv2.inRange(hsv, params['lower2'], params['upper2'])
            mask = mask1 | mask2
        else:
            mask = cv2.inRange(hsv, params['lower'], params['upper'])

        # Detect contours
        contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
                                        cv2.CHAIN_APPROX_SIMPLE)

        for contour in contours:
            if cv2.contourArea(contour) > 500:
                x, y, w, h = cv2.boundingRect(contour)
                cv2.rectangle(result, (x, y), (x+w, y+h), params['color'], 2)
                cv2.putText(result, name, (x, y-10),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, params['color'], 2)

    return result

8. Practice Problems

Exercise 1: Color Palette Generator

Define 16 main colors (red, orange, yellow, green, cyan, blue, magenta, pink, white, black, gray, etc.) in BGR values and create a palette image by arranging 100x100 color chips in a 4x4 grid.

Exercise 2: HSV Color Picker

Write a program that outputs the HSV values of a pixel when clicked on an image and highlights all areas with similar colors.

# Hint: use cv2.setMouseCallback()
def on_click(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        # Output HSV value of clicked position
        pass

Exercise 3: Channel Swap Effects

Create 6 different effects by combining image channels in various ways (BGR, BRG, GBR, GRB, RBG, RGB) and compare them.

Exercise 4: Skin Color Detection

Detect skin-colored areas in an image using HSV and YCrCb color spaces. Compare the results of both methods.

# Example HSV ranges for skin color
# H: 0-50, S: 20-150, V: 70-255

# Example YCrCb ranges for skin color
# Y: 0-255, Cr: 135-180, Cb: 85-135

Exercise 5: Color Transition Animation

Create an animation where the image colors change like a rainbow by gradually increasing the H channel.

# Hint
for h_shift in range(0, 180, 5):
    h_channel = (original_h + h_shift) % 180
    # ...

9. Next Steps

In 04_Geometric_Transforms.md, you'll learn about image resizing, rotation, flipping, affine/perspective transformations, and more!

Next topics: - cv2.resize() and interpolation methods - Rotation and flipping functions - Affine transformation (translation, rotation, scaling) - Perspective transformation (document scanning)


10. References

Official Documentation

Folder Related Content
02_Image_Basics.md Image reading, pixel access
07_Thresholding.md HSV-based thresholding

Color Space References

to navigate between lessons