Image overlay certain pixels - python

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')

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')

Unique Color Detection and Storing images dynamically

If an image is given , find out the unique colors in that image and write output images corresponding to each unique color.
In that all other pixels which don't have that unique color should me marked white.
for eg , if an image has 3 colors - in the output folder there should be three images where each color is separated. Using Open CV & Python.
I've created the unique color list using my methods. What I want is to give a count of all those unique colors in the sample.png image and give the corresponding images output as per the question.
I believe the code below (with comments) should help you with this!
Feel free to follow up if any of the code is unclear!
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from copy import deepcopy
# Load image and convert it from BGR (opencv default) to RGB
fpath = "dog.png" # TODO: replace with your path
IMG = cv.cvtColor(cv.imread(fpath), cv.COLOR_BGR2RGB)
# Get dimensions and reshape into (H * W, C) vector - i.e. a long vector, where each element is a tuple corresponding to a color!
H, W, C = IMG.shape
IMG_FLATTENED = np.vstack([IMG[:, w, :] for w in range(W)])
# Get unique colors using np.unique function, and their counts
colors, counts = np.unique(IMG_FLATTENED, axis=0, return_counts = True)
# Jointly loop through colors and counts
for color, count in zip(colors, counts):
print("COLOR: {}, COUNT: {}".format(color, count))
# Create placeholder image and mark all pixels as white
SINGLE_COLOR = (255 * np.ones(IMG.shape)).astype(np.uint8) # Make sure casted to uint8
# Compute binary mask of pixel locations where color is, and set color in new image
color_idx = np.all(IMG[..., :] == color, axis=-1)
SINGLE_COLOR[color_idx, :] = color
# Write file to output with color and counts specified
cv.imwrite("color={}_count={}.png".format(color, count), SINGLE_COLOR)
Ack, he beat me to it. Well, here's what I've got.
Oh no, I don't think the line
blank[img == color] = img[img == color]
behaves how I think it does. I think it just coincidentally works for this case. I'll edit the code with a solution I'm more confident works for all cases.
Original Image
import cv2
import numpy as np
# load image
img = cv2.imread("circles.png");
# get uniques
unique_colors, counts = np.unique(img.reshape(-1, img.shape[-1]), axis=0, return_counts=True);
# split off each color
splits = [];
for a in range(len(unique_colors)):
# get the color
color = unique_colors[a];
blank = np.zeros_like(img);
mask = cv2.inRange(img, color, color); # edited line 1
blank[mask == 255] = img[mask == color]; # edited line 2
# show
cv2.imshow("Blank", blank);
cv2.waitKey(0);
# save each color with its count
file_str = "";
for b in range(3):
file_str += str(color[b]) + "_";
file_str += str(counts[a]) + ".png";
cv2.imwrite(file_str, blank);

Convert greenish to redish

I have images of the stanrd 52 game cards. Some of them are black and some are red. A neural network has been trained on it to recognize them correctly. Now it turns out that sometimes green is used instead of red. That's why I want to convert all images that are green(ish) to red(ish). If they are blackish or redish they should not be changed much or at all if possible.
What's the best way to achieve this?
The easiest way to do this, IMHO, is to split the image into its constituent R, G and B channels and then recombine them in the "wrong" order:
#!/usr/bin/env python3
from PIL import Image
# Open image
im = Image.open('cards.jpg')
# Split into component channels
R, G, B = im.split()
# Recombine, but swapping order of red and green
result = Image.merge('RGB',[G,R,B])
# Save result
result.save('result.jpg')
Alternatively, you can do the same thing via a colour matrix multiplication:
#!/usr/bin/env python3
from PIL import Image
# Open image
im = Image.open('cards.jpg')
# Define color matrix to swap the green and red channels
# This says:
# New red = 0*old red + 1*old green + 0*old blue + 0offset
# New green = 1*old red + 0*old green + 0*old blue + 0offset
# New blue = 1*old red + 0*old green + 1*old blue + 0offset
Matrix = ( 0, 1, 0, 0,
1, 0, 0, 0,
0, 0, 1, 0)
# Apply matrix
result = im.convert("RGB", Matrix)
result.save('result.jpg')
Alternatively, you could use ImageMagick in Terminal to separate the image into its constituent R,G and B channels, swap the Red and Green channels and recombine like this:
magick cards.jpg -separate -swap 0,1 -combine result.png
Alternatively, you could use ImageMagick to perform a "hue rotation". Basically, you convert the image to HSL colourspace and rotate the hue leaving the saturation and lightness unaffected. That gives you the flexibility to make almost any colour you desire. You can do that in the Terminal with:
magick cards.jpg -modulate 100,100,200 result.jpg
where the 200 above is the interesting parameter - see documentation here. Here is an animation of the various possibilities:
If still using v6 ImageMagick, replace magick with convert in all commands.
Keywords: Python, image processing, hue rotation, channel swap, cards, prime, swap channels, PIL, Pillow, ImageMagick.
Here's one approach using a tolerance value to decide on the -ish factor to set a mathematical notation to it -
def set_image(a, tol=100): #tol - tolerance to decides on the "-ish" factor
# define colors to be worked upon
colors = np.array([[255,0,0],[0,255,0],[0,0,0],[255,255,255]])
# Mask of all elements that are closest to one of the colors
mask0 = np.isclose(a, colors[:,None,None,:], atol=tol).all(-1)
# Select the valid elements for edit. Sets all nearish colors to exact ones
out = np.where(mask0.any(0)[...,None], colors[mask0.argmax(0)], a)
# Finally set all green to red
out[(out == colors[1]).all(-1)] = colors[0]
return out.astype(np.uint8)
A more memory-efficient approach would be to loop through those selective colors, like so -
def set_image_v2(a, tol=100): #tol - tolerance to decides on the "-ish" factor
# define colors to be worked upon
colors = np.array([[255,0,0],[0,255,0],[0,0,0],[255,255,255]])
out = a.copy()
for c in colors:
out[np.isclose(out, c, atol=tol).all(-1)] = c
# Finally set all green to red
out[(out == colors[1]).all(-1)] = colors[0]
return out
Sample run -
Input image :
from PIL import Image
img = Image.open('green.png').convert('RGB')
x = np.array(img)
y = set_image(x)
z = Image.fromarray(y, 'RGB')
z.save("tmp.png")
Output -

Applying a coloured overlay to an image in either PIL or Imagemagik

I am a complete novice to image processing, and I am guessing this is quite easy to do, but I just don't know the terminology.
Basically, I have a black and white image, I simply want to apply a colored overlay to the image, so that I have got the image overlayed with blue green red and yellow like the images shown below (which actually I can't show because I don't have enough reputation to do so - grrrrrr). Imagine I have a physical image, and a green/red/blue/yellow overlay, which I place on top of the image.
Ideally, I would like to do this using Python PIL but I would be just as happy to do it using ImageMagik, but either way, I need to be able to script the process as I have 100 or so images that I need to carry out the process on.
EDIT: As mentioned by Matt in the comments, this functionality is now available in skimage.color.label2rgb.
In the latest development version, we've also introduced a saturation parameter, which allows you to add overlays to color images.
Here's a code snippet that shows how to use scikit-image to overlay colors on a grey-level image. The idea is to convert both images to the HSV color space, and then to replace the hue and saturation values of the grey-level image with those of the color mask.
from skimage import data, color, io, img_as_float
import numpy as np
import matplotlib.pyplot as plt
alpha = 0.6
img = img_as_float(data.camera())
rows, cols = img.shape
# Construct a colour image to superimpose
color_mask = np.zeros((rows, cols, 3))
color_mask[30:140, 30:140] = [1, 0, 0] # Red block
color_mask[170:270, 40:120] = [0, 1, 0] # Green block
color_mask[200:350, 200:350] = [0, 0, 1] # Blue block
# Construct RGB version of grey-level image
img_color = np.dstack((img, img, img))
# Convert the input image and color mask to Hue Saturation Value (HSV)
# colorspace
img_hsv = color.rgb2hsv(img_color)
color_mask_hsv = color.rgb2hsv(color_mask)
# Replace the hue and saturation of the original image
# with that of the color mask
img_hsv[..., 0] = color_mask_hsv[..., 0]
img_hsv[..., 1] = color_mask_hsv[..., 1] * alpha
img_masked = color.hsv2rgb(img_hsv)
# Display the output
f, (ax0, ax1, ax2) = plt.subplots(1, 3,
subplot_kw={'xticks': [], 'yticks': []})
ax0.imshow(img, cmap=plt.cm.gray)
ax1.imshow(color_mask)
ax2.imshow(img_masked)
plt.show()
Here's the output:
I ended up finding an answer to this using PIL, basically creating a new image with a block colour, and then compositing the original image, with this new image, using a mask that defines a transparent alpha layer. Code below (adapted to convert every image in a folder called data, outputting into a folder called output):
from PIL import Image
import os
dataFiles = os.listdir('data/')
for filename in dataFiles:
#strip off the file extension
name = os.path.splitext(filename)[0]
bw = Image.open('data/%s' %(filename,))
#create the coloured overlays
red = Image.new('RGB',bw.size,(255,0,0))
green = Image.new('RGB',bw.size,(0,255,0))
blue = Image.new('RGB',bw.size,(0,0,255))
yellow = Image.new('RGB',bw.size,(255,255,0))
#create a mask using RGBA to define an alpha channel to make the overlay transparent
mask = Image.new('RGBA',bw.size,(0,0,0,123))
Image.composite(bw,red,mask).convert('RGB').save('output/%sr.bmp' % (name,))
Image.composite(bw,green,mask).convert('RGB').save('output/%sg.bmp' % (name,))
Image.composite(bw,blue,mask).convert('RGB').save('output/%sb.bmp' % (name,))
Image.composite(bw,yellow,mask).convert('RGB').save('output/%sy.bmp' % (name,))
Can't post the output images unfortunately due to lack of rep.
See my gist https://gist.github.com/Puriney/8f89b43d96ddcaf0f560150d2ff8297e
Core function via opencv is described as below.
def mask_color_img(img, mask, color=[0, 255, 255], alpha=0.3):
'''
img: cv2 image
mask: bool or np.where
color: BGR triplet [_, _, _]. Default: [0, 255, 255] is yellow.
alpha: float [0, 1].
Ref: http://www.pyimagesearch.com/2016/03/07/transparent-overlays-with-opencv/
'''
out = img.copy()
img_layer = img.copy()
img_layer[mask] = color
out = cv2.addWeighted(img_layer, alpha, out, 1 - alpha, 0, out)
return(out)
Add colored and transparent overlay on either RGB or gray image can work:

Python: PIL replace a single RGBA color

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.

Categories