How to generate a mask using Pillow's Image.load() function - python

I want to create a mask based on certain pixel values. For example: every pixel where B > 200
The Image.load() method seems to be exactly what I need for identifying the pixels with these values, but I can't seem to figure out how to take all these pixels and create a mask image out of them.
R, G, B = 0, 1, 2
pixels = self.input_image.get_value().load()
width, height = self.input_image.get_value().size
for y in range(0, height):
for x in range(0, width):
if pixels[x, y][B] > 200:
print("%s - %s's blue is more than 200" % (x, y))
``

I meant for you to avoid for loops and just use Numpy. So, starting with this image:
from PIL import Image
import numpy as np
# Open image
im = Image.open('colorwheel.png')
# Make Numpy array
ni = np.array(im)
# Mask pixels where Blue > 200
blues = ni[:,:,2]>200
# Save logical mask as PNG
Image.fromarray((blues*255).astype(np.uint8)).save('result.png')
If you want to make the masked pixels black, use:
ni[blues] = 0
Image.fromarray(ni).save('result.png')
You can make more complex, compound tests against ranges like this:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
# Open image
im = Image.open('colorwheel.png')
# Make Numpy array
ni = np.array(im)
# Mask pixels where 100 < Blue < 200
blues = ( ni[:,:,2]>100 ) & (ni[:,:,2]<200)
# Save logical mask as PNG
Image.fromarray((blues*255).astype(np.uint8)).save('result.png')
You can also make a condition on Reds, Greens and Blues and then use Numpy's np.logical_and() and np.logical_or() to make compound conditions, e.g.:
bluesHi = ni[:,:,2] > 200
redsLo = ni[:,:,0] < 50
mask = np.logical_and(bluesHi,redsLo)

Thanks to the reply from Mark Setchell, I solved by making a numpy array the same size as my image filled with zeroes. Then for every pixel where B > 200, I set the corresponding value in the array to 255. Finally I converted the numpy array to a PIL image in the same mode as my input image was.
R, G, B = 0, 1, 2
pixels = self.input_image.get_value().load()
width, height = self.input_image.get_value().size
mode = self.input_image.get_value().mode
mask = np.zeros((height, width))
for y in range(0, height):
for x in range(0, width):
if pixels[x, y][2] > 200:
mask[y][x] = 255
mask_image = Image.fromarray(mask).convert(mode)

Related

i want to change every single pixel of an img to black exluding transparent python

I want to edit all the pixels of a PNG image except for the transparent pixels. I have this code:
img_da_maskerare = Image.open("temp.png")
width = img_da_maskerare.size[0]
height = img_da_maskerare.size[1]
for i in range(0,width):
for j in range(0,height):
data = img_da_maskerare.getpixel((i,j))
#print(data) #(255, 255, 255)
if (data[0]!=255 and data[1]!=0 and data[2]!=0):
img_da_maskerare.putpixel((i,j),(00, 00, 00))
img_da_maskerare.show()
example: original becomes this
What is wrong with the code?
Iterating over images in Python with for loops should only be a very last resort - it is slow, error-prone and inefficient.
Try to favour built-in functions that are programmed in optimised C or vectorised Numpy functions.
Essentially, you seem to want an image that is black anywhere the original image is opaque. So, let's be guided by that:
from PIL import Image
# Load image
im = Image.open('Ow40z.png')
# Extract the alpha channel and threshold it at 200
alpha = im.getchannel('A')
alphaThresh = alpha.point(lambda p: 255 if p>200 else 0)
# Make a new completely black image same size as original
res = Image.new('RGB', im.size)
# Copy across the alpha channel from original
res.putalpha(alphaThresh)
res.save('result.png')
Just FYI, that takes 337 microseconds versus 166 milliseconds for your loop-based approach, i.e. it is around 500x faster, and there are no loops and indices to get wrong.
I mentioned you can use Numpy so here is much the same thing done using that:
import numpy as np
# Open image and make into Numpy array
na = np.array(Image.open('Ow40z.png'))
# Make every pixel, of every row of every column black in RGB channels, i.e. channel 0, 1, 2
na[:, :, :3] = 0
# Make Boolean True/False mask of all alphas > 200
mask = na[:,:,3] > 200
# Set alpha channel to 255 wherever mask is True, and 0 elsewhere
na[:, :, 3] = mask * 255
# Convert back from Numpy array to PIL Image
pilImage = Image.fromarray(na)
if data[3] == 255
The data has a length of 4 representing the RGBA. Where A alpha represents the opacity of the pixel. which has values from 0 to 255. 0 means completely transparent, and 255 means completely opaque.
Here, I used data[3] > 200 to include those pixels too that have slight transparency.
The code code changes
to
# import PIL module
from PIL import Image
img_da_maskerare = Image.open("temp.png")
width = img_da_maskerare.size[0]
height = img_da_maskerare.size[1]
for i in range(0,width):
for j in range(0,height):
data = img_da_maskerare.getpixel((i,j))
# RGBA = Red, Green, Blue, Alpha
# Alpha = 0 = transparent, 255 = opaque
# alpha > 200 means almost opaque
if data[3] >= 200:
img_da_maskerare.putpixel((i,j),(00, 00, 00))
img_da_maskerare.save('temp_maskerad.png')

What is the Fastest way to change pixel values of an RGB image using Numpy / Opencv / scikit-image

Given a binary image, what is the fastest and Pythonic way to convert the image to RGB and then modify it's pixels?
I have these two ways but they don't feel good to me
def get_mask(rgb_image_path):
mask = np.array(Image.open(rgb_image_path).convert('L'), dtype = np.float32) # Mask should be Grayscale so each value is either 0 or 255
mask[mask == 255.0] = 1.0 # whereever there is 255, convert it to 1: (1 == 255 == White)
return mask
def approach1(mask):
mask = np.logical_not(mask)
mask = mask.astype(np.uint8)
mask = mask*255
red = mask.copy()
blue = mask.copy()
green = mask.copy()
red[red == 0] = 26
blue[blue == 0] = 237
green[green == 0] = 160
mask = np.stack([red, blue, green], axis = -1)
plt.imshow(mask)
def approach2(mask):
mask = np.logical_not(mask)
mask = mask.astype(np.uint8)
mask = np.stack([mask,mask,mask], axis = -1)
mask = mask*255
width,height, channels = mask.shape
for i in range(width):
for j in range(height):
if mask[i][j][0] == 0:
mask[i][j] = (26,237,160)
plt.imshow(mask)
Below is the Image
I suppose the most simple way is this:
def mask_coloring(mask):
expected_color = (26, 237, 160)
color_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
color_mask[mask == 255.0, :] = expected_color
plt.imshow(color_mask)
By the way, similar approach can be found here.
The easiest way to do this is to take advantage of the fact that you only have and only want 2 colours and to use a fast, space-efficient palette image that doesn't require you to iterate over all the pixels, or store 3 bytes per pixel:
from PIL import Image
# Open your image
im = Image.open('oqX2g.gif')
# Create a new palette where the 1st entry is black (0,0,0) and the 2nd is your colour (26,237,160) then pad out to 256 RGB entries
palette = [0,0,0] + [26,237,160] + [0,0,0] * 254
# Put palette into image and save
im.putpalette(palette)
im.save('result.png')
Read more about palette images here.
What if you want the black background to become magenta, and the foreground green? Oh, and you want it as an RGB Numpy array?
Just change the palette and convert:
from PIL import Image
import numpy as np
# Open your image, push in new palette
im = Image.open('oqX2g.gif')
palette = [255,0,255] + [0,255,0] + [0,0,0] * 254
im.putpalette(palette)
# Make RGB Numpy array
na = np.array(im.convert('RGB'))

How can I convert an array of pixel colors into an image efficiently with Python?

I am working on a project that implements Numba and CUDA with Python 3.8. Currently, I create an array with the dimensions of the final image. Next, I generate an image with a CUDA kernel (incredibly fast). Then, I copy the pixel color into a Pillow Image (incredibly slow). My code:
for x in range(width):
for y in range(height):
if pixels[x][y] = 0:
color = [0, 0, 0]
else:
# Get color from int as tuple
color = rgb_conv(pixels[x][y]
red = int(numpy.floor(color[0]))
if red > 255:
red = 255
elif red < 0:
red = 0
green = int(numpy.floor(color[1]))
if green > 255:
green = 255
elif green < 0:
green = 0
blue = int(numpy.floor(color[2]))
if blue > 255:
blue = 255
elif blue < 0:
blue = 0
image.putpixel((x, y), (red, green, blue))
Are there any more efficient Python image libraries for this implementation? Is there a way to convert the array to an image on the GPU? Any help with direction works. Thanks!
EDIT 1: A request was made for the function rgb_conv. This is a function I found to convert a single integer into a three-wide color.
def rgb_conv(i):
color = 255 * numpy.array(colorsys.hsv_to_rgb(i / 255.0, 1.0, 0.5))
return tuple(color.astype(int))
However, I didn't particularly like the colors this function produces, so I removed it and began working with the following:
pixelArr = image.load()
for x in range(width):
for y in range(height):
color = int(numpy.floor(pixels[x][y]))
pixelArr[x, y] = (color << 21) + (color << 10) + color * 8
This adjustment doesn't do much to the running time of the code. I am looking further into a suggestion load the image from an array rather than putting each pixel into the image.
It is not efficient to place each pixel into an image with pillow. Creating an image from a numpy array is significantly faster than before. By faster, I mean the 3840x2160 image took minutes, but now takes 0.0117 seconds.
To create an array that can be converted to an image:
import numpy
pixels = numpy.zeros((height, width, 3), dtype=numpy.uint8)
Here, I run calculations on the GPU to create an image with the pixels array. To efficiently convert an array of pixels into an image:
from PIL import Image
image = Image.fromarray(pixels)

Conditionnally assign pixel values of one image to another

I have two images ( of the same size): A and B
A is the mask, it contains regions that have zero value and others that have RGB values.
B is the RGB image that i want to change the values of some of its pixels to their correspondent A's pixels (pixels that have the same position and that are different from zero).
I think it would be something like this:
if A(i,j) <>0 then B(i,j)=A(i,j)
except that i don't know how to write it in python...
can anyone help?
If you read the images with opencv:
h = b.shape[0]
w = b.shape[1]
for y in range(0, h):
for x in range(0, w):
if a[y,x] > 0:
b[y,x] = a[y,x]
Or better, as points #Dan Mašek in the comment
import numpy as np
def apply_mask(img, mask):
img = np.where(mask > 0, mask, img)
return img
Notice that in numpy arrays, the height comes first in shape. Opencv loads the image into numpy arrays.
To apply the mask for src, you can use cv2.bitwise_and:
cv2.bitwise_and(src, src, mask=mask)

Set masked pixels in a 3D RGB numpy array

I'd like to set all pixels matching some condition in a 3d numpy array (RGB image) using a mask. I have something like this:
def make_dot(img, color, radius):
"""Make a dot of given color in the center of img (rgb numpy array)"""
(ydim,xdim,dummy) = img.shape
# make an open grid of x,y
y,x = np.ogrid[0:ydim, 0:xdim, ]
y -= ydim/2 # centered at the origin
x -= xdim/2
# now make a mask
mask = x**2+y**2 <= radius**2 # start with 2d
mask.shape = mask.shape + (1,) # make it 3d
print img[mask].shape
img[mask] = color
img = np.zeros((100, 200, 3))
make_dot(img, np.array((.1, .2, .3)), 25)
but that gives ValueError: array is not broadcastable to correct shape in this line:
img[mask] = color
because the shape of img[mask] is (1961,); i.e. it's flattened to contain only the "valid" pixels, which makes sense; but how can I make it "write through the mask" as it were to set only the pixels where the mask is 1? Note that I want to write three values at once to each pixel (the last dim).
You almost have it right.
(ydim,xdim,dummy) = img.shape
# make an open grid of x,y
y,x = np.ogrid[0:ydim, 0:xdim, ]
y -= ydim/2 # centered at the origin
x -= xdim/2
# now make a mask
mask = x**2+y**2 <= radius**2 # start with 2d
img[mask,:] = color
the extra ",:" at the end of the assignment lets you assign the color throughout the 3 channels in one shot.

Categories