Reputation: 367
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]);
Upvotes: 3
Views: 1938
Reputation: 114310
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
Upvotes: 1
Reputation: 231385
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
.
Upvotes: 2