Filter numpy image array by multiple conditions as fast as possible

Viewed 279

I need to find specific pixels (BGR format) coordinates in a game while it's live ,

(i saved a screenshot just for testing , the program it self takes screenshots continuously).

Here's a working for loop example of what i need

s = np.array(Image.open("image.png"))
cr = []
for y in range(338):
    for x in range(326):
        C = s[y, x]
        if C[1] < 25 and 20 <= C[0] < 25 and 130 < C[2] < 215:
            cr.append((y, x))

but the for loop takes 1.03 sec , which is very slow for my task , so i tried

cr = s[(20 < s[:,][:,0]) & (s[:,][:,0] < 25) & (s[:,][:,1] < 25) & (130 < s[:,][:,2]) & (s[:,][:,2] < 215)]

but it returns me the error IndexError: boolean index did not match indexed array along dimension 1; dimension is 415 but corresponding boolean dimension is 3

so how do i need to make it , with the fastest way?

EDIT:

example image:

3 Answers

I would write your condition like this:

c = (s[...,1] < 25) & (20 < s[...,0]) & (s[...,0] < 25) & (130 < s[...,2]) & (s[...,2] < 215)
cr = np.argwhere(c)

The error you got is because the shape of the s is different than the shape of whatever you put within brackets. Other comments:

  • the color axis is the last one, not the second
  • s[:,] is the same as s

In the example image you posted above, I get:

In [3]: s.shape
Out[3]: (326, 338, 3)

In [4]: (20 < s[:,:,0]).shape
Out[4]: (326, 338)

that is because by setting the index along one dimension to a fixed value, you get rid of that dimension. That would have been ok, but you also got rid of the wrong dimension.

You can reduce the condition significantly using broadcasting. Let's say you have an (M, N, 3) array s, with M = 338 and N = 326. All the pixels have an upper threshold of [25, 25, 215]. Channel0 has a lower threshold of 20 (inclusive), and Channel 2 has one at 130 (exclusive). You an express this as

cr = np.argwhere((s < [25, 25, 215]).all(-1) & (s[..., 0] >= 20) & (s[..., 2] > 130))

You have to put parentheses around the conditional expressions because they have lower precedence than & otherwise.

The expression (s < [25, 25, 215]) produces an (M, N, 3) boolean mask because of broadcasting. Dimensions line up on the right, so [25, 25, 215] is treated as (1, 1, 3), and effectively expanded to (M, N, 3). The final all along the last axis reduces the mask to (M, N) by checking that all the channels for a given pixel pass their respective criteria.

You can take this approach a step further because you're working with integers. In that case x >= 20 is equivalent to x > 19. So you only need two conditions:

cr = np.argwhere((s < [25, 25, 215]).all(-1) & (s[0::2] > [19, 130]).all(-1)

You have to use numpy.argwhere and numpy.logical_and

es:

matching_indexes = np.argwhere(np.logical_end(np.logical_and(s<3, s>50), s !=4))
Related