How to detect objects by colour using HSV filtering in OpenCV

Based on contributions by Glenn.

If the objects you need to detect are a distinct, consistent color, filtering by HSV (hue, saturation, value) is a fast and simple alternative to a full object-detection model. This how-to assumes you already know how to get an image or frame from a camera, and shows how to build a color mask and find the objects within it.

What you need

  • Python with OpenCV. On Linux: sudo apt-get install python3-opencv (or pip install opencv-python).
  • NumPy: pip install numpy
  • An image or camera feed containing objects of a known, distinct color.
  • A helper script to find the right HSV range for your object: HSV_Image_Testing.py (1.7 KB)

Steps

1. Load the image and work on a copy

import cv2
import numpy as np

image = cv2.imread("testfile.png")
image_copy = image.copy()  # keep the original untouched

2. Crop to a region of interest (optional but recommended)

Restricting detection to a region of interest (ROI) speeds up processing and prevents anything outside that area from affecting your results.

# Define your ROI boundaries
min_x, max_x = 445, 1630
min_y, max_y = 0, 775

# Extract ROI
roi = image_copy[min_y:max_y, min_x:max_x]

3. Convert the ROI to HSV

hsv_image = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

4. Find the HSV range for your object’s color

Run the HSV_Image_Testing.py helper script against a sample image to interactively find the hue/saturation/value range that matches your object.

5. Build the mask

Fill in the range you found and create a binary mask that keeps only pixels inside it.

# Lower/upper bound for hue, e.g. for green-yellowish tones
hue = [0, 100]
# Lower/upper bound for saturation, to exclude very low-saturation colors
saturation = [0, 100]
# Lower/upper bound for brightness (value)
value = [100, 255]

lower_hsv = np.array([hue[0], saturation[0], value[0]], dtype=np.uint8)
upper_hsv = np.array([hue[1], saturation[1], value[1]], dtype=np.uint8)

# Create a mask that identifies the regions of the image within the HSV range
mask = cv2.inRange(hsv_image, lower_hsv, upper_hsv)

Optional: clean up small noise and holes in the mask. Test whether this actually helps your specific image before keeping it.

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

Example mask, used here to detect a plate:

6. Find contours in the mask

contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
detected_blocks = []

7. Filter contours by shape and size

Loop over the contours and keep only the ones that match what you’re looking for. This example looks for roughly square blocks in a set size range, and normalizes each detection to a square centered on its bounding rectangle.

for contour in contours:
    x, y, w, h = cv2.boundingRect(contour)

    # Convert the bounding rect to a square, centered on the original rect
    side = max(w, h)
    center_x = x + w // 2
    center_y = y + h // 2
    square_x = center_x - side // 2
    square_y = center_y - side // 2

    w, h = side, side

    # Filter by size
    if 40 <= w <= 115 and 40 <= h <= 115:
        detected_blocks.append([x, y, w, h, 0])  # angle = 0 for squares

        # Draw the square back onto the full (non-cropped) image
        top_left = (square_x + min_x, square_y + min_y)
        bottom_right = (square_x + min_x + side, square_y + min_y + side)
        cv2.rectangle(image_copy, top_left, bottom_right, (0, 255, 0), 2)

The objects that passed the filter are now in detected_blocks, each entry holding [x, y, w, h, angle] in full-image coordinates.

Troubleshooting

  • No detections at all: double-check your HSV range with the testing script — lighting changes can shift hue/saturation more than expected.
  • Too many false positives: narrow the saturation and value ranges first (low-saturation, near-white/near-black areas are the most common source of noise), before tightening the hue range.
  • Detected boxes look noisy or fragmented: try the optional morphological open/close step in step 5.

Related


Rewritten and consolidated (Sept 2026) from the original student how-to’s: Detect objects using HSV.