Search code examples
pythonopencvcolors

How to keep Only Black color text in the image using OpenCV Python?


I have the following image:

This image.

I want to keep only the black colored text 0790 and remove all from the picture. This stackoverflow question teaches to remove the color. However, I need to keep the color, not remove it.


Solution

  • A possible solution involves converting the image to the CMYK color space and extracting the K (Key - black) channel, thresholding it and applying some morphology to clean up the binary image.

    OpenCV does not implement the conversion from BGR to CMYK, so we have to compute the K channel manually. The code would look like this:

    # Imports
    import cv2
    import numpy as np
    
    # Read image
    imagePath = "D://opencvImages//"
    inputImage = cv2.imread(imagePath + "A6RXi.png")
    
    # Conversion to CMYK (just the K channel):
    
    # Convert to float and divide by 255:
    imgFloat = inputImage.astype(np.float) / 255.
    
    # Calculate channel K:
    kChannel = 1 - np.max(imgFloat, axis=2)
    
    # Convert back to uint 8:
    kChannel = (255 * kChannel).astype(np.uint8)
    

    This is the K (black) Channel:

    Now, threshold the image using a fixed value. In this case, I set the threshold to 190:

    # Threshold image:
    binaryThresh = 190
    _, binaryImage = cv2.threshold(kChannel, binaryThresh, 255, cv2.THRESH_BINARY)
    

    This is the binary image:

    It is a little noisy, but we can remove the smaller blobs if we implement an area filter. The function is defined at the end of this post. Let's apply the filter with a minimum value of 100. All blobs smaller than this will be erased:

    # Filter small blobs:
    minArea = 100
    binaryImage = areaFilter(minArea, binaryImage)
    

    This is the filtered image:

    Cool. Let's improve the morphology of the blobs with a closing filter:

    # Use a little bit of morphology to clean the mask:
    # Set kernel (structuring element) size:
    kernelSize = 3
    # Set morph operation iterations:
    opIterations = 2
    # Get the structuring element:
    morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
    # Perform closing:
    binaryImage = cv2.morphologyEx(binaryImage, cv2.MORPH_CLOSE, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
    
    cv2.imshow("binaryImage [closed]", binaryImage)
    cv2.waitKey(0)
    

    This is the final result:

    And this is the areaFilter function. It receives a minimum area and a binary image, it returns the image free of small blobs :

    def areaFilter(minArea, inputImage):
        # Perform an area filter on the binary blobs:
        componentsNumber, labeledImage, componentStats, componentCentroids = \
            cv2.connectedComponentsWithStats(inputImage, connectivity=4)
    
        # Get the indices/labels of the remaining components based on the area stat
        # (skip the background component at index 0)
        remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
    
        # Filter the labeled pixels based on the remaining labels,
        # assign pixel intensity to 255 (uint8) for the remaining pixels
        filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
    
        return filteredImage