Related
[[ 0, 0, 0, 0, 255, 0, 0, 0, 0],
[ 0, 0, 255, 255, 255, 255, 255, 0, 0],
[ 0, 255, 255, 255, 255, 255, 255, 255, 0],
[ 0, 255, 255, 255, 255, 255, 255, 255, 0],
[255, 255, 255, 255, 255, 255, 255, 255, 255],
[ 0, 255, 255, 255, 255, 255, 255, 255, 0],
[ 0, 255, 255, 255, 255, 255, 255, 255, 0],
[ 0, 0, 255, 255, 255, 255, 255, 0, 0],
[ 0, 0, 0, 0, 255, 0, 0, 0, 0]]
I have a mask array like the one above. I would like to get the x and y coordinates belonging to the perimeter of the mask. The perimeter points are the ones shown in the array below:
[[ 0, 0, 0, 0, 255, 0, 0, 0, 0],
[ 0, 0, 255, 255, 0, 255, 255, 0, 0],
[ 0, 255, 0, 0, 0, 0, 0, 255, 0],
[ 0, 255, 0, 0, 0, 0, 0, 255, 0],
[255, 0, 0, 0, 0, 0, 0, 0, 255],
[ 0, 255, 0, 0, 0, 0, 0, 255, 0],
[ 0, 255, 0, 0, 0, 0, 0, 255, 0],
[ 0, 0, 255, 255, 0, 255, 255, 0, 0],
[ 0, 0, 0, 0, 255, 0, 0, 0, 0]]
In the array above, I could just use numpy.nonzero() but I was unable to apply this logic to the original array because it returns a tuple of arrays each containing all the x or y values of all non zero elements without partitioning by row.
I wrote the code below which works but seems inefficient:
height = mask.shape[0]
width = mask.shape[1]
y_coords = []
x_coords = []
for y in range(1,height-1,1):
for x in range(0,width-1,1):
val = mask[y,x]
prev_val = mask[y,(x-1)]
next_val = mask[y, (x+1)]
top_val = mask[y-1, x]
bot_val = mask[y+1, x]
if (val != 0 and prev_val == 0) or (val != 0 and next_val == 0) or (val != 0 and top_val == 0) or (val != 0 and bot_val == 0):
y_coords.append(y)
x_coords.append(x)
I am new to python and would like to learn a better way to do this. Perhaps using Numpy?
I played a bit with your problem and found a solution and I realized you could use convolutions to count the number of neighboring 255s for each cell, and then perform a filtering of points based on the appropriate values of neighbors.
I am giving a detailed explanation below, although one part was trial and error and you could potentially skip it and get directly to the code if you understand that convolutions can count neighbors in binary images.
First observation: When does a point belong to the perimeter of the mask?
Well, that point has to have a value of 255 and "around" it, there must be at least one (and possibly more) 0.
Next: What is the definition of "around"?
We could consider all four cardinal (i.e. North, East, South, West) neighbors. In this case, a point of the perimeter must have at least one cardinal neighbor which is 0.
You have already done that, and truly, I cannot thing of a faster way by this definition.
What if we extended the definition of "around"?
Now, let's consider the neighbors of a point at (i,j) all points along an N x N square centered on (i,j). I.e. all points (x,y) such that i-N/2 <= x <= i+N/2 and j-N/2 <= y <= j+N/2 (where N is odd and ignoring out of bounds for the moment).
This is more useful from a performance point of view, because the operation of sliding "windows" along the points of 2D arrays is called a "convolution" operation. There are built in functions to perform such operations on numpy arrays really fast. The scipy.ndimage.convolve works great.
I won't attempt to fully explain convolutions here (the internet is ful of nice visuals), but the main idea is that the convolution essentially replaces the value of each cell with the weighted sum of the values of all its neighboring cells. Depending on what weight matrix, (or kernel) you specify, the convolution does different things.
Now, if your mask was 1s and 0s, to count the number of neighboring ones around a cell, you would need a kernel matrix of 1s everywhere (since the weighted sum will simply add the original ones of your mask and cancel the 0s). So we will scale the values from [0, 255] to [0,1].
Great, we know how to quickly count the neighbors of a point within an area, but the two questions are
What area size should we choose?
How many neighbors do the points in the perimeter have, now that we are including diagonal and more faraway neighbors?
I suppose there is an explicit answer to that, but I did some trial and error. It turns out, we need N=5, at which case the number of neighbors being one for each point in the original mask is the following:
[[ 3 5 8 10 11 10 8 5 3]
[ 5 8 12 15 16 15 12 8 5]
[ 8 12 17 20 21 20 17 12 8]
[10 15 20 24 25 24 20 15 10]
[11 16 21 25 25 25 21 16 11]
[10 15 20 24 25 24 20 15 10]
[ 8 12 17 20 21 20 17 12 8]
[ 5 8 12 15 16 15 12 8 5]
[ 3 5 8 10 11 10 8 5 3]]
Comparing that matrix with your original mask, the points on the perimeter are the ones having values between 11 and 15 (inclusive) [1]. So we simply filter out the rest using np.where().
A final caveat: We need to explicilty say to the convolve function how to treat points near the edges, where an N x N window won't fit. In those cases, we tell it to treat out of bounds values as 0s.
The full code is following:
from scipy import ndimage as ndi
mask //= 255
kernel = np.ones((5,5))
C = ndi.convolve(mask, kernel, mode='constant', cval=0)
#print(C) # The C matrix contains the number of neighbors for each cell.
outer = np.where( (C>=11) & (C<=15 ), 255, 0)
print(outer)
[[ 0 0 0 0 255 0 0 0 0]
[ 0 0 255 255 0 255 255 0 0]
[ 0 255 0 0 0 0 0 255 0]
[ 0 255 0 0 0 0 0 255 0]
[255 0 0 0 0 0 0 0 255]
[ 0 255 0 0 0 0 0 255 0]
[ 0 255 0 0 0 0 0 255 0]
[ 0 0 255 255 0 255 255 0 0]
[ 0 0 0 0 255 0 0 0 0]]
[1] Note that we are also counting the point itself as one of its own neighbors. That's alright.
I think this would work, I edit my answer based on my new understanding of the problem you want the outer pixel of 255 circle
What I did here is getting all the axis of where pixels are 255 items (print it to see) and then selecting the first occurrence and last occurrence that's it easy
result=np.where(pixel==255)
items=list(zip(result[0],result[1]))
unique=[]
perimiter=[]
for index in range(len(items)-1):
if items[index][0]!=items[index+1][0] or items[index][0] not in unique:
unique.append(items[index][0])
perimiter.append(items[index])
perimiter.append(items[-1])
Output
[(0, 4),
(1, 2),
(1, 6),
(2, 1),
(2, 7),
(3, 1),
(3, 7),
(4, 0),
(4, 8),
(5, 1),
(5, 7),
(6, 1),
(6, 7),
(7, 2),
(7, 6),
(8, 4)]
I am working with numpy and images. I have a big image which i want to process bit by bit.
So I want to create a reference to the original image, do something with it and move on. But when I change something in the frame the change does not transfer to the original, which is the opposite of everything I read online
for example here : https://stackoverflow.com/a/53939444/11333604 if you don't use copy if you change one array the other changes. That does NOT HAPPEN to me
here is some pseudo code that demonstrates my problem
import numpy as np
#create a "big" imgae total black
matrix = np.full((5,5),255,np.uint8)
# the frame that i want to process
h = 3
w = 3
sub = matrix[:h, :w]
#mask to make everything 0
mask = np.zeros((h,w),np.uint8)
sub = sub & mask
#also change something radom so i know the problem is not the & or the mask
sub[0][0] = 12
#sub is changes
print(sub)
#matrix is not
print(matrix)
output
[[12 0 0]
[ 0 0 0]
[ 0 0 0]]
[[255 255 255 255 255]
[255 255 255 255 255]
[255 255 255 255 255]
[255 255 255 255 255]
[255 255 255 255 255]]
I suspect it has something to do with my arrays being 2d but i cant think of how
That's because in this line
sub = sub & mask
you're making sub to "look" at some new array formed with sub & mask and it loses its connection to matrix. If you do this in-place instead
sub &= mask
then matrix will be affected too:
>>> matrix
array([[ 12, 0, 0, 255, 255],
[ 0, 0, 0, 255, 255],
[ 0, 0, 0, 255, 255],
[255, 255, 255, 255, 255],
[255, 255, 255, 255, 255]], dtype=uint8)
I would like to extend the 2d array in python in some way.
No loops
F.e. if it is:
[[255, 255, 255],
[255, 255, 255],
[255, 255, 255]]
I would say I want to extend it by the factor of 2 and get like this:
[[255, 0, 255, 0, 255, 0],
[0, 0, 0, 0, 0, 0],
[255, 0, 255, 0, 255, 0],
[0, 0, 0, 0, 0, 0],
[255, 0, 255, 0, 255, 0],
[0, 0, 0, 0, 0, 0]]
and etc, if by 4 factor.
Is there any function?
Here is a solution using numpy. You didn't provide example for N=4, so I guessed :
import numpy as np
arr = np.array([[255, 255, 255], [255, 255, 255], [255, 255, 255]])
factor = 4
print(arr)
nx, ny = arr.shape
if nx != ny:
raise Exception("Array is not square")
step = 2 + factor//2 - 1
stop = nx * step
print('stop:', stop)
print('step:', step)
for x in range(1,stop,step):
print()
nx, ny = arr.shape
print('x:', x)
value = [[0]*nx]*(factor//2)
print('Inserting columns:', value)
arr = np.insert(arr, x, value, axis=1)
nx, ny = arr.shape
print(arr)
value = [[0]*ny]*(factor//2)
print('Inserting rows:', value)
arr = np.insert(arr, x, value, axis=0)
print(arr)
[[255 255 255]
[255 255 255]
[255 255 255]]
stop: 9
step: 3
x: 1
Inserting columns: [[0, 0, 0], [0, 0, 0]]
[[255 0 0 255 255]
[255 0 0 255 255]
[255 0 0 255 255]]
Inserting rows: [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
[[255 0 0 255 255]
[ 0 0 0 0 0]
[ 0 0 0 0 0]
[255 0 0 255 255]
[255 0 0 255 255]]
x: 4
Inserting columns: [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
[[255 0 0 255 0 0 255]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255]
[255 0 0 255 0 0 255]]
Inserting rows: [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]]
[[255 0 0 255 0 0 255]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255]
[ 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255]]
x: 7
Inserting columns: [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]]
[[255 0 0 255 0 0 255 0 0]
[ 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255 0 0]
[ 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255 0 0]]
Inserting rows: [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]
[[255 0 0 255 0 0 255 0 0]
[ 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255 0 0]
[ 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0]
[255 0 0 255 0 0 255 0 0]
[ 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0]]
You can create new bigger array with zeros
factor = 2
h, w = arr.shape[:2]
new_w = h*factor
new_h = h*factor
new_arr = np.zeros((new_h, new_w), np.uint8)
and then you can use for-loops with zip() and range(0, new_h, factor) to get value and its new position
for row, y in zip(arr, range(0, new_h, factor)):
for value, x in zip(row, range(0, new_w, factor)):
new_arr[y,x] = value
gives
[[255 0 255 0 255 0]
[ 0 0 0 0 0 0]
[255 0 255 0 255 0]
[ 0 0 0 0 0 0]
[255 0 255 0 255 0]
[ 0 0 0 0 0 0]]
If you use different value instead of 0 in range then you can get offset
offset_y = 1
offset_x = 1
for row, y in zip(arr, range(offset_y, new_h, factor)):
for value, x in zip(row, range(offset_x, new_w, factor)):
new_arr[y,x] = value
gives:
[[ 0 0 0 0 0 0]
[ 0 255 0 255 0 255]
[ 0 0 0 0 0 0]
[ 0 255 0 255 0 255]
[ 0 0 0 0 0 0]
[ 0 255 0 255 0 255]]
Working code
import numpy as np
arr = np.array([[255, 255, 255],
[255, 255, 255],
[255, 255, 255]]
)
factor = 2
h, w = arr.shape[:2]
new_w = h*factor
new_h = h*factor
new_arr = np.zeros((new_h, new_w), np.uint8)
offset_x = 0
offset_y = 0
for row, y in zip(arr, range(offset_y, new_h, factor)):
#print(row, y)
for value, x in zip(row, range(offset_x, new_w, factor)):
#print(y, x, value)
new_arr[y,x] = value
print(new_arr)
BTW: You could even use factor_x, factor_y with different values.
for example
factor_x = 4
factor_y = 2
in code
import numpy as np
arr = np.array([[255, 255, 255],
[255, 255, 255],
[255, 255, 255]]
)
factor_x = 4
factor_y = 2
h, w = arr.shape[:2]
new_w = h*factor_x
new_h = h*factor_y
new_arr = np.zeros((new_h, new_w), np.uint8)
offset_x = 0
offset_y = 0
for row, y in zip(arr, range(offset_y, new_h, factor_y)):
#print(row, y)
for value, x in zip(row, range(offset_x, new_w, factor_x)):
#print(y, x, value)
new_arr[y,x] = value
print(new_arr)
gives
[[255 0 0 0 255 0 0 0 255 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0]
[255 0 0 0 255 0 0 0 255 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0]
[255 0 0 0 255 0 0 0 255 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0]]
Extending is simple with slicing:
array = np.array([[255, 255, 255], [255, 255, 255], [255, 255, 255]])
factor = 2
extended_array = np.zeros((array.shape[0]*factor, array.shape[1]*factor))
extended_array[::factor,::factor] = array
You can do this without numpy just with list comprehension:
lst = [[255, 255, 255],
[255, 255, 255],
[255, 255, 255]]
extend_list = [ [lst[j // 2][i // 2] if j % 2 == 0 and i % 2 == 0 else 0 for i in range( 2 * (len(lst[j // 2])) )] if j != len(2 * lst) else [0 for _ in range( (2 * len(lst)) -1)] for j in range(2 * (len(lst)) )]
print(extend_list)
the output:
[[255, 0, 255, 0, 255, 0], [0, 0, 0, 0, 0, 0], [255, 0, 255, 0, 255, 0], [0, 0, 0], [255, 0, 255, 0, 255, 0], [0, 0, 0, 0, 0, 0]]
I want to create a PIL image from a NumPy array. Here is my attempt:
# Create a NumPy array, which has four elements. The top-left should be pure
# red, the top-right should be pure blue, the bottom-left should be pure green,
# and the bottom-right should be yellow.
pixels = np.array([[[255, 0, 0], [0, 255, 0]], [[0, 0, 255], [255, 255, 0]]])
# Create a PIL image from the NumPy array
image = Image.fromarray(pixels, 'RGB')
# Print out the pixel values
print image.getpixel((0, 0))
print image.getpixel((0, 1))
print image.getpixel((1, 0))
print image.getpixel((1, 1))
# Save the image
image.save('image.png')
However, the print out gives the following:
(255, 0, 0)
(0, 0, 0)
(0, 0, 0)
(0, 0, 0)
And the saved image has pure red in the top-left, but all the other pixels are black. Why are these other pixels not retaining the colour I have assigned to them in the NumPy array?
The RGB mode is expecting 8-bit values, so just casting your array should fix the problem:
In [25]: image = Image.fromarray(pixels.astype('uint8'), 'RGB')
...:
...: # Print out the pixel values
...: print image.getpixel((0, 0))
...: print image.getpixel((0, 1))
...: print image.getpixel((1, 0))
...: print image.getpixel((1, 1))
...:
(255, 0, 0)
(0, 0, 255)
(0, 255, 0)
(255, 255, 0)
Your numpy array should be of the form:
[[[248 248 248] # R G B
[248 248 248]
[249 249 249]
...
[ 79 76 45]
[ 79 76 45]
[ 78 75 44]]
[[247 247 247]
[247 247 247]
[248 248 248]
...
[ 80 77 46]
[ 79 76 45]
[ 79 76 45]]
...
[[148 121 92]
[149 122 93]
[153 126 97]
...
[126 117 100]
[126 117 100]
[125 116 99]]]
Assuming you have your numpy array stored in np_arr, here is how to convert it to a pillow Image:
from PIL import Image
import numpy as np
new_im = Image.fromarray(np_arr)
To show the new image, use:
new_im.show()
I'm trying to get pixel values within contours. I've followed along with answers to similar questions but my results are off.
This block of code finds contours for an image and then iterates through them to find the contour containing the largest area. I added the ending if statement that tries to get the RGB value of the code if it is during daylight hours. The original image (video frame) is passed to a function I wrote (grab_rgb), along with the contour.
thresh = cv2.dilate(thresh, None, iterations=2)
(_, cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# look for motion
motion_found = False
biggest_area = 0
# examine the contours, looking for the largest one
for c in cnts:
(x, y, w, h) = cv2.boundingRect(c)
# get an approximate area of the contour
found_area = w * h
# find the largest bounding rectangle
if (found_area > MIN_AREA) and (found_area > biggest_area):
biggest_area = found_area
motion_found = True
if not is_nighttime():
rgb = grab_rgb(image, c)
else:
rgb = 'nighttime'
This is the function I wrote:
def grab_rgb(image, c):
pixels = []
# TODO: Convert to real code
# Detect pixel values (RGB)
mask = np.zeros_like(image)
cv2.drawContours(mask, c, -1, color=255, thickness=-1)
points = np.where(mask == 255)
for point in points:
pixel = (image[point[1], point[0]])
pixel = pixel.tolist()
pixels.append(pixel)
pixels = [tuple(l) for l in pixels]
car_color = (pixels[1])
r = car_color[0]
g = car_color[1]
b = car_color[2]
pixel_string = '{0},{1},{2}'.format(r, g, b)
return pixel_string
The code runs, but returns only three RGB values, with only the second value containing anything meaningful (values 0 and 2 are [0,0,0],[0,0,0]. There should definitely be more than three pixels within the contours, so I'm not sure where I went wrong.
EDIT: I realized it might be helpful to include what is actually being stored in the variables.
mask:
[[[ 0 0 0]
[ 0 0 0]
[ 0 0 0]
...,
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[ 0 0 0]
[255 0 0]
[ 0 0 0]
...,
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[ 0 0 0]
[ 0 0 0]
[ 0 0 0]
...,
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
...,
[[ 0 0 0]
[ 0 0 0]
[ 0 0 0]
...,
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[ 0 0 0]
[ 0 0 0]
[ 0 0 0]
...,
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[ 0 0 0]
[ 0 0 0]
[ 0 0 0]
...,
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]]
points:
(array([ 1, 1, 3, 5, 10, 11, 11, 12, 12, 13, 13, 14, 14], dtype=int32), array([ 1, 22, 22, 24, 24, 21, 23, 16, 20, 9, 15, 1, 8], dtype=int32), array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int32))
pixel:
[0, 0, 0] [136, 89, 96] [0, 0, 0]
pixels:
[(0, 0, 0), (136, 89, 96), (0, 0, 0)]
car_color:
(136, 89, 96)
It seems like what you've asked the code to return is the RGB value of just the second point in the pixel values list (called 'pixels' here ) of the points in every contour passed to grab_rgb, with
car_color = (pixels[1])
r = car_color[0]
g = car_color[1]
b = car_color[2]
So the output should mean that your image has atleast three detected contours satisfying your area constraints, and that the RGB values of the second point in the contours' point lists are what you mentioned([0,0,0],[x,y,z] and [0,0,0]).