Assume we want to automatically find the red wells.
Assume we know that the wells are circular, and that the color is red (reddish).
We also know that the wells are not tiny, but we don't want to overfit the solution too much...
We may use the following stages:
- Use cv2.bilateralFilter for reducing noise (while keeping edges fairly sharp).
- Find red pixels as described in the following post.
Convert from BGR to HSV, and find the pixels in range that is considered red.
According to the post there are "lower_red" and "upper_red" ranges (we are keeping the ranges without tuning).
- Find contours.
Iterate the contours.
Skip small contours (assumed to be noise).
- Use
cv2.minEnclosingCircle
as described here.
Draw a filled circle for each contour, to form a mask, and use cv2.bitwise_and
as used in your question.
Code sample:
import numpy as np
import argparse
import cv2
# Construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", help="path to the image")
args = vars(ap.parse_args())
# Load the image
image = cv2.imread(args["image"])
img = cv2.bilateralFilter(image, 11, 75, 75)
# https://stackoverflow.com/questions/30331944/finding-red-color-in-image-using-python-opencv
# Convert from BGR to HSV
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Lower mask (0-10)
lower_red = np.array([0, 50, 50])
upper_red = np.array([10, 255, 255])
mask0 = cv2.inRange(img_hsv, lower_red, upper_red)
# Upper mask (170-180)
lower_red = np.array([170, 50, 50])
upper_red = np.array([180, 255, 255])
mask1 = cv2.inRange(img_hsv, lower_red, upper_red)
# Join the masks
raw_mask = mask0 | mask1
ctns = cv2.findContours(raw_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] # Find contours
mask = np.zeros_like(raw_mask) # Fill mask with zeros
idx = 0
# Iterate contours
for c in ctns:
area = cv2.contourArea(c) # Find the area of each contours
if (area > 50): # Ignore small contours (assume noise).
cv2.drawContours(mask, [c], 0, 255, -1)
# https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html
(x, y), radius = cv2.minEnclosingCircle(c)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(mask, center, radius, 255, -1)
tmp_mask = np.zeros_like(mask)
cv2.circle(tmp_mask, center, radius, 255, -1)
output = cv2.bitwise_and(image, image, mask=tmp_mask)
cv2.imshow(f'output{idx}', output) # Show output images for testing
cv2.imwrite(f'output{idx}.png', output) # Save output images for testing
idx += 1
cv2.imshow('image', image)
cv2.imshow('img', img)
cv2.imshow('raw_mask', raw_mask)
cv2.imshow('mask', mask)
cv2.waitKey()
cv2.destroyAllWindows()
Results:
Image after bilateral filter:

raw_mask
:

mask
:

output0.png
:

output1.png
:

output2.png
:

output3.png
:

output4.png
:

In case the circles are too large, we can reduce the radius, and get only the center of each well.