Python: Contour around rectangle based on specific color on a dark image (OpenCV)

Viewed 5381

since I'm trying to enhance my skills with OpenCV in Python, I would like to know what's the best way of extracting a specific gray tone out of a image with mostly dark colors.

To start of, I created a test image in order to test different methods with OpenCV:

Lets say I want to extract a specific color in this image and add a border to it. For now I chose the gray rectangle in the middle with the color (33, 33, 34 RGB), see following:

(Here's the image without the red border in order you want to test your ideas: https://i.stack.imgur.com/Zf8Vb.png)

example image with border

This is what I've tried so far, but it's not quite working:

img = cv2.imread(path) #Read input image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Convert from BGR to HSV color space
saturation_plane = hsv[:, :, 1] # all black/white/gray pixels are zero, and colored pixels are above zero
_, thresh = cv2.threshold(saturation_plane, 8, 255, cv2.THRESH_BINARY) # Apply threshold on s
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # draw all contours 
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()

for contour in contours:
   (x, y, w, h) = cv2.boundingRect(contour) # compute the bounding box for the contour
   if width is equal to the width of the rectangle i want to extract:
       draw contour

What if the size of the rectangle is not fixed, so that I won't be able to detect it through its width/height? Moreover, is it better to convert the image into a gray scale instead of HSV? I'm just new to it and I would like to hear your way of achieving this.

Thanks in advance.

1 Answers

In case the specific color is known, you may start with gray = np.all(img == (34, 33, 33), 2).

The result is a logical matrix with True where BGR = (34, 33, 33), and False where it is not.
Note: OpenCV color ordering is BGR and not RGB.

  • Convert the logical matrix to uint8: gray = gray.astype(np.uint8)*255
  • Use findContours on gray image.

Converting the image to HSV in not going useful in case you want to find the blue rectangle, but not a gray rectangle with very specific RGB values.

The following code finds the contour with maximum size with color (33, 33, 34 RGB):

import numpy as np
import cv2

# Read input image
img = cv2.imread('rectangles.png')

# Gel all pixels in the image - where BGR = (34, 33, 33), OpenCV colors order is BGR not RGB
gray = np.all(img == (34, 33, 33), 2)  # gray is a logical matrix with True where BGR = (34, 33, 33).

# Convert logical matrix to uint8
gray = gray.astype(np.uint8)*255

# Find contours
cnts = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]  # Use index [-2] to be compatible to OpenCV 3 and 4

# Get contour with maximum area
c = max(cnts, key=cv2.contourArea)

x, y, w, h = cv2.boundingRect(c)

# Draw green rectangle for testing
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), thickness = 2)

# Show result
cv2.imshow('gray', gray)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Result:

gray:
enter image description here

img:
enter image description here


In case you don't know the specific color of the mostly dark colors, you may find all contours, and search for the one with the lowest gray value:

import numpy as np
import cv2

# Read input image
img = cv2.imread('rectangles.png')

# Convert from BGR to Gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Apply threshold on gray
_, thresh = cv2.threshold(gray, 8, 255, cv2.THRESH_BINARY)

# Find contours on thresh
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]  # Use index [-2] to be compatible to OpenCV 3 and 4

min_level = 255
min_c = []

#Iterate contours, and find the darkest:
for c in cnts:
    x, y, w, h = cv2.boundingRect(c)

    # Ignore contours that are very thin (like edges)
    if w > 5 and h > 5:
        level = gray[y+h//2, x+w//2]  # Get gray level of center pixel

        if level < min_level:
            # Update min_level abd min_c
            min_level = level
            min_c = c

x, y, w, h = cv2.boundingRect(min_c)

# Draw red rectangle for testing
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), thickness = 2)

# Show result
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Result:
enter image description here

Related