Reputation: 335
Take a look at this image. I want to turn this card blue.
I use Python and OpenCV to perform image processing.
Here's how I do it now:
import cv2
import numpy as np
# Load the image
image = cv2.imread("input.jpg")
# Convert the image to the HSV color space
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Threshold the HSV image to get only the red colors
# Bitwise OR unites Hue value 170-179 and 0-10.
mask = cv2.bitwise_or(
cv2.inRange(hsv_image, np.array([0, 120, 100]), np.array([10, 255, 255])),
cv2.inRange(hsv_image, np.array([170, 120, 100]), np.array([180, 255, 255]))
)
# Perform median blurring
mask = cv2.medianBlur(mask, 5)
# Define a kernel for the morphological operations
kernel = np.ones((5, 5), np.uint8)
# Perform an opening operation to remove small objects
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)
# Perform a closing operation to fill small holes
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)
# Perform a gradient operation to extract object boundaries
gradient_mask = cv2.morphologyEx(mask, cv2.MORPH_GRADIENT, kernel)
# Modify hue value of every masked pixel
hsv_image[:, :, 0][mask != 0] = (hsv_image[:, :, 0][mask != 0].astype(int, copy=False) + 120) % 180
# Convert the HSV image back to BGR color space
result = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)
# Display the result
cv2.namedWindow("output", cv2.WINDOW_NORMAL)
cv2.imshow("output", result)
# Save images of the mask, result and gradient
cv2.imwrite('mask.jpg', mask)
cv2.imwrite('result.jpg', result)
cv2.imwrite('gradient.jpg', gradient_mask)
# Wait for the window to close
while cv2.getWindowProperty('output', 0) >= 0:
cv2.waitKey(50)
cv2.destroyAllWindows()
It works well. The result, The filtering mask
But if you take a closer look you'll see the problem: link, link
Some red pixels is still here, and here's why. They do not fall in the filtered range of the red color: 170 <= Hue <= 10, Saturation >= 120, Value >= 100. Their HSV color is near to (178, 32, 60). So, the saturation and value fall out of the filter range.
Why I can't lower the range of saturation and value? That's because in this case there would be too much noise on another background that has more colors. The noise in this case is hard to avoid even using multiple iterations of opening morphological operation.
I don't have much experience in image processing and using OpenCV, so my ideas may be far from the best solution. It's okay if you propose another approach.
Possible solution. Would it be possible to perform the dilate morphological operation on the filtering mask (expand the filtering mask) but only to those pixels that fall in another, broader range of red (with saturation and value range equal to 10, hue range stays the same). So that all the red pixels that fall in the broader range of the red color AND that are adjacent to the pixels of the existing mask (so no pixels from the background is added creating noise). If that is a good idea, how can I implement it, especially the part of dilating only to pixels that fall in the broader range of the red color? Maybe it's already implemented in OpenCV and I just don't know about that?
Also I would be glad to hear any suggestions or recommendations. I am a student, I want to learn more. Thanks in advance!
Upvotes: 0
Views: 767
Reputation: 335
I found myself how to do it, but it's still not the best mask. I create a broader mask as follows:
broader_mask = cv2.bitwise_or(
cv2.inRange(hsv_image, np.array([0, 30, 30]), np.array([20, 255, 255])),
cv2.inRange(hsv_image, np.array([160, 30, 30]), np.array([180, 255, 255]))
)
And apply bitwise AND on the dilated primary mask and the broader mask to get the resulting mask:
mask = cv2.bitwise_and(cv2.dilate(mask, kernel, iterations=2), broader_mask)
It works much better, but now the mask might be bigger than I want in some cases. For example:
It all depends on the kernel and the number of iterations of the dilation operation. I don't think it's the best solution because the kernel and the number of iterations might be different for different image sizes, and I'm still looking for a better solution.
Upvotes: 1