when i was trying to overlay one image over the other one image had a transparent rounded rectangle filling and the other was just a normal image it looked either like this ( just putting the yellow over the pink without taking into account the rounded corners at all) or like this (looks just like the rounded rectangle without adding anything even kept the transparency)
this is how it should look like:
here are the 2 example images: (pink.png) and (yellow.png)
here is the code used for this :
import cv2
import numpy as np
layer0 = cv2.imread(r'yellow.png', cv2.IMREAD_UNCHANGED)
h0, w0 = layer0.shape[:2]
layer4 = cv2.imread(r"pink.png", cv2.IMREAD_UNCHANGED)
#just a way to help the image look more transparent in the opencv imshow because imshow always ignores
# the transparency and pretends that the image has no alpha channel
for y in range(layer4.shape[0]):
for x in range(layer4.shape[1]):
if layer4[y,x][3]<255:
layer4[y,x][:] =0,0,0,0
# Create a new np array
shapes = np.zeros_like(layer4, np.uint8)
shapes = cv2.cvtColor(shapes, cv2.COLOR_BGR2BGRA)
#the start position of the yellow image on the pink
gridpos = (497,419)
shapes[gridpos[1]:gridpos[1] h0, gridpos[0]:gridpos[0] w0] = layer0
# Change this into bool to use it as mask
mask = shapes.astype(bool)
# We'll create a loop to change the alpha
# value i.e transparency of the overlay
for alpha in np.arange(0, 1.1, 0.1)[::-1]:
# Create a copy of the image to work with
bg_img = layer4.copy()
# Create the overlay
bg_img[mask] = cv2.addWeighted( bg_img,1-alpha, shapes, alpha, 0)[mask]
# print the alpha value on the image
cv2.putText(bg_img, f'Alpha: {round(alpha,1)}', (50, 200),
cv2.FONT_HERSHEY_PLAIN, 8, (200, 200, 200), 7)
# resize the image before displaying
bg_img = cv2.resize(bg_img, (700, 600))
cv2.imwrite("out.png", bg_img)
cv2.imshow('Final Overlay', bg_img)
cv2.waitKey(0)
you can test different alpha combinations by pressing a key on the keyboard
CodePudding user response:
OpenCV Version
Took me some time, but basically you have to mask both images and then combine them. The code bellow is commented and should be self explenatory. I think the hardest part to grasp is, that your pink image actually represents the foreground and the yellow image is your background. The trickiest part is to not let anything through from your background, which is why you have to mask both images.
import cv2
import numpy as np
pink = cv2.imread("pink.png", cv2.IMREAD_UNCHANGED)
# We now have to use an image that has the same size as the pink "foreground"
# and create a black image wiht numpy's zeros_like (gives same size as input)
background = np.zeros_like(pink)
# We then split the pink image into 4 channels:
# b, g, r and alpha, we only need the alpha as mask
_, _, _, mask = cv2.split(pink)
yellow = cv2.imread("yellow.png", cv2.IMREAD_UNCHANGED)
# we need the x and y dimensions for pasting the image later
h_yellow, w_yellow = yellow.shape[:2]
# Assuming format is (x, y)
gridpos = (497, 419)
# We paste the yellow image onto our black background
# IMPORTANT: if any of the dimensions of yellow plus the gridpos is
# larger than the background width or height, this will give you an
# error! Also, this only works with the same number of input channels.
# If you are loading a jpg image without alpha channel, you can adjust
# the number of channels, the last input param, e.g. with :3 to only use
# the first 3 channels
background[gridpos[1]:gridpos[1] h_yellow, gridpos[0]:gridpos[0] w_yellow, :] = yellow
# This step was not intuitive for me in the first run, since the
# pink img should aready be masked, but for some reason, it is not
pink_masked = cv2.bitwise_and(pink, pink, mask=mask)
# In this step, we mask the positioned yellow image with the inverse
# mask from the pink image, achieved by bitwise_not
background = cv2.bitwise_and(background, background, mask=cv2.bitwise_not(mask))
# We combine the pink masked image with the background
img = cv2.convertScaleAbs(pink_masked background)
cv2.imshow("img", img), cv2.waitKey(0), cv2.destroyAllWindows()
Cheers!
Old Answer:
It looks like you are setting the whole image as a mask, this is why the rounded corners have no effect at all from your pink background. I myself was struggling a lot with this task aswell and ended up using pillow instead of OpenCV. I don't know if it is more performant, but I got it running.
Here the code that works for your example:
from PIL import Image
# load images
background = Image.open(r"pink.png")
# load image and scale it to the same size as the background
foreground = Image.open(r"yellow.png").resize(background.size)
# split gives you the r, g, b and alpha channel of the image.
# For the mask we only need alpha channel, indexed at 3
mask = background.split()[3]
# we combine the two images and provide the mask that is applied to the foreground.
im = Image.composite(background, foreground, mask)
im.show()
If your background is not monochrome as in your example, and you want to use the version, where you paste your original image, you have to create an empty image with the same size as the background, then paste your foreground to the position (your gridpos
), e.g. like this:
canvas = Image.new('RGBA', background.size)
canvas.paste(foreground, gridpos)
foreground = canvas
Hope this helps!