I am using Image.point and Image.fromarray to do exactly the same operation on an image, increase the value of all pixels together by the same value. The thing is that i get to absolutelly different images.
using point
def getValue(val):
return math.floor(255*float(val)/100)
def func(i):
return int(i+getValue(50))
out = img.point(func)
using array and numpy
arr = np.array(np.asarray(img).astype('float'))
value = math.floor(255*float(50)/100)
arr[...,0] += value
arr[...,1] += value
arr[...,2] += value
out = Image.fromarray(arr.astype('uint8'), 'RGB')
I am using the same image (a jpg).
the initial image
the image with point
the image with arrays
How can they be so much different?
You have values greater than 255 in your array, which you then convert to uint8 ... what do you want those values to become in the image? If you want them to be 255, clip them first:
out_arr_clip = Image.fromarray(arr.clip(0,255).astype('uint8'), 'RGB')
By the way, there's no need to add to each color band separately:
arr = np.asarray(img, dtype=float) # also simplified
value = math.floor(255*float(50)/100)
arr += value # the same as doing this in three separate lines
If your value is different for each band, you can still do this because of broadcasting:
percentages = np.array([25., 50., 75.])
values = np.floor(255*percentages/100)
arr += values # the first will be added to the first channel, etc.
Fixxed it :)
Didn't take under consideration getting out of bounds. So i did
for i in range(3):
conditions = [arr[...,i] > 255, arr[...,i] < 0]
choices = [255, 0]
arr[...,i] = np.select(conditions, choices, default=arr[...,i]
Worked like a charm....:)
Related
I am trying to make a function that uses a double for loop to traverse all pixels in an image (png) to find all the red pixels. The function should return a 2D array in numpy representing the output binary image and writes the 2D array into a new jpg file. I can only use numpy, matplotlib and skimage.
import numpy as np
from matplotlib import pyplot as mat_plot
def find_red_pixels(map_filename, upper_threshold=100, lower_threshold=50):
"""
Returns a 2D array in numpy representing the output binary image and writes the 2D array into a file
"""
map_filename =
mat_plot.imread('./data/map.png')
map_filename = map_filename * 255 # Scale the color values from [0 – 1] range to [0 – 255] range
rgb = np.array(map_filename)
row, col = rgb.shape
for i in range(0, row):
for j in range(0, col):
color = rgb[i,j]
if(color[0] > upper_threshold):
if(color[1] < lower_threshold):
if(color[2] < lower_threshold):
rgb[i,j] = [0,0,0]
mat_plot.imsave('map-red-pixels.jpg', map_filename.astype(np.uint8))
This is what i have so far but i am stuck. A pixel is red if r > upper_threshold, g < lower_threshold and b < lower_threshold
The only problem with your code is the syntax:
Replace '.data/map.png' by map_filename, which is the input of your function.
Delete the empty line between map_filename= and mat_plot.imread('./data/map.png').
Replace map_filename.astype(np.uint8) by rgb.astype(np.uint8).
That being said...
The point of using NumPy is that you can replace loops by matrix operations, which makes the code faster and easier to read. This is an alternative solution for your problem using NumPy and Matplotlib.
def find_red_pixels(_image: str, up_threshold: float, low_threshold: float) -> None:
image = plt.imread(_image) * 255
rgb = image * np.array([[[-1.0, 1.0, 1.0]]])
threshold = np.array([[[-up_threshold, low_threshold, low_threshold]]])
new_image = image - image * np.all(rgb - threshold < 0.0, axis=2)[:, :, None]
plt.imsave("no_red_image.jpg", new_image.astype(np.uint8))
return None
I have a small image that I would like to find all RGB values that are the same and then compare that against my variable and if the match print match or similar.
Below is a little snippet that I have put together from other sources.
I am able to print that I found the colour but it will print a line for every time that value is found in the image.
Is there a better way to search an image and match that to a single RGB value? Then if found do a thing.
import cv2
path = '3.png'
blue = int(224)
green = int(96)
red = int(32)
img = cv2.imread(path)
x,y,z = img.shape
for i in range(x):
for j in range(y):
if img[i,j,0]==blue & img[i,j,1]==green & img[i,j,1]==red:
print("Found colour at ",i,j)
General Python advice: do not say blue = int(224). Just say blue = 224
Your program expects to find those exact values. In any kind of photo, nothing is exact. You need to find ranges of values.
cv.imread returns data in BGR order. Be aware of that if you access individual values in the numpy array.
Use this:
import numpy as np
import cv2 as cv
img = cv.imread("3.png")
lower_bound = (224-20, 96-20, 32-20) # BGR
upper_bound = (224+20, 96+20, 32+20) # BGR
mask = cv.inRange(img, lower_bound, upper_bound)
count = cv.countNonZero(mask)
print(f"picture contains {count} pixels of that color")
And if you need to know where pixels of that color are, please explain what you need that for. A list of those points is generally useless. There are more useful ways to get these locations but they depend on why you need this information, what for.
I think this may help you with small touching :)
import cv2
import numpy as np
r = int(255)
g = int(255)
b = int(255)
img = 255*np.ones((5,5,3), np.uint8)
[rows, cols] = img.shape[:2]
print(img.shape[:2])
print(img.shape)
for i in range(rows):
for j in range(cols):
if img[i, j][0] == r and img[i, j][1] == g and img[i, j][2] == b:
print(img[i, j])
print(i, j)
I have an RGBA image where I have to find if any pixel has red value < 150 and to replace such pixels to black. I am using following code for this:
import numpy as np
imgarr = np.array(img)
for x in range(imgarr.shape[0]):
for y in range(imgarr.shape[1]):
if imgarr[x, y][0] < 150: # red value < 150
imgarr[x, y] = (0,0,0,255)
However, this is a slow loop and I am sure it can be optimized using some function such as numpy.where, but I am not able to fit it in this code. How can this be solved?
For one channel image, we can do as follow
out_val = 0
gray = cv2.imread("colour.png",0)
gray[gray<value] = out_val
Use np.where with the mask of comparison against the threshold -
img = np.asarray(img)
imgarr = np.where(img[...,[0]]<150,(0,0,0,255),img)
We are using img[...,[0]] to keep the number of dims as needed for broadcasted assignment with np.where. So, another way would be to use img[...,0,None]<150 to get the mask that keeps dims.
Editors comment:
How to count pixels occurences in an image?
I have a set of images where each pixel consists of 3 integers in the range 0-255.
I am interested in finding one pixel that is "representative" (as much as possible) for the entire pixel-population as a whole, and that pixel must occur in the pixel-population.
I am determining which pixel is the most common (the median mode) in my set of images makes the most sense.
I am using python, but I am not sure how to go about it.
The images are stored as an numpy array with dimensions [n, h, w, c], where n is the number of images, h is the height, w is the widthandc` is the channels (RGB).
I'm going to assume you need to find the most common element, which as Cris Luengo mentioned is called the mode. I'm also going to assume that the bit depth of the channels is 8-bit (value between 0 and 255, i.e. modulo 256).
Here is an implementation independent approach:
The aim is to maintain a count of all the different kinds of pixels encountered. It makes sense to use a dictionary for this, which would be of the form {pixel_value : count}.
Once this dictionary is populated, we can find the pixel with the highest count.
Now, 'pixels' are not hashable and hence cannot be stored in a dictionary directly. We need a way to assign an integer(which I'll be referring to as the pixel_value) to each unique pixel, i.e., you should be able to convert pixel_value <--> RGB value of a pixel
This function converts RGB values to an integer in the range of 0 to 16,777,215:
def get_pixel_value(pixel):
return pixel.red + 256*pixel.green + 256*256*pixel.blue
and to convert pixel_value back into RGB values:
def get_rgb_values(pixel_value):
red = pixel_value%256
pixel_value //= 256
green = pixel_value%256
pixel_value //= 256
blue = pixel_value
return [red,green,blue]
This function can find the most frequent pixel in an image:
def find_most_common_pixel(image):
histogram = {} #Dictionary keeps count of different kinds of pixels in image
for pixel in image:
pixel_val = get_pixel_value(pixel)
if pixel_val in histogram:
histogram[pixel_val] += 1 #Increment count
else:
histogram[pixel_val] = 1 #pixel_val encountered for the first time
mode_pixel_val = max(histogram, key = histogram.get) #Find pixel_val whose count is maximum
return get_rgb_values(mode_pixel_val) #Returna a list containing RGB Value of the median pixel
If you wish to find the most frequent pixel in a set of images, simply add another loop for image in image_set and populate the dictionary for all pixel_values in all images.
You can iterate over the x/y of the image.
a pixel will be img_array[x, y, :] (the : for the RBG channel)
you will add this to a Counter (from collections)
Here is an example of the concept over an Image
from PIL import Image
import numpy as np
from collections import Counter
# img_path is the path to your image
cnt = Counter()
img = Image.open(img_path)
img_arr = np.array(img)
for x in range(img_arr.shape[0]):
for y in range(img_arr.shape[1]):
cnt[str(img_arr[x, y, :])] += 1
print(cnt)
# Counter({'[255 255 255]': 89916, '[143 143 143]': 1491, '[0 0 0]': 891, '[211 208 209]': 185, ...
A More efficient way to do it is by using the power of numpy and some math manipulation (because we know values are bound [0, 255]
img = Image.open(img_path)
img_arr = np.array(img)
pixels_arr = (img_arr[:, :, 0] + img_arr[:, :, 1]*256 + img_arr[:, :, 2]*(256**2)).flatten()
cnt = Counter(pixels_arr)
# print(cnt)
# Counter({16777215: 89916, 9408399: 1491, 0: 891, 13750483: 185, 14803425: 177, 5263440: 122 ...
# print(cnt.most_common(1))
# [(16777215, 89916)]
pixel_value = cnt.most_common(1)[0][0]
Now a conversion back to the original 3 values is exactly like Aayush Mahajan have writte in his answer. But I've shorten it for the sake of simplicity:
r, b, g = pixel_value%256, (pixel_value//256)%256, pixel_value//(256**2)
So you are using the power of numpy fast computation (and it's significate improvement on run time.
You use Counter which is an extension of python dictionary, dedicated for counting.
I have an RGB video and a single keyframe from that video. In that keyframe, the user will apply a binary mask.
I want to create a mask of the video where pixels have values that exist in the keyframe's masked region.
In other words, I want to create a list of RGB pixel values that exist in the mask of the keyframe, and create a mask of all other frames on the condition that the pixel values exist within the list. Pixel values can be (0,0,0)-(255,255,255)
My current implementation, although technically correct, is extremely inefficient, and I imagine there must be something better.
count = 0
for x in sequence
img = cv2.imread(x)
curr = np.zeros(img.shape[:2],dtype = np.uint16)
for x in range(img.shape[0]):
for y in range(img.shape[1]):
tuple = (img[x][y][0],img[x][y][1],img[x][y][2])
if tuple not in dict:
dict[tuple] = count
curr[x][y] = count
count+=1
else:
curr[x][y] = dict[tuple]
newsequence.append(curr)
#in another function, generate mask2, the mask of the keyframe
immask = cv2.bitwise_and(newsequence[keyframe],newsequence[keyframe],mask=mask2[index].astype('uint8'))
immask = [x for x in immask.flatten() if x != 0]
#for thresholding purposes (if at least 80% of pixels with that value are selected in the keyframe)
valcount= np.bincount(immask)
truecount = np.bincount(newsequence[keyframe].flatten())
frameset = set(immask)
framemask = list(frameset)
framemask = [x for x in framemask if (float(valcount[x])/float(truecount[x]))>0.8]
for frame in range(0,numframes):
for val in framemask:
mask[frame] = np.where((newsequence[frame]==val),255,0).astype('uint8')