Home > Back-end >  How to find these semi-transparent blue pixels via OpenCV?
How to find these semi-transparent blue pixels via OpenCV?

Time:08-04

I have a range of images that look similar to this:

Sample image

The images sometimes have these blue-ish areas, but as you can see, their color values are in a broad range. I need to find that blue blob, so I first convert the image to HSV, then filter via colors:

hsv_img = cv2.cvtColor(orig_img, cv2.COLOR_BGR2HSV)
lower_blue = numpy.array([150, 10, 0])
upper_blue = numpy.array([200, 50, 255])
mask = cv2.inRange(hsv_img, lower_blue, upper_blue)

However, I haven't found good values for lower_blue and upper_blue, and I am afraid there is no single such combination for my use case. In HSV the image looks like this:

HSV image

Another approach might be to have mulitple such boundaries, lower and upper, and look for all of them, then combine all identified pixels via cv2.bitwise_or(). But that again seems quite cumbersome and error prone.

Is there a another / better approach for this case, beside using neural networks, i.e. solely based on OpenCV?

CodePudding user response:

You can convert the BGR image to the CMKY color space and see if the blob of interest shows mostly in one uniform color in one of the channels. This is pretty much by trial and error. In the image you posted, seems that working directly on the Yellow (Y) channels yields a good result. Let's check out the code:

# Image path
path = "D://opencvImages//"
fileName = "KEQyZ.jpg"

# Reading an image in default mode:
inputImage = cv2.imread(path   fileName)

# BGR to CMKY:
(C, M, Y, K) = bgr2cmky(inputImage)

# Show K:
cv2.imshow("Y", Y)
cv2.waitKey(0)

This is the Y channel:

The bag is mostly black. If we threshold the image we should get a good segmentation. Some bias should be useful to adjust a little bit better the threshold value. Let's compute Otsu's threshold to get an initial threshold estimation, add some bias and threshold again with the new value:

# (Otsu) Threshold:
thresh, binaryImage = cv2.threshold(Y, 0, 255, cv2.THRESH_BINARY   cv2.THRESH_OTSU)

# (Fixed) Threshold   bias:
thresh = 0.4 * thresh
_, binaryImage = cv2.threshold(Y, thresh, 255, cv2.THRESH_BINARY_INV)

This is the binary image so far:

Looks good. Let's apply some morphology to isolate the biggest blob:

# Apply Morphology:
kernel = np.ones((3, 3), np.uint8)
binaryImage = cv2.morphologyEx(binaryImage, cv2.MORPH_ERODE, kernel, iterations=3)
binaryImage = cv2.morphologyEx(binaryImage, cv2.MORPH_DILATE, kernel, iterations=3)

This is the result:

Not bad, let's compute the blob's bounding rectangle and draw it on the original image:

# (Optinal) Get Bounding Rect:
boundRect = cv2.boundingRect(binaryImage)

# (Optional) Draw the rectangle on the input image:
# Get the dimensions of the bounding rect:
rectX = boundRect[0]
rectY = boundRect[1]
rectWidth = boundRect[2]
rectHeight = boundRect[3]

# (Optional) Set color and draw:
color = (255, 0, 0)
cv2.rectangle(inputImage, (int(rectX), int(rectY)),
              (int(rectX   rectWidth), int(rectY   rectHeight)), color, 2)

# Show the image:
cv2.imshow("Bag", inputImage)
cv2.waitKey(0)

Which yields the following result:

OpenCV does not have a bgr2cmyk conversion, the function is pretty much the conversion formula directly implemented like this:

# Converts BGR to CMYK (as a tuple of 4 arrays)
def bgr2cmky(bgrImage):
    bgrdash = bgrImage.astype(float) / 255.
    # Calculate K as (1 - whatever is biggest out of Rdash, Gdash, Bdash)
    K = 1 - np.max(bgrdash, axis=2)

    # Calculate C
    C = (1 - bgrdash[..., 2] - K) / (1 - K)
    C  = 255 * C
    C  = C .astype(np.uint8)

    # Calculate M
    M = (1 - bgrdash[..., 1] - K) / (1 - K)
    M  = 255 * M
    M  = M.astype(np.uint8)

    # Calculate Y
    Y = (1 - bgrdash[..., 0] - K) / (1 - K)
    Y  = 255 * Y
    Y  = Y.astype(np.uint8)

    return (C, M, Y, K)
  • Related