Home > Mobile >  find and mask RGB-color in cv2 image
find and mask RGB-color in cv2 image

Time:07-03

I have the following image:

enter image description here

I want to filter out a single color (RGB-style)

r, g, b = 119, 226, 108
img = cv2.imread('img.png', cv2.IMREAD_UNCHANGED)
idx = np.where(np.all(img == [b, g, r], axis=-1))
img[idx[0], idx[1], :] = [255, 255, 255]

cv2.imshow('img', img)
cv2.waitKey(0)

The output looks as follows:

enter image description here

However, I want to do the opposite and show only the color. I write

img = cv2.imread('img.png', cv2.IMREAD_UNCHANGED)
idx = np.where(np.all(img != [b, g, r], axis=-1))
img[idx[0], idx[1], :] = [255, 255, 255]

cv2.imshow('img', img)
cv2.waitKey(0)

The output shows now two colors?

enter image description here

The green contours are also not the same. What am I doing wrong and what is the correct output?

CodePudding user response:

In order to index all the cells where the color doesn't match, you only need to check that one of the colors doesn't match, not all of them. I believe this is the code you are looking for:

img = cv2.imread('img.png', cv2.IMREAD_UNCHANGED)
idx = np.where(np.any(img != [b, g, r], axis=-1)) # any instead of all
img[idx[0], idx[1], :] = [255, 255, 255]

cv2.imshow('img', img)
cv2.waitKey(0)

CodePudding user response:

Logic bug.

you say:

  • idx = np.where(np.all(img == [b, g, r], axis=-1))
  • idx = np.where(np.all(img != [b, g, r], axis=-1))

... which is incorrectly inverted. Correct boolean logic (ok first order logic but you get the idea):

not (all (equal)) <=> any (not (equal))

So you need to switch the "all" to any.

np.where has dual operation. For a single argument, it acts like np.nonzero, giving a list of indices. For multiple arguments, it selects from the second or third array, depending on the truth value in the first array. If you want to work with indices, you should prefer to use np.nonzero itself.

I would however recommend that you not use indices but instead calculate that mask once, and then just invert:

mask = (img == [b, g, r]).all(axis=-1) # np.all() exists but I find this nice too
inverted = ~mask

# then

img[mask] = [255, 255, 255]
# or
img[~mask] = [255, 255, 255]

In fact, why don't you just use cv.inRange()? It's capable of selecting ranges of values. Real pictures rarely contain exact color values. Anything that's been compressed as JPEG will certainly contain a little noise from the compression.

Your picture looks like you might actually be interested in a kind of peak/ridge to follow on a map... in case that's ever not uniformly green (say, varying in height), you will need some logic to work with gradients.

  • Related