Put image on another image using python - python

Hello i'm using python to track objects in a video and i want to show an image on top of the object instead of a text.
The current line that im using to show text on top of the target box :
cv2.putText(img_current_frame ,"object name",(int(bbox[0]), int(bbox[1]-45)),0, 0.75, (0,0,0),2)

There's already an answer for this: overlay a smaller image on a larger image python OpenCv
In your case, you could wrap this functionality with the following:
def overlay_image(im1, im2, x_offset, y_offset):
'''Mutates im1, placing im2 over it at a given offset.'''
im1[y_offset:y_offset+im2.shape[0], x_offset:x_offset+im2.shape[1]] = im2
then call it:
im_over = cv2.imread("my_overlay_image.png")
overlay_image(img_current_frame, im_over, 10, 10)
(as per the referenced solution)

You might prefer to composite text with the PIL .text() method:
from PIL import Image, ImageDraw
img = Image.open("blank.png").convert("RGBA")
d = ImageDraw.Draw(img)
d.text((10, 10), "Hello world!")
img.show()
Or if you have pixels, then use alpha_composite().
If you stick with cv2,
then addWeighted() will help you blend in a logo image with alpha set to taste.
Or you can just use the matrix addition operator:
output = img_current_frame + logo

Related

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)

Python add noise to image breaks PNG

I'm trying to create a image system in Python 3 to be used in a web app. The idea is to load an image from disk and add some random noise to it. When I try this, I get what looks like a totally random image, not resembling the original:
import cv2
import numpy as np
from skimage.util import random_noise
from random import randint
from pathlib import Path
from PIL import Image
import io
image_files = [
{
'name': 'test1',
'file': 'test1.png'
},
{
'name': 'test2',
'file': 'test2.png'
}
]
def gen_image():
rand_image = randint(0, len(image_files)-1)
image_file = image_files[rand_image]['file']
image_name = image_files[rand_image]['name']
image_path = str(Path().absolute())+'/img/'+image_file
img = cv2.imread(image_path)
noise_img = random_noise(img, mode='s&p', amount=0.1)
img = Image.fromarray(noise_img, 'RGB')
fp = io.BytesIO()
img.save(fp, format="PNG")
content = fp.getvalue()
return content
gen_image()
I have also tried using pypng:
import png
# Added the following to gen_image()
content = png.from_array(noise_img, mode='L;1')
content.save('image.png')
How can I load a png (With alpha transparency) from disk, add some noise to it, and return it so that it can be displayed by web server code (flask, aiohttp, etc).
As indicated in the answer by makayla, this makes it better: noise_img = (noise_img*255).astype(np.uint8) but the colors are still wrong and there's no transparency.
Here's the updated function for that:
def gen_image():
rand_image = randint(0, len(image_files)-1)
image_file = image_files[rand_image]['file']
image_name = image_files[rand_image]['name']
image_path = str(Path().absolute())+'/img/'+image_file
img = cv2.imread(image_path)
cv2.imshow('dst_rt', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Problem exists somewhere below this line.
img = random_noise(img, mode='s&p', amount=0.1)
img = (img*255).astype(np.uint8)
img = Image.fromarray(img, 'RGB')
fp = io.BytesIO()
img.save(fp, format="png")
content = fp.getvalue()
return content
This will popup a pre-noise image and return the noised image. RGB (And alpha) problem exists in returned image.
I think the problem is it needs to be RGBA but when I change to that, I get ValueError: buffer is not large enough
Given all the new information I am updating my answer with a few more tips for debugging the issue.
I found a site here which creates sample transparent images. I created a 64x64 cyan (R=0, G=255, B=255) image with a transparency layer of 0.5. I used this to test your code.
I read in the image two ways to compare: im1 = cv2.imread(fileName) and im2 = cv2.imread(fileName,cv2.IMREAD_UNCHANGED). np.shape(im1) returned (64,64,3) and np.shape(im2) returned (64,64,4). This is why that flag is required--the default imread settings in opencv will read in a transparent image as a normal RGB image.
However opencv reads in as BGR instead of RGB, and since you cannot save out with opencv, you'll need to convert it to the correct order otherwise the image will have reversed color. For example, my cyan image, when viewed with the reversed color appears like this:
You can change this using openCV's color conversion function like this im = cv2.cvtColor(im, cv2.COLOR_BGRA2RGBA) (Here is a list of all the color conversion codes). Again, double check the size of your image if you need to, it should still have four channels since you converted it to RGBA.
You can now add your noise to your image. Just so you know, this is also going to add noise to your alpha channel as well, randomly making some pixels more transparent and others less transparent. The random_noise function from skimage converts your image to float and returns it as float. This means the image values, normally integers ranging from 0 to 255, are converted to decimal values from 0 to 1. Your line img = Image.fromarray(noise_img, 'RGB') does not know what to do with the floating point noise_img. That's why the image is all messed up when you save it, as well as when I tried to show it.
So I took my cyan image, added noise, and then converted the floats back to 8 bits.
noise_img = random_noise(im, mode='s&p', amount=0.1)
noise_img = (noise_img*255).astype(np.uint8)
img = Image.fromarray(noise_img, 'RGBA')
It now looks like this (screenshot) using img.show():
I used the PIL library to save out my image instead of openCV so it's as close to your code as possible.
fp = 'saved_im.png'
img.save(fp, format="png")
I loaded the image into powerpoint to double-check that it preserved the transparency when I saved it using this method. Here is a screenshot of the saved image overlaid on a red circle in powerpoint:

Resizing JPG using PIL.resize gives a completely black image

I'm using PIL to resize a JPG. I'm expecting the same image, resized as output, but instead I get a correctly sized black box. The new image file is completely devoid of any information, just an empty file. Here is an excerpt for my script:
basewidth = 300
img = Image.open(path_to_image)
wpercent = (basewidth/float(img.size[0]))
hsize = int((float(img.size[1])*float(wpercent)))
img = img.resize((basewidth,hsize))
img.save(dir + "/the_image.jpg")
I've tried resizing with Image.LANCZOS as the second argument, (defaults to Image.NEAREST with 1 argument), but it didn't make a difference. I'm running Python3 on Ubunutu 16.04. Any ideas on why the image file is empty?
I also encountered the same issue when trying to resize an image with transparent background. The "resize" works after I add a white background to the image.
Code to add a white background then resize the image:
from PIL import Image
im = Image.open("path/to/img")
if im.mode == 'RGBA':
alpha = im.split()[3]
bgmask = alpha.point(lambda x: 255-x)
im = im.convert('RGB')
im.paste((255,255,255), None, bgmask)
im = im.resize((new_width, new_height), Image.ANTIALIAS)
ref:
Other's code for making thumbnail
Python: Image resizing: keep proportion - add white background
The simplest way to get to the bottom of this is to post your image! Failing that, we can check the various aspects of your image.
So, import Numpy and PIL, open your image and convert it to a Numpy ndarray, you can then inspect its characteristics:
import numpy as np
from PIL import Image
# Open image
img = Image.open('unhappy.jpg')
# Convert to Numpy Array
n = np.array(img)
Now you can print and inspect the following things:
n.shape # we are expecting something like (1580, 1725, 3)
n.dtype # we expect dtype('uint8')
n.max() # if there's white in the image, we expect 255
n.min() # if there's black in the image, we expect 0
n.mean() # we expect some value between 50-200 for most images

Python PIL 0.5 opacity, transparency, alpha

Is there any way to make an image half transparent?
the pseudo code is something like this:
from PIL import Image
image = Image.open('image.png')
image = alpha(image, 0.5)
I googled it for a couple of hours but I can't find anything useful.
I realize this question is really old, but with the current version of Pillow (v4.2.1), there is a function called putalpha. It seems to work fine for me. I don't know if will work for every situation where you need to change the alpha, but it does work. It sets the alpha value for every pixel in the image. It seems, though that you can use a mask: http://www.leancrew.com/all-this/2013/11/transparency-with-pil/.
Use putalpha like this:
from PIL import Image
img = Image.open(image)
img.putalpha(127) # Half alpha; alpha argument must be an int
img.save(dest)
Could you do something like this?
from PIL import Image
image = Image.open('image.png') #open image
image = image.convert("RGBA") #convert to RGBA
rgb = image.getpixel(x,y) #Get the rgba value at coordinates x,y
rgb[3] = int(rgb[3] / 2) or you could do rgb[3] = 50 maybe? #set alpha to half somehow
image.putpixel((x,y), rgb) #put back the modified reba values at same pixel coordinates
Definitely not the most efficient way of doing things but it might work. I wrote the code in browser so it might not be error free but hopefully it can give you an idea.
EDIT: Just noticed how old this question was. Leaving answer anyways for future help. :)
I put together Pecan's answer and cr333's question from this question:
Using PIL to make all white pixels transparent?
... and came up with this:
from PIL import Image
opacity_level = 170 # Opaque is 255, input between 0-255
img = Image.open('img1.png')
img = img.convert("RGBA")
datas = img.getdata()
newData = []
for item in datas:
newData.append((0, 0, 0, opacity_level))
else:
newData.append(item)
img.putdata(newData)
img.save("img2.png", "PNG")
In my case, I have text with black background and wanted only the background semi-transparent, in which case:
from PIL import Image
opacity_level = 170 # Opaque is 255, input between 0-255
img = Image.open('img1.png')
img = img.convert("RGBA")
datas = img.getdata()
newData = []
for item in datas:
if item[0] == 0 and item[1] == 0 and item[2] == 0:
newData.append((0, 0, 0, opacity_level))
else:
newData.append(item)
img.putdata(newData)
img.save("img2.png", "PNG")
I had an issue, where black boxes were appearing around my image when applying putalpha().
This workaround (applying alpha in a copied layer) solved it for me.
from PIL import Image
with Image.open("file.png") as im:
im2 = im.copy()
im2.putalpha(180)
im.paste(im2, im)
im.save("file2.png")
Explanation:
Like I said, putalpha modifies all pixels by setting their alpha value, so fully transparent pixels become only partially transparent. The code I posted above first sets (putalpha) all pixels to semi-transparent in a copy, then copies (paste) all pixels to the original image using the original alpha values as a mask. This means that fully transparent pixels in the original image are skipped during the paste.
Credit: https://github.com/nulano # https://github.com/python-pillow/Pillow/issues/4687#issuecomment-643567573
I just did this by myself...even though my code maybe a little bit weird...But it works fine. So I share it here. Hopes it could help anybody. =)
The idea: To transparent a pic means lower alpha which is the 4th element in the tuple.
my frame code:
from PIL import Image
img=open(image)
img=img.convert('RGBA') #you can make sure your pic is in the right mode by check img.mode
data=img.getdata() #you'll get a list of tuples
newData=[]
for a in data:
a=a[:3] #you'll get your tuple shorten to RGB
a=a+(100,) #change the 100 to any transparency number you like between (0,255)
newData.append(a)
img.putdata(newData) #you'll get your new img ready
img.save(filename.filetype)
I didn't find the right command to fulfil this job automatically, so I write this by myself. Hopes it'll help again. XD
This method helps to reduce opacity of logo with transparency before pasting it over image
# pip install Pillow
# PIL.__version__ is 9.3.0
from PIL import Image, ImageEnhance
im = Image.open('logo.png').convert('RGBA')
alpha = im.split()[3]
alpha = ImageEnhance.Brightness(alpha).enhance(.5)
im.putalpha(alpha)

Python Image Library - Create and paste image

How do I create a new image with a black background and paste another image on top of it?
What I am looking to do is turn some 128x128 transparent icons into 75x75 black background icons.
Doesnt work ...
import Image
theFile = "/home/xxxxxx/Pictures/xxxxxx_128.png"
img = Image.open(theFile)
newImage = Image.new(img.mode, img.size, "black")
newImage.paste(img)
newImage.resize((75,75))
newImage.save("out.png")
print "Done"
Thanks!
The resize method returns a new image object, rather than changing the existing one. Also, you should resize the image before pasting it. The following works for me:
import Image
theFile = "foo.png"
img = Image.open(theFile)
resized = img.resize((75,75))
r, g, b, alpha = resized.split()
newImage = Image.new(resized.mode, resized.size, "black")
newImage.paste(resized, mask=alpha)
newImage.save("out.png")
print "Done"
I found an example of this split + mask technique from this blog post.
Example input:
Output:

Categories