Bounding box on objects based on color python

The approach is fairly straightforward. We begin by converting to HSV and grabbing only the hue channel.

image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h,_,_ = cv2.split(image_hsv)

Next, we find the dominant hues -- first count the occurrences of each hue using numpy.bincount (we flatten the hue channel image to make it one-dimensional):

bins = np.bincount(h.flatten())

And then find which ones are common enough using numpy.where:

MIN_PIXEL_CNT_PCT = (1.0/20.0)
peaks = np.where(bins > (h.size * MIN_PIXEL_CNT_PCT))[0]

Now that we've identified all the dominant hues, we can repeatedly process the image to find the areas correspond to each of them:

for i, peak in enumerate(peaks):

We begin by creating a mask which selects all the pixels of this hue (cv2.inRange, and then extracting the corresponding parts from the input BGR image (cv2.bitwise_and.

mask = cv2.inRange(h, peak, peak)
blob = cv2.bitwise_and(image, image, mask=mask)

Next, we find the contours (cv2.findContours of all the continuous areas of this hue, so that we can process each of them individually

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

Now, for each of the identified continuous area

for j, contour in enumerate(contours):

We determine the bounding box (cv2.boundingRect, and create a mask corresponding to just this contour by filling the contour polygon with white (numpy.zeros_like and cv2.drawContours)

bbox = cv2.boundingRect(contour)
contour_mask = np.zeros_like(mask)
cv2.drawContours(contour_mask, contours, j, 255, -1)

Then we can extra just the ROI corresponding to the bounding box

region = blob.copy()[bbox[1]:bbox[1]+bbox[3],bbox[0]:bbox[0]+bbox[2]]
region_mask = contour_mask[bbox[1]:bbox[1]+bbox[3],bbox[0]:bbox[0]+bbox[2]]
region_masked = cv2.bitwise_and(region, region, mask=region_mask)

Or visualize (cv2.rectangle the bounding box:

result = cv2.bitwise_and(blob, blob, mask=contour_mask)
top_left, bottom_right = (bbox[0], bbox[1]), (bbox[0]+bbox[2], bbox[1]+bbox[3])
cv2.rectangle(result, top_left, bottom_right, (255, 255, 255), 2)

Or do any other processing you want.


Full Script

import cv2
import numpy as np

# Minimum percentage of pixels of same hue to consider dominant colour
MIN_PIXEL_CNT_PCT = (1.0/20.0)

image = cv2.imread('colourblobs.png')
if image is None:
    print("Failed to load iamge.")
    exit(-1)

image_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# We're only interested in the hue
h,_,_ = cv2.split(image_hsv)
# Let's count the number of occurrences of each hue
bins = np.bincount(h.flatten())
# And then find the dominant hues
peaks = np.where(bins > (h.size * MIN_PIXEL_CNT_PCT))[0]

# Now let's find the shape matching each dominant hue
for i, peak in enumerate(peaks):
    # First we create a mask selecting all the pixels of this hue
    mask = cv2.inRange(h, peak, peak)
    # And use it to extract the corresponding part of the original colour image
    blob = cv2.bitwise_and(image, image, mask=mask)

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

    for j, contour in enumerate(contours):
        bbox = cv2.boundingRect(contour)
        # Create a mask for this contour
        contour_mask = np.zeros_like(mask)
        cv2.drawContours(contour_mask, contours, j, 255, -1)

        print "Found hue %d in region %s." % (peak, bbox)
        # Extract and save the area of the contour
        region = blob.copy()[bbox[1]:bbox[1]+bbox[3],bbox[0]:bbox[0]+bbox[2]]
        region_mask = contour_mask[bbox[1]:bbox[1]+bbox[3],bbox[0]:bbox[0]+bbox[2]]
        region_masked = cv2.bitwise_and(region, region, mask=region_mask)
        file_name_section = "colourblobs-%d-hue_%03d-region_%d-section.png" % (i, peak, j)
        cv2.imwrite(file_name_section, region_masked)
        print " * wrote '%s'" % file_name_section

        # Extract the pixels belonging to this contour
        result = cv2.bitwise_and(blob, blob, mask=contour_mask)
        # And draw a bounding box
        top_left, bottom_right = (bbox[0], bbox[1]), (bbox[0]+bbox[2], bbox[1]+bbox[3])
        cv2.rectangle(result, top_left, bottom_right, (255, 255, 255), 2)
        file_name_bbox = "colourblobs-%d-hue_%03d-region_%d-bbox.png" % (i, peak, j)
        cv2.imwrite(file_name_bbox, result)
        print " * wrote '%s'" % file_name_bbox

Console Output

Found hue 32 in region (186, 184, 189, 122).
 * wrote 'colourblobs-0-hue_032-region_0-section.png'
 * wrote 'colourblobs-0-hue_032-region_0-bbox.png'
Found hue 71 in region (300, 197, 1, 1).
 * wrote 'colourblobs-1-hue_071-region_0-section.png'
 * wrote 'colourblobs-1-hue_071-region_0-bbox.png'
Found hue 71 in region (301, 195, 1, 1).
 * wrote 'colourblobs-1-hue_071-region_1-section.png'
 * wrote 'colourblobs-1-hue_071-region_1-bbox.png'
Found hue 71 in region (319, 190, 1, 1).
 * wrote 'colourblobs-1-hue_071-region_2-section.png'
 * wrote 'colourblobs-1-hue_071-region_2-bbox.png'
Found hue 71 in region (323, 176, 52, 14).
 * wrote 'colourblobs-1-hue_071-region_3-section.png'
 * wrote 'colourblobs-1-hue_071-region_3-bbox.png'
Found hue 71 in region (45, 10, 330, 381).
 * wrote 'colourblobs-1-hue_071-region_4-section.png'
 * wrote 'colourblobs-1-hue_071-region_4-bbox.png'
Found hue 109 in region (0, 0, 375, 500).
 * wrote 'colourblobs-2-hue_109-region_0-section.png'
 * wrote 'colourblobs-2-hue_109-region_0-bbox.png'
Found hue 166 in region (1, 397, 252, 103).
 * wrote 'colourblobs-3-hue_166-region_0-section.png'
 * wrote 'colourblobs-3-hue_166-region_0-bbox.png'

Example Output Images

Yellow bounding box:

Hue 32 bounding box

Yellow extracted region:

Hue 32 section

Biggest green bounding box (there are several other small disjoint areas as well):

Hue 71 largest bounding box

...and the corresponding extracted region:

Hue 71 largest section