4

I need help indexing a 3 dimensional array (an RGB/BGR image) using a 2 dimensional array of indices. All values are either 0, 1, or 2 for different color channels. The result should be a 2D array of color values. If anyone can tell me the syntax for this in python that would be great!

For a context of what I am trying to do (also read TLDR below):

I am essentially trying to convert the following code from normal for loop syntax which is very very slow to a more efficient python/numpy syntax:

colorIndices = np.zeros((height,width));       # an array which has the index of the outstanding color
colorIndices -= 1;           # all -1's

for x in range(0,width):
    for y in range(0,height):

        pix = img[y,x];        # get the pixel, a 1D array of length 3
        colorID = np.argmax(pix);           #get which index has max value (candidate for outstanding color)

        if(pix[colorID]>np.average(pix)+np.std(pix)):      # if that value is more than one std dev away from the overall pixel's value, the pixel has an outstanding color
            colorIndices[y,x] = colorID;

I then would like to access the outstanding color channels in each pixel using something like:

img[:,:,:]=0;
img[colorIndices] = 255;

TLDR: I want to set a pixel to pure blue, green, or red if it is a shade of that color. The way I define if a pixel is shade red is if the R value of the pixel is more than one std above the average of the overall distribution of {R, G, B}.

The broken code I have so far:

  colorIDs = np.argmax(img, axis=2);

  averages = np.average(img, axis=2);
  stds = np.std(img, axis=2);
  cutoffs = averages + stds;

  print(img[colorIDs]);

Here is an example of the image I am using

4
  • Can you paste a sample of img Commented Jan 7, 2019 at 6:13
  • Why/ how is your code broken and what is the shape of img? Commented Jan 7, 2019 at 6:13
  • @MadPhysicist My code is just not complete and I have no idea how to finish it to satisfy my goal. I should've said not complete. Commented Jan 7, 2019 at 6:15
  • @najeem The image is suppose to be generic to any size, but I will upload the one I am using for your convenience. Commented Jan 7, 2019 at 6:17

2 Answers 2

2

I think you want to apply the 2d indexing mask from argmax to the 2nd axis:

In [38]: img=np.random.randint(0,10,(16,16,3))
In [39]: ids = np.argmax(img, axis=2)
In [40]: ids
Out[40]: 
array([[0, 1, 2, 1, 2, 0, 0, 0, 2, 2, 1, 0, 1, 2, 1, 0],
       [0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1],
       [1, 1, 0, 0, 0, 0, 1, 2, 0, 2, 2, 1, 2, 1, 1, 0],
       [2, 0, 1, 2, 0, 0, 1, 1, 0, 2, 2, 1, 1, 1, 1, 2],
       [2, 2, 1, 1, 0, 1, 0, 2, 1, 0, 0, 2, 2, 0, 1, 2],
       [1, 0, 2, 1, 0, 2, 0, 1, 0, 1, 1, 2, 1, 1, 0, 2],
       [1, 0, 0, 0, 1, 2, 1, 0, 1, 2, 1, 1, 1, 2, 0, 0],
       [1, 2, 2, 2, 0, 0, 1, 1, 0, 1, 0, 2, 2, 1, 1, 0],
       [0, 2, 2, 1, 0, 0, 1, 0, 2, 1, 1, 0, 2, 1, 1, 0],
       [1, 0, 2, 1, 2, 0, 1, 1, 0, 2, 2, 2, 1, 1, 0, 1],
       [1, 1, 1, 1, 1, 2, 1, 1, 0, 2, 1, 0, 0, 1, 0, 0],
       [1, 2, 1, 0, 2, 2, 2, 1, 0, 1, 2, 1, 2, 0, 2, 1],
       [2, 0, 2, 1, 2, 0, 1, 1, 2, 2, 2, 2, 1, 0, 2, 1],
       [0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 0, 2, 0, 2, 0, 1],
       [0, 1, 2, 1, 1, 0, 1, 2, 0, 1, 0, 0, 2, 1, 0, 2],
       [0, 0, 2, 2, 2, 2, 2, 1, 0, 0, 0, 2, 0, 0, 1, 1]])
In [41]: I,J = np.ix_(np.arange(16), np.arange(16))
In [42]: img[I,J,ids]
Out[42]: 
array([[5, 9, 9, 8, 8, 8, 5, 7, 1, 9, 9, 5, 5, 9, 6, 8],
       [6, 7, 5, 8, 5, 6, 9, 6, 7, 7, 7, 8, 3, 7, 9, 5],
       [7, 6, 8, 7, 6, 9, 6, 8, 9, 5, 8, 8, 9, 7, 9, 6],
       [8, 9, 3, 4, 7, 5, 8, 4, 4, 9, 1, 4, 9, 9, 9, 7],
       [9, 8, 9, 7, 9, 8, 7, 5, 8, 9, 9, 6, 9, 5, 8, 8],
       [7, 9, 8, 8, 9, 3, 6, 9, 8, 6, 8, 7, 7, 7, 7, 7],
       [8, 8, 5, 8, 9, 8, 8, 2, 8, 7, 8, 9, 5, 5, 6, 7],
       [9, 6, 6, 9, 5, 3, 6, 4, 7, 6, 8, 8, 6, 3, 9, 9],
       [7, 8, 9, 7, 5, 7, 5, 9, 6, 4, 7, 7, 8, 5, 7, 8],
       [9, 7, 6, 4, 8, 9, 3, 8, 9, 2, 6, 9, 6, 7, 9, 7],
       [9, 8, 6, 6, 5, 9, 3, 9, 2, 4, 9, 5, 9, 9, 6, 9],
       [8, 7, 8, 3, 8, 8, 9, 7, 9, 5, 9, 8, 6, 9, 7, 8],
       [8, 2, 7, 7, 4, 5, 9, 8, 8, 8, 6, 5, 3, 9, 9, 6],
       [6, 8, 8, 5, 8, 8, 8, 9, 3, 7, 7, 8, 5, 4, 2, 9],
       [3, 7, 9, 9, 8, 5, 9, 8, 9, 7, 3, 3, 9, 5, 5, 9],
       [8, 4, 3, 6, 4, 9, 9, 9, 9, 9, 9, 7, 9, 7, 5, 8]])

Recent numpy versions have a function that does this for us

np.take_along_axis(img, ids[:,:,None], 2)[:,:,0]

and to set values np.put_along_axis.

Sign up to request clarification or add additional context in comments.

3 Comments

Does this create the mask of 0's and 255's though?
@MadPhysicist the take along axis method was what I was looking for(indexing with a 2D array), but I couldn’t apply it to what I was doing afterwords. Your post solved that.
@hpaulj thanks for tipping me off about take_along_axis. I'd been utterly and blissfully unaware of that function. Unfortunately, I don't think put_along_axis works as advantageously as take in OP's case.
1

You can convert a numerical index along a given axis into values, you can use np.take_along_axis or fancy indexing. When using a fancy index, you need to index along all axes with arrays whose shapes broadcast to the size of the final result. np.ogrid helps with this. For an MxNx3 array img (M, N, _ = img.shape), if you had ix = np.argmax(img, axis=2), the index would look like this:

r, c = np.ogrid[:M, :N]
maxes = img[r, c, ix]

Using take_along_axis saves you a step and some temp arrays:

maxes = np.take_along_axis(img, ix, 2)

Now create your mask:

significant = np.abs(maxes - img.mean(axis=2) > img.std(axis=2))

At this point you have a 2D boolean mask and an integer index in the third dimension. The simplest thing is probably to turn everything into a linear index:

r, c = np.where(significant)

Now you can construct the output:

color_index = np.zeros_like(img)
color_index[r, c, ix[significant]] = 255

While tempting, np.put_along_axis can not be used here in a straightforward manner. The issue is that masking ix with significant would invalidated its shape similarity. You could, however, create an intermediate 2D array containing 255 at the locations marked by significant, and use that with put_along_axis:

values = np.zeros(significant.shape, dtype=img.dtype)
values[significant] = 255
color_index = np.zeros_like(img)
np.put_along_axis(color_index, ix, values, 2)

All combined:

ix = np.argmax(img, axis=2)
significant = np.abs(np.take_along_axis(img, ix, 2) - img.mean(axis=2)) > img.std(axis=2)
color_index = np.zeros_like(img)
color_index[(*np.where(significant), ix[significant])] = 255

8 Comments

what are the M and N variables?
And also, those asterisks in that last block of code do not run for me and it says syntax error.
M and N are height and width. What version of Python are you using?
You're right. I've probably fixed both issues not, but can't really test since I'm on a mobile platform. I'll update you once I've had a chance to play with this on a desktop in a couple of hours.
@akarshkumar0101. I've fixed all the code snippets by actually testing them.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.