pythonopencvimage-processingimage-thresholdingcolor-thief

Creating mask for shapes based on color in image python


I would like to extract some shapes and create mask based on shape colours from this image

original image

(the mask should be created for the red shapes) and below the expected figure expected figure with mask then I want to determine the area of these shapes.

import cv2
import numpy as np
from matplotlib import pyplot as plt
from colorthief import ColorThief

ct=ColorThief('image_red.png')
domainant_color = ct.get_color(quality=1)
palette = ct.get_palette(color_count=number_colors)
print(palette)
plt.imshow([[palette[i] for i in range(number_colors)]])
plt.show()

#LOAD IMAGE 
image_org=cv2.imread("image_red.png",0)
print(image_org.shape)

#FILTER /REMOVE NOISES
img_filter=cv2.medianBlur(image_org, 3)

#### THRESHOLDING..
thresh_value,thresh_img=cv2.threshold(img_filter,0,225,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
low=np.array([251, 4, 4], dtype = "uint8") #this is based on values from palette 
upper=np.array([251, 4, 4], dtype = "uint8")#this is based on values from palette 
img_f = cv2.cvtColor(thresh_img, cv2.COLOR_BGR2RGB)
mask1=cv2.inRange(img_f,lo,up)
io.imshow(mask1)
plt.show()

I started via the following steps:

  1. getting palette of most 5 dominant colors, then getting RGB of red color
  2. importing original image. Then I used filter to remove noises and then thresholding(but not sure if the thresholding is correct or not ).
  3. I applied cv2.Range and added lower and upper values of RGB of red to only get red shapes and create mask...

The result seems to give me the other phases, not the red color shapes. What can I do to get only the red shapes as I attached expected image file?


Solution

  • Unless your question is misleading, you are simply looking for coloured pixels. These are most readily found by converting to HSV colourspace and looking for pixels with high saturation, since black, white and grey pixels will all have zero saturation.

    The code will look like this:

    #!/usr/bin/env python3
    
    import numpy as np
    import cv2 as cv
    
    # Load image and convert to HSV colourspace
    im = cv.imread('YOURIMAGE.jpg')
    HSV = cv.cvtColor(im,cv.COLOR_BGR2HSV)
    
    # Select the Saturation channel
    S = HSV[:,:,1]
    
    # Save Saturation channel for debug purposes
    cv.imwrite('DEBUG-sat.png', S)
    
    # Decide some threshold
    # Save thresholded Saturation channel for debug purposes
    thr = S>50
    cv.imwrite('DEBUG_thr.png', thr*255)
    
    # Count the saturated pixels and total image area
    Nsat = np.count_nonzero(thr)
    area = im.shape[0]*im.shape[1]
    
    # Output results
    print(f'{Nsat=}, {area=}')
    

    That prints:

    Nsat=6774, area=436230
    

    And the intermediate, debug image looks like this:

    enter image description here


    If you want to detect the black pixels, you will want those with low Saturation (i.e. not colourful) and low value (i.e. not bright):

    # Mask of black pixels: Any Hue, Low Saturation, Low value
    lo = np.array([0, 0, 0], np.uint8)
    hi = np.array([255, 50, 30], np.uint8)
    blackMask = cv.inRange(HSV, lo, hi)
    cv.imwrite('DEBUG-black.png', blackMask)
    

    enter image description here

    If you want to detect the light grey pixels, you will want those with low Saturation (i.e. not colourful) and medium value (i.e. neither bright white nor dark black):

    # Mask of light grey pixels: Any Hue, Low Saturation, Medium High value
    lo = np.array([0, 0, 120], np.uint8)
    hi = np.array([255, 50, 200], np.uint8)
    greyMask = cv.inRange(HSV, lo, hi)
    cv.imwrite('DEBUG-grey.png', greyMask)
    

    enter image description here