I have a range of images that look similar to this:
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:
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)