Reputation: 3620
I am familiar with OpenCV's inRange function to create a mask. Let's say I want to get a mask of pixels in a color range "around" a certain color, I can do this:
color = np.array([240, 60, 70])
max_dist = 50
img = cv2.inRange(img, [color] - max_dist, [color] + max_dist)
But this masks all BGR colors in a "cube" around the center color. I am looking for an alternative using a "sphere" around the center color in BGR space, i.e. euclidean distance. Any ideas?
I can, of course, loop through the image, calculate distances using scipy.spatial.distance.cdist and then loop through all pixels one by one and either include or exclude them from the mask. However, this is very slow in python...
Thanks!
Upvotes: 1
Views: 680
Reputation: 3620
Finally got a working answer. To be exact, I do not want to limit myself to one color, but rather allow several colors. I'm using the built-in cdist function + the post-processing suggested above by dobkind to convert distances to a mask. This works about 7% faster than the previous approach.
max_dist = 10
colors = np.array([[250,40,60],[245,245,245]])
dist = scipy.spatial.distance.cdist(colors, img.reshape(-1, 3), 'euclidean')
mask = np.any(dist <= max_dist, axis=0).reshape(img.shape[0], img.shape[1])
img = np.repeat(mask[..., None], 3, axis=2) * img
Upvotes: 0
Reputation: 426
Create a mask that indicates the pixels whose Euclidean distance is closer than max_dist
:
R = img[:, :, 0].astype(np.float32)
G = img[:, :, 1].astype(np.float32)
B = img[:, :, 2].astype(np.float32)
sq_dist = (R - color[0]) ** 2 + (G - color[1]) ** 2 + (B - color[2]) ** 2
mask = sq_dist < (max_dist ** 2)
masked_img = np.repeat(mask[..., None], 3, axis=2) * img
Upvotes: 2