I have already taken a look at this question: SO question and seem to have implemented a very similar technique for replacing a single color including the alpha values:
c = Image.open(f)
c = c.convert("RGBA")
w, h = c.size
cnt = 0
for px in c.getdata():
c.putpixel((int(cnt % w), int(cnt / w)), (255, 0, 0, px[3]))
cnt += 1
However, this is very slow. I found this recipe out on the interwebs, but have not had success using it thus far.
What I am trying to do is take various PNG images that consist of a single color, white. Each pixel is 100% white with various alpha values, including alpha = 0. What I want to do is basically colorize the image with a new set color, for instance #ff0000<00-ff>. SO my starting and resulting images would look like this where the left side is my starting image and the right is my ending image (NOTE: background has been changed to a light gray so you can see it since it is actually transparent and you wouldn't be able to see the dots on the left.)
Any better way to do this?
If you have numpy, it provides a much, much faster way to operate on PIL images.
E.g.:
import Image
import numpy as np
im = Image.open('test.png')
im = im.convert('RGBA')
data = np.array(im) # "data" is a height x width x 4 numpy array
red, green, blue, alpha = data.T # Temporarily unpack the bands for readability
# Replace white with red... (leaves alpha values alone...)
white_areas = (red == 255) & (blue == 255) & (green == 255)
data[..., :-1][white_areas.T] = (255, 0, 0) # Transpose back needed
im2 = Image.fromarray(data)
im2.show()
Edit: It's a slow Monday, so I figured I'd add a couple of examples:
Just to show that it's leaving the alpha values alone, here's the results for a version of your example image with a radial gradient applied to the alpha channel:
Original:
Result:
Try this , in this sample we set the color to black if color is not white .
#!/usr/bin/python
from PIL import Image
import sys
img = Image.open(sys.argv[1])
img = img.convert("RGBA")
pixdata = img.load()
# Clean the background noise, if color != white, then set to black.
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255):
pixdata[x, y] = (0, 0, 0, 255)
you can use color picker in gimp to absorb the color and see that's rgba color
The Pythonware PIL online book chapter for the Image module stipulates that putpixel() is slow and suggests that it can be sped up by inlining. Or depending on PIL version, using load() instead.
Related
I have two functions.
The first one changes the opacity of all black pixels in the overlay image to 0.
The second image overlays onto another one with changing opacity. But only for all pixels that are not black.
Overall I want to overlay images with different opacities and prevent that the black background does not show up.
It works just fine, but not really fast (both avg > 2sec). Is there a way to speed up the process a little? Numpy for example?
Here is what I have so far:
def remove_background(image):
im = image.convert('RGBA')
w, h = im.size
for i in range(w):
for j in range(h):
cordinate = i, j
rgb_map = im.getpixel(cordinate)
if rgb_map == (0, 0, 0, 255):
im.putpixel(cordinate, (0, 0, 0, 0))
return im
def overlay_image(background, overlay, opacity=0.5):
background = background.convert('RGBA')
overlay = overlay.convert('RGBA')
opacity = int(opacity * 255)
w, h = background.size
for i in range(w):
for j in range(h):
cordinate = i, j
rgb_map = overlay.getpixel(cordinate)
r = rgb_map[0]
g = rgb_map[1]
b = rgb_map[2]
if r == 64 and g == 128 and b == 19:
overlay.putpixel(cordinate, (64, 128, 19, opacity))
background.paste(overlay, (0,0), overlay)
return background
I know there is something from PIL called Image.blend(), but this is just for the whole image and not for certain pixels.
Here is an example I want to overlay:
animal
mask
result
Updated Answer
I think this is what your updated question is looking for:
#!/usr/bin/env python3
from PIL import Image
# Open the mask, discarding any alpha channel
mask = Image.open('mask.png').convert('RGB')
# Generate a new alpha channel that is transparent wherever the mask is black
alpha = mask.copy()
alpha = alpha.convert('L')
alpha.point(lambda p: 128 if p > 0 else 0)
# Open the animal, discarding its pointless alpha channel
animal = Image.open('animal.png').convert('RGB')
# Paste the mask over the animal, respecting transparency
animal.paste(mask,mask=alpha)
animal.save('result.png')
Alternative
As an alternative, you could simply add the mask to the animal. Where the mask is black (i.e. zero) it will add nothing:
#!/usr/bin/env python3
from PIL import Image, ImageChops
# Open the mask, discarding any alpha channel
mask = Image.open('mask.png').convert('RGB')
# Open the animal, discarding any alpha channel
animal = Image.open('animal.png').convert('RGB')
# Add the mask to the animal
res = ImageChops.add(animal, mask)
res.save('result.png')
You could scale the mask first, say by a half, using Image.point() with a lambda like below, if the green is too much.
Original Answer
Mmmm, if you want to mask an input image with another input image and show the results, I think you'd need three images, not two.
So, I am guessing this is what you want and it should be nice and fast:
#!/usr/bin/env python3
from PIL import Image
# Open the mask, discarding its pointless alpha channel and colour information
mask = Image.open('mask.png').convert('L')
# Generate a new alpha channel that is transparent wherever the mask is black
mask.point(lambda p: 255 if p > 0 else 0)
# Open the animal, discarding its pointless alpha channel
animal = Image.open('animal.png').convert('RGB')
# Put our lovely new alpha channel into animal image and save
animal.putalpha(mask)
animal.save('result.png')
mask.png
animal.png
Just for fun, here's another way of doing it leveraging Numpy:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
# Open the overlay and animal, discarding any alpha channel
overlay = Image.open('mask.png').convert('RGB')
animal = Image.open('animal.png').convert('RGB')
mask = overlay.convert('L')
# Make into Numpy arrays
overlay= np.array(overlay)
animal = np.array(animal)
mask = np.array(mask)
# Blend animal and overlay
blended = animal//2 + overlay//2
# Choose blended or original at each location depending on mask
res = np.where(mask[...,np.newaxis], blended, animal)
# Revert to PIL Image and save
Image.fromarray(res).save('result.png')
I have a picture with a white background in which there are red, green and blue lines. The red green and blue lines are exclusively of the following values: (255,0,0), (0,255,0) and (0,0,255). The image is now saved as .png and as .jpg. The code should work in both formats. The following code is to detect the colours and return the number of the respective colour pixels. As .png the code works, as .jpg only the green pixels are recognised, but unfortunately not all of them. How can all pixels of all 3 colours also be recognised with .jpg?
import cv2
import numpy as np
img = cv2.imread(r"...\red.jpg")
x,y,z = img.shape
print(x,y,z)
r = np.where((img == (0, 0, 255)).all(axis=2))
redarray = np.array(r)
red = np.size(redarray)
g = np.where((img == (0, 255, 0)).all(axis=2))
greenarray = np.array(g)
green = np.size(greenarray)
bl = np.where((img == (255, 0, 0)).all(axis=2))
bluearray = np.array(bl)
blue = np.size(bluearray)
print("red: ", red)
print("green: ", green)
print("blue: ", blue)
Without seeing the images, jpeg images are compressed and if not handled with care pixel values are not the same as the equivalent png image.
Two approaches:
Upon saving the jpeg you might try using a higher resolution/lower compression. It might work. It will also increase the file size.
Instead of searching for the exact values (for example red == (0, 0, 255)), look for pixels under some thresholds. For example define th=10 and look for red pixels in the range of: green <= 0+th & blue <= 0+th & red >= 255-th.
Sorry I don't have a running code under this answer, let me know if you need further assistance with the masking.
The png is lossless format while the jpg is not (even if you use quality of 100).
This is why part of your original "pure" pixels become "impure" after being saved and reread from jpeg format.
BTW, your question states:
The red green and blue lines are exclusively of the following values:
(255,0,0), (0,255,0) and (0,0,255)
but the opencv behaviour as well as your code is BGR. so red, for example is (0,0,255)
I have a huge dataset of images like this:
I would like to change the colors on these. All white should stay white, all purple should turn white and everything else should turn black. The desired output would look like this:
I've made the code underneath and it is doing what I want, but it takes way to long to go through the amount of pictures I have. Is there another and faster way of doing this?
path = r"C:path"
for f in os.listdir(path):
f_name = (os.path.join(path,f))
if f_name.endswith(".png"):
im = Image.open(f_name)
fn, fext = os.path.splitext(f_name)
print (fn)
im =im.convert("RGBA")
for x in range(im.size[0]):
for y in range(im.size[1]):
if im.getpixel((x, y)) == (255, 255, 255, 255):
im.putpixel((x, y),(255, 255, 255,255))
elif im.getpixel((x, y)) == (128, 64, 128, 255):
im.putpixel((x, y),(255, 255, 255,255))
else:
im.putpixel((x, y),(0, 0, 0,255))
im.show()
Your images seem to be palettised as they represent segmentations, or labelled classes and there are typically fewer than 256 classes. As such, each pixel is just a label (or class number) and the actual colours are looked up in a 256-element table, i.e. the palette.
Have a look here if you are unfamiliar with palletised images.
So, you don't need to iterate over all 12 million pixels, you can instead just iterate over the palette which is only 256 elements long...
#!/usr/bin/env python3
import sys
import numpy as np
from PIL import Image
# Load image
im = Image.open('image.png')
# Check it is palettised as expected
if im.mode != 'P':
sys.exit("ERROR: Was expecting a palettised image")
# Get palette and make into Numpy array of 256 entries of 3 RGB colours
palette = np.array(im.getpalette(),dtype=np.uint8).reshape((256,3))
# Name our colours for readability
purple = [128,64,128]
white = [255,255,255]
black = [0,0,0]
# Go through palette, setting purple to white
palette[np.all(palette==purple, axis=-1)] = white
# Go through palette, setting anything not white to black
palette[~np.all(palette==white, axis=-1)] = black
# Apply our modified palette and save
im.putpalette(palette.ravel().tolist())
im.save('result.png')
That takes 290ms including loading and saving the image.
If you have many thousands of images to do, and you are on a decent OS, you can use GNU Parallel. Change the above code to accept a command-line parameter which is the name of the image, and save it as recolour.py then use:
parallel ./recolour.py {} ::: *.png
It will keep all CPU cores on your CPU busy till they are all processed.
Keywords: Image processing, Python, Numpy, PIL, Pillow, palette, getpalette, putpalette, classes, classification, label, labels, labelled image.
If you're open to use NumPy, you can heavily speed-up pixel manipulations:
from PIL import Image
import numpy as np
# Open PIL image
im = Image.open('path/to/your/image.png').convert('RGBA')
# Convert to NumPy array
pixels = np.array(im)
# Get logical indices of all white and purple pixels
idx_white = (pixels == (255, 255, 255, 255)).all(axis=2)
idx_purple = (pixels == (128, 64, 128, 255)).all(axis=2)
# Generate black image; set alpha channel to 255
out = np.zeros(pixels.shape, np.uint8)
out[:, :, 3] = 255
# Set white and purple pixels to white
out[idx_white | idx_purple] = (255, 255, 255, 255)
# Convert back to PIL image
im = Image.fromarray(out)
That code generates the desired output, and takes around 1 second on my machine, whereas your loop code needs 33 seconds.
Hope that helps!
Hello I am trying to give a black and white photo a colored background, first I convert pixels to either white or black then I would like to replace the white with a specific color (148,105,39) then save it.
here is the image I am working on
here is my function so far it doesn't change the color (also doesn't give error)
def binary_image(img):
gray = img.convert('L')
bw = gray.point(lambda x: 0 if x<128 else 255, '1')
img = bw.convert('RGBA')
pixdata = img.load()
for y in range(img.size[1]):
for x in range(img.size[0]):
if pixdata[x, y] == (255, 255, 255, 255): #white color
pixdata[x, y] = (148,105,39,255)
return full_4
img=Image.open('logo.jpg')
bg_fps = binary_image(img)
bg_fps.show()
You really, really want to avoid for loops with PIL images, try to use Numpy and it will be easier to write and faster to run.
I would do it like this:
import numpy as np
from PIL import image
# Load image and ensure greyscale
im = Image.open('U6IhW.jpg').convert('L')
# Make Numpy version for fast, efficient access
npim = np.array(im)
# Make solid black RGB output image, same width and height but 3 channels (RGB)
res = np.zeros((npim.shape[0],npim.shape[1],3), dtype=np.uint8)
# Anywhere the grey image is >127, make result image new colour
res[npim>127] = [148,105,39]
# Convert back to PIL Image and save to disk
Image.fromarray(res).save('result.png')
You could equally use PIL's ImageOps.colorize() like this:
from PIL import Image, ImageOps
im = Image.open('U6IhW.jpg').convert('L')
# Threshold to pure black and white
bw = im.point(lambda x: 0 if x<128 else 255)
# Colorize
result = ImageOps.colorize(bw, (0,0,0), (148,105,39))
I was wondering how to change the alpha value of an image (.png or .jpg), only for multiple colours? I have done a some looking around and found some solutions with the PIL module, and I found a partial solution here:
Python: PIL replace a single RGBA color
(Code used from link provided)
import Image
import numpy
im = Image.open('test.png').convert('RGBA')
data = numpy.array(im)
r, g, b, a = data.T
colour_to_keep = (r == 255) & (b == 255) & (g == 255)
data[..., :-1][colour_to_keep.T] = (255, 0, 0)
im2 = Image.fromarray(data)
im2.show()
But after playing around with it, I don't think that it will do what I am attempting to do. This code will replace all except one colour.
I am attempting to set only one "background colour" (in this case, green) of a very simple image to transparent (with alpha = 0).
Can anyone please point me in a direction of how to do this? Thank you!