Remove numpy array of False/True from image numpy array - python

I have an array of an image
img = [[[63 48 27]
[ 63 48 27]
[ 63 48 27]
...
[117 88 70]
[113 84 66]
[111 82 64]]
[[ 64 49 28]
[ 64 49 28]
[ 64 49 28]
...
[117 88 70]
[114 85 67]
[111 82 64]]
[[ 65 50 29]
[ 66 51 30]
[ 66 51 30]
...
[118 89 71]
[114 85 67]
[111 82 64]]...
And another array of the pixels that I want to keep from that image array:
mask = [[[False False False ... False False False]
[False False False ... False False False]
[False False False ... False False False]
...
[False False False ... False False False]
[False False False ... False False False]
[False False False ... False False False]]]
I thought I could just do img[mask] but I get boolean index did not match indexed array along dimension 0; dimension is 549 but corresponding boolean dimension is 1. How can I either expand the mask array back to the right dimension, it comes from converting a detectron2 mask to numpy array mask = outputs['instances'].pred_masks.numpy() (originally it's a tensor). Or, and this could be easier I think, if the value in the numpy mask array is False then convert the element in the image array to white/255.
The function I'm using is:
from matplotlib.image import imread
import scipy.misc
def cropper(org_image_path, mask_array, out_file_name):
img = imread(org_image_path)
output = img[mask_array]
scipy.misc.toimage(output).save(out_file_name)

Given an image of shape (M, N, 3) and a mask of shape (1, M, N), you can set the False elements of the image to 255 in all channels by using simple boolean indexing. Whereas broadcasting lines up indices on the right, indexing fills them in on the left. That means that to get the mask to correspond to your image dimensions you need to remove the first axis. There are a number of ways of doing this:
mask = mask[0]
mask = mask[0, ...]
mask = mask.squeeze()
mask = np.squeeze(mask)
mask = mask.reshape(mask.shape[1:])
mask = np.reshape(mask, mask.shape[1:])
...
With the first two dimensions matching up, you can negate the mask and preform a direct assignment:
img[~mask] = 255
This can be combined into a simple one-liner:
img[~mask[0]] = 255

For those interested, Nicholas' comment was a useful bump (thank you) - just changed the mask array to be on the same axis & the same shape. Then was just a simple edit to change the values:
from matplotlib.image import imread
import scipy.misc
from PIL import Image
def cropper(org_image_path, mask_array, out_file_name):
img = imread(org_image_path)
mask_array = np.moveaxis(mask_array, 0, -1)
mask_array = np.repeat(mask_array, 3, axis=2)
output = np.where(mask_array==False, 255, img)
im = Image.fromarray(output)
im.save(out_file_name)

Related

Per-Pixel operation with conditonals in Python without slowy loops

I'm trying to apply a treshold to an image, but not a regular simple treshold.
I need to set to black pixels if they fit the conditonal, and if not, set them to white.
I could just loop over pixels, but on a 1080p image, it's far too long.
I'm using HSV for the comparisons I need to make.
Here is the conditional (this example is how I would use it if it was in a loop):
if abs(input_pixel_color.hue - reference.hue) < 2 and input_pixel_color.saturation >= 0.25 and input_pixel_color.brightness >= 0.42:
set_to_black
else:
set_to_white
input_pixel is the HSV value of the pixel in the loop.
reference is a variable to be compared to.
I thought about using numpy, but I really don't know how to write this :/
Thanks in advance
Updated
Now that your actual intended processing has become clearer, you would probably be better served by OpenCV inRange() function. Like this:
#!/usr/local/bin/python3
import cv2 as cv
import numpy as np
# Load the image and convert to HLS
image = cv.imread("image.jpg")
hls = cv.cvtColor(image,cv.COLOR_BGR2HLS)
# Define lower and uppper limits for each component
lo = np.array([50,0,0])
hi = np.array([70,255,255])
# Mask image to only select filtered pixels
mask = cv.inRange(hls,lo,hi)
# Change image to white where we found our colour
image[mask>0]=(255,255,255)
cv.imwrite("result.png",image)
So, if we use this image:
We are selecting Hues in the range 50-70, and making them white:
If you go here to a colour converter, you can see that "Green" is Hue=120, but OpenCV divides Hues by 2 so that 360 degrees becomes 180 and still fits in a uint8. So, our 60 in the code means 120 in online colour converters.
The ranges OpenCV uses for uint8 images are:
Hue 0..180
Lightness 0..255
Saturation 0..255
As I said before, you should get in the habit of looking at your data types, shapes and ranges in your debugger. To see the shape, dtype, and maximum Hue, Lightness and Saturation, use:
print(hls.dtype, hls.shape)
print(hls[...,0].max())
print(hls[...,1].max())
print(hls[...,2].max())
Original Answer
There are several ways to do that. The most performant is probably with the OpenCV function cv2.inRange() and there are plenty of answers on StackOverflow about that.
Here is a Numpy way. If you read the comments and look at the printed values, you can see how to combine logical AND with logical OR and so on, as well as how to address specific channels.
#!/usr/bin/env python3
from random import randint, seed
import numpy as np
# Generate a repeatable random HSV image
np.random.seed(42)
h, w = 4, 5
HSV = np.random.randint(1,100,(h,w,3),dtype=np.uint8)
print('Initial HSV\n',HSV)
# Create mask of all pixels with acceptable Hue, i.e. H > 50
HueOK = HSV[...,0] > 50
print('HueOK\n',HueOK)
# Create mask of all pixels with acceptable Saturation, i.e. S > 20 AND S < 80
SatOK = np.logical_and(HSV[...,1]>20, HSV[...,1]<80)
print('SatOK\n',SatOK)
# Create mask of all pixels with acceptable value, i.e. V < 20 OR V > 60
ValOK = np.logical_or(HSV[...,2]<20, HSV[...,2]>60)
print('ValOK\n',ValOK)
# Combine masks
combinedMask = HueOK & SatOK & ValOK
print('Combined\n',combinedMask)
# Now, if you just want to set the masked pixels to 255
HSV[combinedMask] = 255
print('Result1\n',HSV)
# Or, if you want to set the masked pixels to one value and the others to another value
HSV = np.where(combinedMask,255,0)
print('Result2\n',HSV)
Sample Output
Initial HSV
[[[93 98 96]
[52 62 76]
[93 4 99]
[15 22 47]
[60 72 85]]
[[26 72 61]
[47 66 26]
[21 45 76]
[25 87 40]
[25 35 83]]
[[66 40 87]
[24 26 75]
[18 95 15]
[75 86 18]
[88 57 62]]
[[94 86 45]
[99 26 19]
[37 24 63]
[69 54 3]
[33 33 39]]]
HueOK
[[ True True True False True]
[False False False False False]
[ True False False True True]
[ True True False True False]]
SatOK
[[False True False True True]
[ True True True False True]
[ True True False False True]
[False True True True True]]
ValOK
[[ True True True False True]
[ True False True False True]
[ True True True True True]
[False True True True False]]
Combined
[[False True False False True]
[False False False False False]
[ True False False False True]
[False True False True False]]
Result1
[[[ 93 98 96]
[255 255 255]
[ 93 4 99]
[ 15 22 47]
[255 255 255]]
[[ 26 72 61]
[ 47 66 26]
[ 21 45 76]
[ 25 87 40]
[ 25 35 83]]
[[255 255 255]
[ 24 26 75]
[ 18 95 15]
[ 75 86 18]
[255 255 255]]
[[ 94 86 45]
[255 255 255]
[ 37 24 63]
[255 255 255]
[ 33 33 39]]]
Result2
[[ 0 255 0 0 255]
[ 0 0 0 0 0]
[255 0 0 0 255]
[ 0 255 0 255 0]]
Notes:
1) You can also access pixels not selected by the mask, using negation:
# All unmasked pixels become 3
HSV[~combinedMask] = 3
2) The ellipsis (...) is just a shortcut meaning "all other dimensions I didn't bother listing", so HSV[...,1] is the same as HSV[:,:,1]
3) If you don't like writing HSV[...,0] for Hue, and HSV[...,1] for Saturation, you can split the channels
H, S, V = cv2.split(HSV)
Then you can just use H instead of HSV[...,0]. When you are finished, if you want to re-assemble the channels back into a 3-channel image, you can do:
HSV = cv2.merge((H,S,V))
or
HSV = np.dstack((H,S,V))

how to fix "Sliced assignment is only supported for variables for tensors"

I am trying to define my own custom layer in keras. In the call function, where the logic of the class lies, I am dealing with the tensor object.
After finding the maximum value from a shredded slice of a tensor object, I want to assign it to a different tensor, but I am getting ERROR
"Sliced assignment is only supported for variables"
I have tried Sess.eval() in the call function of a class which does not solve the problem
mid_arr = x[i:spliti,j:splitj] #shredded slice
num = tf.reduce_max(mid_arr) #max vlaue from shred slice
res_arr = res_arr.assign( tf.where (res_arr[m][n],num, res_arr) ) #assign it
Specifying the solution here (Answer Section) even though it is present in Comments Section (thanks to jdehesa), for the benefit of the community.
Complete 2.x compatible code (work around) to perform sliced assignment of a Tensor is shown below:
import tensorflow as tf
def replace_slice(input_, replacement, begin, size=None):
inp_shape = tf.shape(input_)
if size is None:
size = tf.shape(replacement)
else:
replacement = tf.broadcast_to(replacement, size)
padding = tf.stack([begin, inp_shape - (begin + size)], axis=1)
replacement_pad = tf.pad(replacement, padding)
mask = tf.pad(tf.ones_like(replacement, dtype=tf.bool), padding)
return tf.where(mask, replacement_pad, input_)
def replace_slice_in(tensor):
return _SliceReplacer(tensor)
class _SliceReplacer:
def __init__(self, tensor):
self._tensor = tensor
def __getitem__(self, slices):
return _SliceReplacer._Inner(self._tensor, slices)
def with_value(self, replacement): # Just for convenience in case you skip the indexing
return _SliceReplacer._Inner(self._tensor, (...,)).with_value(replacement)
class _Inner:
def __init__(self, tensor, slices):
self._tensor = tensor
self._slices = slices
def with_value(self, replacement):
begin, size = _make_slices_begin_size(self._tensor, self._slices)
return replace_slice(self._tensor, replacement, begin, size)
# This computes begin and size values for a set of slices
def _make_slices_begin_size(input_, slices):
if not isinstance(slices, (tuple, list)):
slices = (slices,)
inp_rank = tf.rank(input_)
inp_shape = tf.shape(input_)
# Did we see a ellipsis already?
before_ellipsis = True
# Sliced dimensions
dim_idx = []
# Slice start points
begins = []
# Slice sizes
sizes = []
for i, s in enumerate(slices):
if s is Ellipsis:
if not before_ellipsis:
raise ValueError('Cannot use more than one ellipsis in slice spec.')
before_ellipsis = False
continue
if isinstance(s, slice):
start = s.start
stop = s.stop
if s.step is not None:
raise ValueError('Step value not supported.')
else: # Assumed to be a single integer value
start = s
stop = s + 1
# Dimension this slice refers to
i_dim = i if before_ellipsis else inp_rank - (len(slices) - i)
dim_size = inp_shape[i_dim]
# Default slice values
start = start if start is not None else 0
stop = stop if stop is not None else dim_size
# Fix negative indices
start = tf.cond(tf.convert_to_tensor(start >= 0), lambda: start, lambda: start + dim_size)
stop = tf.cond(tf.convert_to_tensor(stop >= 0), lambda: stop, lambda: stop + dim_size)
dim_idx.append([i_dim])
begins.append(start)
sizes.append(stop - start)
# For empty slice specs like [...]
if not dim_idx:
return tf.zeros_like(inp_shape), inp_shape
# Make full begin and size array (including omitted dimensions)
begin_full = tf.scatter_nd(dim_idx, begins, [inp_rank])
size_mask = tf.scatter_nd(dim_idx, tf.ones_like(sizes, dtype=tf.bool), [inp_rank])
size_full = tf.where(size_mask,
tf.scatter_nd(dim_idx, sizes, [inp_rank]),
inp_shape)
return begin_full, size_full
#with tf.Graph().as_default():
x = tf.reshape(tf.range(60), (4, 3, 5))
x2 = replace_slice_in(x)[:2, ..., -3:].with_value([100, 200, 300])
print('Tensor before Changing is \n', x)
print('\n')
print('Tensor after Changing is \n', x2)
Output of the above code is shown below:
Tensor before Changing is
tf.Tensor(
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
[[15 16 17 18 19]
[20 21 22 23 24]
[25 26 27 28 29]]
[[30 31 32 33 34]
[35 36 37 38 39]
[40 41 42 43 44]]
[[45 46 47 48 49]
[50 51 52 53 54]
[55 56 57 58 59]]], shape=(4, 3, 5), dtype=int32)
Tensor after Changing is
tf.Tensor(
[[[ 0 1 100 200 300]
[ 5 6 100 200 300]
[ 10 11 100 200 300]]
[[ 15 16 100 200 300]
[ 20 21 100 200 300]
[ 25 26 100 200 300]]
[[ 30 31 32 33 34]
[ 35 36 37 38 39]
[ 40 41 42 43 44]]
[[ 45 46 47 48 49]
[ 50 51 52 53 54]
[ 55 56 57 58 59]]], shape=(4, 3, 5), dtype=int32)

Apply Function foreach Pixel in Numpy Array

I have a function like:
def calcChromaFromPixel(red, green, blue):
r = int(red)
g = int(green)
b = int(blue)
return math.sqrt(math.pow(r - g, 2) +
math.pow(r - b, 2) +
math.pow(g - b, 2))
and I have an RGB Image, which is already converted into an numpy array with a shape like [width, height, 3], where 3 are the color channels.
What I want to do is to apply the method to every pixel and build the mean from the result. I already have done the obvious thing and iterated over the array with two loops, but that seems to be a really slow thing to do... Is there a faster and prettier way to do that?!
Thanks :)
Code:
import math
import numpy as np
np.random.seed(1)
# FAKE-DATA
img = np.random.randint(0,255,size=(4,4,3))
print(img)
# LOOP APPROACH
def calcChromaFromPixel(red, green, blue):
r = int(red)
g = int(green)
b = int(blue)
return math.sqrt(math.pow(r - g, 2) +
math.pow(r - b, 2) +
math.pow(g - b, 2))
bla = np.zeros(img.shape[:2])
for a in range(img.shape[0]):
for b in range(img.shape[1]):
bla[a,b] = calcChromaFromPixel(*img[a,b])
print('loop')
print(bla)
# VECTORIZED APPROACH
print('vectorized')
res = np.linalg.norm(np.stack(
(img[:,:,0] - img[:,:,1],
img[:,:,0] - img[:,:,2],
img[:,:,1] - img[:,:,2])), axis=0)
print(res)
Out:
[[[ 37 235 140]
[ 72 137 203]
[133 79 192]
[144 129 204]]
[[ 71 237 252]
[134 25 178]
[ 20 254 101]
[146 212 139]]
[[252 234 156]
[157 142 50]
[ 68 215 215]
[233 241 247]]
[[222 96 86]
[141 233 137]
[ 7 63 61]
[ 22 57 1]]]
loop
[[ 242.56545508 160.44313634 138.44132331 97.21111048]
[ 246.05283985 192.94040531 291.07730932 98.66103588]
[ 124.99599994 141.90842117 207.88939367 17.20465053]
[ 185.66636744 133.02631319 77.82030583 69.29646456]]
vectorized
[[ 242.56545508 160.44313634 138.44132331 97.21111048]
[ 246.05283985 192.94040531 291.07730932 98.66103588]
[ 124.99599994 141.90842117 207.88939367 17.20465053]
[ 185.66636744 133.02631319 77.82030583 69.29646456]]

Getting error: Cannot reshape array of size 122304 into shape (52,28,28)

I'm trying to reshape a numpy array as:
data3 = data3.reshape((data3.shape[0], 28, 28))
where data3 is:
[[54 68 66 ..., 83 72 58]
[63 63 63 ..., 51 51 51]
[41 45 80 ..., 44 46 81]
...,
[58 60 61 ..., 75 75 81]
[56 58 59 ..., 72 75 80]
[ 4 4 4 ..., 8 8 8]]
data3.shape is (52, 2352 )
But I keep getting the following error:
ValueError: cannot reshape array of size 122304 into shape (52,28,28)
Exception TypeError: TypeError("'NoneType' object is not callable",) in <function _remove at 0x10b6477d0> ignored
What is happening and how to fix this error?
UPDATE:
I'm doing this to obtain data3 that is being used above:
def image_to_feature_vector(image, size=(28, 28)):
return cv2.resize(image, size).flatten()
data3 = np.array([image_to_feature_vector(cv2.imread(imagePath)) for imagePath in imagePaths])
imagePaths contains paths to all the images in my dataset. I actually want to convert the data3 to a flat list of 784-dim vectors, however the
image_to_feature_vector
function converts it to a 3072-dim vector!!
You can reshape the numpy matrix arrays such that before(a x b x c..n) = after(a x b x c..n). i.e the total elements in the matrix should be same as before, In your case, you can transform it such that transformed data3
has shape (156, 28, 28) or simply :-
import numpy as np
data3 = np.arange(122304).reshape(52, 2352 )
data3 = data3.reshape((data3.shape[0]*3, 28, 28))
print(data3.shape)
Output is of the form
[[[ 0 1 2 ..., 25 26 27]
[ 28 29 30 ..., 53 54 55]
[ 56 57 58 ..., 81 82 83]
...,
[ 700 701 702 ..., 725 726 727]
[ 728 729 730 ..., 753 754 755]
[ 756 757 758 ..., 781 782 783]]
...,
[122248 122249 122250 ..., 122273 122274 122275]
[122276 122277 122278 ..., 122301 122302 122303]]]
First, your input image's number of elements should match the number of elements in the desired feature vector.
Assuming the above is satisfied, the below should work:
# Reading all the images to a one numpy array. Paths of the images are in the imagePaths
data = np.array([np.array(cv2.imread(imagePaths[i])) for i in range(len(imagePaths))])
# This will contain the an array of feature vectors of the images
features = data.flatten().reshape(1, 784)

subtracting RGB values from an Image in Python

I'm working in a project where I need to subtract the RGB values from an Image. In example I want to subtract the BLUE channel from RED, so RED gets the difference value of the subtraction.
I have the next properties of the image:
Dimension:1456x2592,
bpp:3
The image I'm using gives me the following arrays:
[[[ 63 58 60]
[ 63 58 60]
[ 64 59 61]
...,
[155 155 161]
[155 155 161]
[155 155 161]]
[[ 58 53 55]
[ 60 55 57]
[ 62 57 59]
...,
[157 157 163]
[157 157 163]
[158 158 164]]
I know those are the values(RGB) from the image, so now I move on to do the code (I based on this code)
import cv2
import numpy as np
from PIL import Image
# read image into matrix.
m = cv2.imread("ITESO.jpeg")
# get image properties.
h,w,bpp = np.shape(m)
# iterate over the entire image.
# BLUE = 0, GREEN = 1, RED = 2.
for py in range(0,h):
for px in range(0,w):
#m[py][px][2] = 2
n = m[py][px][2] //n takes the value of RED
Y = [n, 0, 0] //I create an array with [RED, 0, 0]
m, Y = np.array(m), np.array(Y)
m = np.absolute(m - Y) //Get the matriz with the substraction
y = 1
x = 1
print (m)
print (m[x][y])
#display image
#cv2.imshow('matrix', m)
#cv2.waitKey(0)
cv2.imwrite('new.jpeg',m)
img = Image.open('new.jpeg')
img.show()
img = Image.open('new.jpeg').convert('L')
img.save('new_gray_scale.jpg')
img.show()
When I print the J matrix it gives the following arrays:
B,G,R
Blue = BLUE - RED
[[[ 3 58 60]
[ 3 58 60]
[ 4 59 61]
...,
[ 95 155 161]
[ 95 155 161]
[ 95 155 161]]
[[ 2 53 55]
[ 0 55 57]
[ 2 57 59]
...,
[ 97 157 163]
[ 97 157 163]
[ 98 158 164]]
But I'm not able to open the new image and if I set one RGB channel to one value it shows me the image. I use the next lines for that:
import cv2
import numpy as np
# read image into matrix.
m = cv2.imread("python.png")
# get image properties.
h,w,bpp = np.shape(m)
# iterate over the entire image.
for py in range(0,h):
for px in range(0,w):
m[py][px][0] = 0 //setting channel Blue to values of 0
# display image
cv2.imshow('matrix', m)
cv2.waitKey(0)
How can I subtract the RGB channels from each other?
PS: In MatLab it works like a charm, but I'm not able to do it in python.
Pay attention that this operation is changing the dtype of the matrix (image) from uint8 to int32, and this can cause other problems. A better way (and more efficient) to do this, IMO, is this:
import cv2
import numpy as np
img = cv2.imread('image.png').astype(np.float) # BGR, float
img[:, :, 2] = np.absolute(img[:, :, 2] - img[:, :, 0]) # R = |R - B|
img = img.astype(np.uint8) # convert back to uint8
cv2.imwrite('new-image.png', img) # save the image
cv2.imshow('img', img)
cv2.waitKey()
Code manipulating RGB negative values to zero...
m = cv2.imread("img.jpg")
# get image properties.
h,w,bpp = np.shape(m)
# iterate over the entire image.
# BLUE = 0, GREEN = 1, RED = 2.
for py in range(0,h):
for px in range(0,w):
n = m[py][px][1]
Y = [0, 0, n]
m, Y = np.array(m), np.array(Y)
a = (m - Y)
if (a[py][px][0] <=0): #if Blue is negative or equal 0
a[py][px][0] = 0 #Blue set to 0
cv2.imwrite('img_R-G.jpg',a)
img = Image.open('img_R-G.jpg').convert('L')
img.save('img_R-G_GS.jpg')

Categories