I need to alpha-blend 2 images that are not the same size. I've managed to get them to composite by resizing to the same size, so I've got part of the logic:
import cv2 as cv
def combine_two_color_images_composited(foreground_image, background_image):
foreground = cv.resize(foreground_image, (400,400), interpolation=cv.INTER_CUBIC).copy()
background = cv.resize(background_image, (400,400), interpolation=cv.INTER_CUBIC).copy()
alpha =0.5
# do composite of foreground onto the background
cv.addWeighted(foreground, alpha, background, 1 - alpha, 0, background)
cv.imshow('composited image', background)
cv.waitKey(10000)
I'm wondering if I need to make a mask that is the same size as the larger image and then use that with my first image. If so, I don't know how to do masking yet in OpenCV.... this is but a tiny portion of my project so it's not something I've been able to spend a ton of time researching to learn how masking works.
I have searched all over but the code I'm finding does things like 'adds' the images together (side by side).
To combine the two images you can make use of numpy slicing to select the portion of the background image where you want to blend the foreground, then insert the newly blended portion in your background again.
import cv
def combine_two_color_images(image1, image2):
foreground, background = image1.copy(), image2.copy()
foreground_height = foreground.shape[0]
foreground_width = foreground.shape[1]
alpha =0.5
# do composite on the upper-left corner of the background image.
blended_portion = cv.addWeighted(foreground,
alpha,
background[:foreground_height,:foreground_width,:],
1 - alpha,
0,
background)
background[:foreground_height,:foreground_width,:] = blended_portion
cv.imshow('composited image', background)
cv.waitKey(10000)
edit:
To place the foreground at a specified location you use numpy indexing as before. Numpy indexing is very powerful and you will find it useful on many occasions. I linked the documentation above. Is really worth to take a look at.
def combine_two_color_images_with_anchor(image1, image2, anchor_y, anchor_x):
foreground, background = image1.copy(), image2.copy()
# Check if the foreground is inbound with the new coordinates and raise an error if out of bounds
background_height = background.shape[0]
background_width = background.shape[1]
foreground_height = foreground.shape[0]
foreground_width = foreground.shape[1]
if foreground_height+anchor_y > background_height or foreground_width+anchor_x > background_width:
raise ValueError("The foreground image exceeds the background boundaries at this location")
alpha =0.5
# do composite at specified location
start_y = anchor_y
start_x = anchor_x
end_y = anchor_y+foreground_height
end_x = anchor_x+foreground_width
blended_portion = cv.addWeighted(foreground,
alpha,
background[start_y:end_y, start_x:end_x,:],
1 - alpha,
0,
background)
background[start_y:end_y, start_x:end_x,:] = blended_portion
cv.imshow('composited image', background)
cv.waitKey(10000)
Related
Let say that I have a 2 pictures that are transparent. How can I overlay picture 1 to picture 2 so to get a result picture. Picture 1 is also smaller that picture 2. I assume that it can be done with opencv or PIL (GIMP is not allowed)
Picture number 1 :
And picture number 2 :
[
That's the result that I want to get:
My approach : I thought that I can be done with opencv function cv.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) what I also tried to perform.
But as the matter in fact major problems are next points:
pictures are transparent and the task is to leave pictures with higher resolution.
Rely on your answers.
As a matter in fact, I find out the solution that suits for me. Perhaps It's not an optimal solution but at least It works.
The main idea is that I merge(paste) 2 pictures into 1 result using PIL library and the method paste, so that I have background and foreground.
In my case I have Picture 1 - foreground and Picture 2 - background.
My code :
# This function fit picture 2(background) for picture 1 . It's pare
def transform (template):
img = Image.open(template)
img_w, img_h = img.size
image = img.resize((img_w+coef_w,img_h+coef_h),Image.ANTIALIAS)
# change coef_w and coef_h to resize to fit template
image.save('updated_picture.png',"PNG")
# This function merge,compose, concat) pictures
def compose (image, template):
img = Image.open(image)
img_w, img_h = img.size
print(img.size)
background = Image.open(template)
print(background.size)
# resize the image
size = background.size
background = background.resize(size, Image.ANTIALIAS)
bg_w, bg_h = background.size
offset = ((bg_w - img_w) // 2, (bg_h - img_h) // 2)
background.paste(img, offset, mask=img)
background.save(f'{image}_final.png', "PNG")
How it's work, I transform template(ears) so they would lay on the picture at center and the next one script offset foreground by center so I can get next result.
Transform is needed to make template smaller or bigger. The results would depend on of it's template. I mean the scale of ears in my case. At the next picture you can see what I mean
import numpy as np
from imageio import imread, imwrite
im1 = imread('https://api.sofascore.app/api/v1/team/2697/image')[...,:3]
im2 = imread('https://api.sofascore.app/api/v1/team/2692/image')[...,:3]
result = np.hstack((im1,im2))
imwrite('result.jpg', result)
Original images opening directly from the url's (I'm trying to concatenate the two images into one and keep the background white):
As can be seen both have no background, but when joining the two via Python, the defined background becomes this moss green:
I tried modifying the color reception:
im1 = imread('https://api.sofascore.app/api/v1/team/2697/image')[...,:1]
im2 = imread('https://api.sofascore.app/api/v1/team/2692/image')[...,:1]
But the result is a Black & White with the background still looking like it was converted from the previous green, even though the PNG's don't have such a background color.
How should I proceed to solve my need?
There is a 4th channel in your images - transparency. You are discarding that channel with [...,:1]. This is a mistake.
If you retain the alpha channel this will work fine:
import numpy as np
from imageio import imread, imwrite
im1 = imread('https://api.sofascore.app/api/v1/team/2697/image')
im2 = imread('https://api.sofascore.app/api/v1/team/2692/image')
result = np.hstack((im1,im2))
imwrite('result.png', result)
However, if you try to make a jpg, you will have a problem:
>>> imwrite('test.jpg', result)
OSError: JPEG does not support alpha channel.
This is correct, as JPGs do not do transparency. If you would like to use transparency and also have your output be a JPG, I suggest a priest.
You can replace the transparent pixels by using np.where and looking for places that the alpha channel is 0:
result = np.hstack((im1,im2))
result[np.where(result[...,3] == 0)] = [255, 255, 255, 255]
imwrite('result.png', result)
If you want to improve image quality, here is a solution. #Brondy
# External libraries used for
# Image IO
from PIL import Image
# Morphological filtering
from skimage.morphology import opening
from skimage.morphology import disk
# Data handling
import numpy as np
# Connected component filtering
import cv2
black = 0
white = 255
threshold = 160
# Open input image in grayscale mode and get its pixels.
img = Image.open("image.jpg").convert("LA")
pixels = np.array(img)[:,:,0]
# Remove pixels above threshold
pixels[pixels > threshold] = white
pixels[pixels < threshold] = black
# Morphological opening
blobSize = 1 # Select the maximum radius of the blobs you would like to remove
structureElement = disk(blobSize) # you can define different shapes, here we take a disk shape
# We need to invert the image such that black is background and white foreground to perform the opening
pixels = np.invert(opening(np.invert(pixels), structureElement))
# Create and save new image.
newImg = Image.fromarray(pixels).convert('RGB')
newImg.save("newImage1.PNG")
# Find the connected components (black objects in your image)
# Because the function searches for white connected components on a black background, we need to invert the image
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(np.invert(pixels), connectivity=8)
# For every connected component in your image, you can obtain the number of pixels from the stats variable in the last
# column. We remove the first entry from sizes, because this is the entry of the background connected component
sizes = stats[1:,-1]
nb_components -= 1
# Define the minimum size (number of pixels) a component should consist of
minimum_size = 100
# Create a new image
newPixels = np.ones(pixels.shape)*255
# Iterate over all components in the image, only keep the components larger than minimum size
for i in range(1, nb_components):
if sizes[i] > minimum_size:
newPixels[output == i+1] = 0
# Create and save new image.
newImg = Image.fromarray(newPixels).convert('RGB')
newImg.save("new_img.PNG")
If you want to change the background of a Image, pixellib is the best solution because it seemed the most reasonable and easy library to use.
import pixellib
from pixellib.tune_bg import alter_bg
change_bg = alter_bg()
change_bg.load_pascalvoc_model("deeplabv3_xception_tf_dim_ordering_tf_kernels.h5")
change_bg.color_bg("sample.png", colors=(255,255,255), output_image_name="colored_bg.png")
This code requires pixellib to be higher or the same as 0.6.1
I am currently trying to use an RGBA image to 'punch' out a hole in another RGBA image but all my current attempts have failed to maintain the original transparency. Once I apply an alpha channel using putalpha it will replace the original alpha channel completely and turn previously transparent pixels back to their original colors.
I am trying to perform a "putalpha" on only the pixels with 100% transparency.
In the photos below I attempt to overlap an 'inverted transparency' alpha channel on top of my Circle to perform the 'punch out'. Instead of only applying the transparent pixels it will replace the entire image's alpha which turns the rest of the circle image's transparency white.
Is there a way for me to do this transparency "Merge" to achieve an alpha layer that is a composite of both images?
#image2 is a square, image1 is a circle
# swapTransparency is a function I made that works in swapping the transparency, it just goes pixel by pixel and switches alpha channel value to # max where empty and to 0 everywhere else.
# probably a better and more effective way to invert the transparency but this works right now and might not even be needed.
def swapTransparency(img):
datas = img.getdata()
newData = []
for item in datas:
if item [3] == 0:
newData.append((0, 0, 0, 255))
else:
newData.append((255, 255, 255, 0))
img.putdata(newData)
return img
##This is putting alpha channel overtop but its replacing the entire alpha instead of merging them, losing original cricle transparency.
image2 = swapTransparency(image2)
alphaChannel = image2.getchannel('A')
image1.putalpha(image2)
Image1
Image2
Desired Results
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)
I have two images, one with only background and the other with background + detectable object (in my case its a car). Below are the images
I am trying to remove the background such that I only have car in the resulting image. Following is the code that with which I am trying to get the desired results
import numpy as np
import cv2
original_image = cv2.imread('IMG1.jpg', cv2.IMREAD_COLOR)
gray_original = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
background_image = cv2.imread('IMG2.jpg', cv2.IMREAD_COLOR)
gray_background = cv2.cvtColor(background_image, cv2.COLOR_BGR2GRAY)
foreground = np.absolute(gray_original - gray_background)
foreground[foreground > 0] = 255
cv2.imshow('Original Image', foreground)
cv2.waitKey(0)
The resulting image by subtracting the two images is
Here is the problem. The expected resulting image should be a car only.
Also, If you take a deep look in the two images, you'll see that they are not exactly same that is, the camera moved a little so background had been disturbed a little. My question is that with these two images how can I subtract the background. I do not want to use grabCut or backgroundSubtractorMOG algorithm right now because I do not know right now whats going on inside those algorithms.
What I am trying to do is to get the following resulting image
Also if possible, please guide me with a general way of doing this not only in this specific case that is, I have a background in one image and background+object in the second image. What could be the best possible way of doing this. Sorry for such a long question.
I solved your problem using the OpenCV's watershed algorithm. You can find the theory and examples of watershed here.
First I selected several points (markers) to dictate where is the object I want to keep, and where is the background. This step is manual, and can vary a lot from image to image. Also, it requires some repetition until you get the desired result. I suggest using a tool to get the pixel coordinates.
Then I created an empty integer array of zeros, with the size of the car image. And then I assigned some values (1:background, [255,192,128,64]:car_parts) to pixels at marker positions.
NOTE: When I downloaded your image I had to crop it to get the one with the car. After cropping, the image has size of 400x601. This may not be what the size of the image you have, so the markers will be off.
Afterwards I used the watershed algorithm. The 1st input is your image and 2nd input is the marker image (zero everywhere except at marker positions). The result is shown in the image below.
I set all pixels with value greater than 1 to 255 (the car), and the rest (background) to zero. Then I dilated the obtained image with a 3x3 kernel to avoid losing information on the outline of the car. Finally, I used the dilated image as a mask for the original image, using the cv2.bitwise_and() function, and the result lies in the following image:
Here is my code:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Load the image
img = cv2.imread("/path/to/image.png", 3)
# Create a blank image of zeros (same dimension as img)
# It should be grayscale (1 color channel)
marker = np.zeros_like(img[:,:,0]).astype(np.int32)
# This step is manual. The goal is to find the points
# which create the result we want. I suggest using a
# tool to get the pixel coordinates.
# Dictate the background and set the markers to 1
marker[204][95] = 1
marker[240][137] = 1
marker[245][444] = 1
marker[260][427] = 1
marker[257][378] = 1
marker[217][466] = 1
# Dictate the area of interest
# I used different values for each part of the car (for visibility)
marker[235][370] = 255 # car body
marker[135][294] = 64 # rooftop
marker[190][454] = 64 # rear light
marker[167][458] = 64 # rear wing
marker[205][103] = 128 # front bumper
# rear bumper
marker[225][456] = 128
marker[224][461] = 128
marker[216][461] = 128
# front wheel
marker[225][189] = 192
marker[240][147] = 192
# rear wheel
marker[258][409] = 192
marker[257][391] = 192
marker[254][421] = 192
# Now we have set the markers, we use the watershed
# algorithm to generate a marked image
marked = cv2.watershed(img, marker)
# Plot this one. If it does what we want, proceed;
# otherwise edit your markers and repeat
plt.imshow(marked, cmap='gray')
plt.show()
# Make the background black, and what we want to keep white
marked[marked == 1] = 0
marked[marked > 1] = 255
# Use a kernel to dilate the image, to not lose any detail on the outline
# I used a kernel of 3x3 pixels
kernel = np.ones((3,3),np.uint8)
dilation = cv2.dilate(marked.astype(np.float32), kernel, iterations = 1)
# Plot again to check whether the dilation is according to our needs
# If not, repeat by using a smaller/bigger kernel, or more/less iterations
plt.imshow(dilation, cmap='gray')
plt.show()
# Now apply the mask we created on the initial image
final_img = cv2.bitwise_and(img, img, mask=dilation.astype(np.uint8))
# cv2.imread reads the image as BGR, but matplotlib uses RGB
# BGR to RGB so we can plot the image with accurate colors
b, g, r = cv2.split(final_img)
final_img = cv2.merge([r, g, b])
# Plot the final result
plt.imshow(final_img)
plt.show()
If you have a lot of images you will probably need to create a tool to annotate the markers graphically, or even an algorithm to find markers automatically.
The problem is that you're subtracting arrays of unsigned 8 bit integers. This operation can overflow.
To demonstrate
>>> import numpy as np
>>> a = np.array([[10,10]],dtype=np.uint8)
>>> b = np.array([[11,11]],dtype=np.uint8)
>>> a - b
array([[255, 255]], dtype=uint8)
Since you're using OpenCV, the simplest way to achieve your goal is to use cv2.absdiff().
>>> cv2.absdiff(a,b)
array([[1, 1]], dtype=uint8)
I recommend using OpenCV's grabcut algorithm. You first draw a few lines on the foreground and background, and keep doing this until your foreground is sufficiently separated from the background. It is covered here: https://docs.opencv.org/trunk/d8/d83/tutorial_py_grabcut.html
as well as in this video: https://www.youtube.com/watch?v=kAwxLTDDAwU