How to overlay only the needed image? - python

I have two images.
Image 1:
Image 2:
Both the images have the same resolution. I want to overlay the second image on the first image.
This is what I tried:
from PIL import Image
img1 = Image.open("D:\\obj1__0.png")
img1 = img1.convert("RGBA")
img1 = img1.resize((640,360))
img2 = Image.open('D:\\Renderforest_Watermark.png')
img2 = img2.convert("RGBA")
img2 = img2.resize((640,360))
new_img = Image.blend(img1,img2,0.7)
new_img.save("D:\\Final.png")
Output:
My Question: How do I overlay image 2 on image 1 such that only the watermark is overlaid?
P.S: I already searched for this on Stack Overflow, but couldn't find any answers.

Your overlay image is incorrectly formed. It should be transparent where it is white. It has no transparency, so it isn't see-through. I can only suggest to make a synthetic alpha channel by guessing that it should be transparent where the overlay image is white:
#!/usr/bin/env python3
from PIL import Image
# Set a common size
size = (640, 360)
# Load background and overlay, removing the pointless alpha channel and resizing to a common size
bg = Image.open('background.png').convert('RGB').resize(size)
overlay = Image.open('overlay.png').convert('RGB').resize(size)
# Try and invent a mask by making white pixels transparent
mask = overlay.convert('L')
mask = mask.point(lambda p: 255 if p < 225 else 0)
# Paste overlay onto background only where the mask is, then save
bg.paste(overlay, None, mask)
bg.save('result.png')
If your image did have an alpha channel, you would avoid deleting the original alpha channel and open it like this instead:
overlay = Image.open('overlay.png').resize(size)
then delete these lines:
# Try and invent a mask by making white pixels transparent
mask = overlay.convert('L')
mask = mask.point(lambda p: 255 if p < 225 else 0)
then change the line after the above to:
# Paste overlay onto background only where the mask is, then save
bg.paste(overlay, None, overlay)
Keywords: Image processing, PIL, Pillow, overlay, watermark, transparent, alpha.

Related

Python - replicating the GIMP's "Erase color" blend mode

I'm looking for a way to recreate the GIMP's Erase color blending mode in Python 3 & OpenCV2.
I know it's possible to erase color using the that library, but the code I run works on exactly one of them. Furthermore, I don't believe such small amount of code could do that advanced thing.
Looking for a solution, I found the blend-modes by flrs, but it also doesn't include the option I want.
Sadly, I have no experience in OpenCV2 at the moment, but I think developing such thing could be very helpful.
Can someone guide me how to make this more reliable, or is it even possible to do with things that I've got already?
OpenCV2 color removal
Code
import cv2
from PIL import Image
#-=-=-=-#
File_Name = r"Spectrogram.png"
SRC = cv2.imread(File_Name, 1)
TMP = cv2.cvtColor(SRC, cv2.COLOR_BGR2GRAY)
_, A = cv2.threshold(TMP, 0, 255, cv2.THRESH_BINARY)
B, G, R = cv2.split(SRC)
Colors = [B, G, R, A]
Picture = cv2.merge(Colors, 4)
#-=-=-=-#
# My CV2 image display doesn't include transparency
im = cv2.cvtColor(Picture, cv2.COLOR_BGR2RGB)
im = Image.fromarray(im)
im.show()
Result
Original
Result
GIMP Erase color blending-mode
Type
Background
Foreground
Result
Image
Blending
Normal
Erase color
Normal
Here is one simple way in Python/OpenCV.
Read the input
Choose a color range
Apply range to threshold the image
Invert the range as a mask to be used later for the alpha channel
Convert the image from BGR to BGRA
Put mask into the alpha channel of the BGRA image
Save the result
Input:
import cv2
import numpy as np
# load image and set the bounds
img = cv2.imread("red_black.png")
# choose color range
lower =(0,0,0) # lower bound for each BGR channel
upper = (140,0,190) # upper bound for each BRG channel
# create the mask
mask = cv2.inRange(img, lower, upper)
# invert mask
mask = 255 - mask
# convert image to BGRA
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# put mask into alpha channel
result[:,:,3] = mask
# write result to disk
cv2.imwrite("red_black_color_removed.png", result)
# display it (though does not display transparency properly)
cv2.imshow("mask", mask)
cv2.imshow("results", result)
cv2.waitKey(0)
Result:

Remove border from Logo using Python PIL

Hi I want to remove the white border from this Logo using python's pillow library. The only way I have thought of is to extract all white from the image but then this remove's the white eye of the horse as well and that is something I would like to keep.
What I have.
https://i.stack.imgur.com/DX2LE.png
What I want.
https://i.stack.imgur.com/IPVqi.png
This isn't a daunting task but there are a lot of logo's that I need to do this for so I would like an automated fashion of doing so. Here is some code to extract the image from the source. Thanks for any help anyone can provide.
from PIL import Image
import pandas as pd
import requests
filename = "https://a.espncdn.com/combiner/i?img=/i/teamlogos/ncaa/500/68.png"
image = Image.open(requests.get(filename, stream=True).raw)
I prefer using OpenCV but we can do it with PIL.
Replace white with transparency using the example from here
Get the alpha channel.
Use ImageDraw.floodfill for filling the surrounding zero alpha with 255 color.
(Only the eye stays black).
Invert alpha - make the eye white instead of black.
Paste the white eye on the image with the "transparent white".
Code sample (reading local image):
from PIL import Image, ImageDraw, ImageOps
import numpy as np
# https://stackoverflow.com/questions/765736/how-to-use-pil-to-make-all-white-pixels-transparent
def white_to_transparency(img):
x = np.asarray(img.convert('RGBA')).copy()
x[:, :, 3] &= (255 * (x[:, :, :3] != 255).any(axis=2)).astype(np.uint8) # Small modification: &= is used instead of = (in the original code).
return Image.fromarray(x)
filename = "68.png"
image = Image.open(filename)
im_white_transparent = white_to_transparency(image)
# https://stackoverflow.com/questions/63219166/advanced-cropping-with-python-imaging-library
# Extract alpha channel as new Image
alpha = im_white_transparent.getchannel('A')
# https://stackoverflow.com/questions/46083880/fill-in-a-hollow-shape-using-python-and-pillow-pil
# Fill alpha channel with 255, only the inner part stays 0
ImageDraw.floodfill(alpha, xy=(0, 0), value=255, thresh=200)
mask = ImageOps.invert(alpha) # Invert alpha - make the eye white instead of black
im_white_transparent.paste(image, (0, 0), mask) # Paste the white eye on the image with "transparent white".
# Show images for testing
im_white_transparent.show()
alpha.show()
mask.show()
Result:
im_white_transparent:
(change to dark mode for seeing the transparent background):
Same result with transparency as chessboard pattern:
mask:

ValueError: images do not match when blending pictures in PIL

I have been messing around in python to see if I could "mix" two pictures together. What I mean by that is so that the image is transparent and you can see two pictures together. If that still does not make sense check out this link: (only I would mix a picture and a picture not a gif)
https://cdn.discordapp.com/attachments/652564556211683363/662770085844221963/communism.gif
Here is my code:
from PIL import Image
im1 = Image.open('oip.jpg')
im2 = Image.open('star.jpg')
bg = Image.blend(im1, im2, 0)
bg.save('star_oip_paste.jpg', quality=95)
and I get the error:
line 6, in <module> bg = Image.blend(im1, im2, 0) ValueError: images do not match
I'm not even sure if I'm using the right function for "mixing" two images together — so if I'm not, let me know.
There are several things going on here:
Your input images are both JPEG which doesn't support transparency, so you can only get a fixed blending throughout your image. I mean you can't see one image at one point and the other image at another. You will only see the same proportion of each image at each point. Is that what you want?
For example, if I take Paddington and Buckingham Palace and take 50% of each:
I get this:
If that's what you want, you need to resize the images to a common size and change this line:
bg = Image.blend(im1, im2, 0)
to
bg = Image.blend(im1, im2, 0.5) # blend half and half
If you want to paste something with transparency, so it only shows up in certain places, you need to load the overlay from a GIF or PNG with transparency and use:
background.paste(overlay, box=None, mask=overlay)
Then you can do this - note you can see different amounts of the two images at each point:
So, as a concrete example of overlaying a transparent image onto an opaque background, and starting with Paddington (400x400) and this star (500x500):
#!/usr/bin/env python3
from PIL import Image
# Open background and foreground and ensure they are RGB (not palette)
bg = Image.open('paddington.png').convert('RGB')
fg = Image.open('star.png').convert('RGBA')
# Resize foreground down from 500x500 to 100x100
fg_resized = fg.resize((100,100))
# Overlay foreground onto background at top right corner, using transparency of foreground as mask
bg.paste(fg_resized,box=(300,0),mask=fg_resized)
# Save result
bg.save('result.png')
If you want to grab an image from a website, use this:
from PIL import Image
import requests
from io import BytesIO
# Grab the star image from this answer
response = requests.get('https://i.stack.imgur.com/wKQCT.png')
# Make it into a PIL image
img = Image.open(BytesIO(response.content))
As an alternative, you could try with OpenCV (depending on your desired output)
import cv2
# Read the images
foreground = cv2.imread("puppets.png")
background = cv2.imread("ocean.png")
alpha = cv2.imread("puppets_alpha.png")
# Convert uint8 to float
foreground = foreground.astype(float)
background = background.astype(float)
# Normalize the alpha mask to keep intensity between 0 and 1
alpha = alpha.astype(float)/255
# Multiply the foreground with the alpha matte
foreground = cv2.multiply(alpha, foreground)
# Multiply the background with ( 1 - alpha )
background = cv2.multiply(1.0 - alpha, background)
# Add the masked foreground and background.
outImage = cv2.add(foreground, background)
# Display image
cv2.imshow("outImg", outImage/255)
cv2.waitKey(0)

How do I add a transparent overlay to an image using pillow?

I'm trying to add an transparent overlay on an jpeg image. In the example below, the desired result would be a red image with a pieslice where the it is light red.
My input is an jpeg Image. Since jpeg doesn't have an alpha channel, I though I could convert it to an 'RGBA' image and paste the overlay on it:
from PIL import Image, ImageDraw
# img = Image.open('input.jpg').convert('RGBA')
img = Image.new('RGBA', (400,400), (255,0,0))
img2 = Image.new('RGBA', (400,400))
draw2 = ImageDraw.Draw(img2)
draw2.pieslice([0,0,400,400], 90, 180, fill='white')
img.putalpha(128)
img.save('img.png')
img2.save('img2.png')
img.paste(img2)
img.save('img1+2.png')
However, this doesn't have the desired effect, and windows Photos cannot even open it correctly.
I saw the blend and alpha_composite functions but it doesn't have the desired effect for me. I don't want to lower the alpha values of the background outside of the overlay.

How to add a mask on an image blurring their intersection?

I have a mask and a background image. I want to add this mask on the background image and blurring (or perhaps something like cv2.inpaint()) the intersection to make it more natural, but am blocked for the bluring effect, any help would be highly appreciated.
More details:
I have two RGB images. The first image (foreground) is associated with a binary mask, which I would like to add on the second image (background).
The issue is that when we look at the final image we clearly see which part was added on the background image. Hence, I would like to add a blurring effect at the intersection of the mask and the background image. For now my code looks like:
#foreground image: we'll use only the mask part
#background image: where we will add the mask
foreground = cv2.imread(path1)
background = cv2.imread(path2)
#Convert to float
foreground = foreground.astype(float)
background = background.astype(float)
mask = mask.astype(float)
#Multiply the foreground with the mask
foreground = cv2.multiply(mask, foreground)
#Multiply the background with everywhere except with mask
background = cv2.multiply(1.0 - mask, background)
#Add the masked foreground to background image
outImage = cv2.add(foreground, background)
I could not find a straightforward way to do it, but I guess their should have one. A lot of related answer on internet works by thresholding to some pixel value, but this can not be used here. For now the easiest way I found is:
create a mask of the part I want to blurr
blurr the final images (background +foreground mask)
take from the blurr images only the part of the mask of 1) and add it the initial final image (background +foreground mask)
Before doing this, I was wondering if someone would have some advices.
The way I am doing it so far works well, but I am staying open to any other better solution!
Here's mine:
def foreground_background_into1(background, foreground, with_smooth=True, thickness=3, mask=[]):
'''will add the foreground image to the background image to create a new image, with smooth intersection
-foreground image: this image must be either black where there is no mask, or the mask parameter must be specified in
the mask parameter
-background image: image on which we will add the mask
-mask: binary mask if foreground image does not already contain this information
Note: not tested with mask'''
#make a copy for the smooth part
img = foreground.copy()
#create binary mask (as needed to multiply with the backgound) from foreground image if not existing (replacing all value
#bigger than 0 to 1)
if len(mask)==0:
_,mask = cv2.threshold(foreground,1,1,cv2.THRESH_BINARY)
#verification
if foreground.shape!=background.shape:
raise Warning("the foreground is not of same shape as background, we will convert it")
foreground = imresize(foreground, size=background.shape)
#if mask has one channel add two others
if len(mask.shape)==2:
mask = skimage.color.gray2rgb(mask)
#add foreground to background
foreground = foreground.astype(float)
background = background.astype(float)
mask = mask.astype(float)
foreground = cv2.multiply(mask, foreground)
background = cv2.multiply(1 - mask, background) #is the initial background with black where the mask of forground
outImage = cv2.add(foreground, background)
result = outImage.astype(np.uint8)
if with_smooth:
#find contour
foreground_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(foreground_gray,1,255,0)
_, contours, __ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#create intersection_mask
intersection_mask = cv2.drawContours(np.zeros(foreground.shape, np.uint8),
contours,-1,(0,255,0),thickness)
#inpaint the contour in the first final image
intersection_mask = cv2.cvtColor(intersection_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1,1))
intersection_mask = cv2.dilate(intersection_mask, kernel, iterations=1)
result = cv2.inpaint(result,intersection_mask,1,cv2.INPAINT_TELEA)
return(result)
I am basically adding the mask on the background image, and then smooth the intersection part by inpaint with openCV the contour of the mask.

Categories