Sagi Mann
Sagi Mann

Reputation: 3620

OpenCV: Is there an alternative to cv2.inRange using radius?

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

Answers (2)

Sagi Mann
Sagi Mann

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

dobkind
dobkind

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

Related Questions