I have the following 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.
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