Alpha blending two images with OpenCV and/or Numpy

Viewed 5751

I would like to add a semi-transparent rectangle filled with a solid colour to an already loaded semi-transparent PNG. Here's an example input image I am using:

enter image description here

That image is loaded with a standard cv2.IMREAD_UNCHANGED flag so that alpha channel is perfectly preserved. That input image is stored in the image variable.

Here's my code that I have so far:

# get image dimensions
imgHeight, imgWidth = image.shape[:2]

# create empty overlay layer with 4 channels
overlay = np.zeros((imgHeight, imgWidth, 4), dtype = "uint8")

# draw semi-transparent red rectangle
overlay[200:300, 0:imgWidth] = (0, 0, 255, 200)

# extract alpha channel from overlay
alpha = cv2.split(overlay)[3]

# compute mask
mask = (np.multiply(alpha, 1.0 / 255))[:, :, np.newaxis]

# blend input image and overlay
output = cv2.convertScaleAbs(overlay * mask + image * (1 - mask))

And here's the result that I am getting:

enter image description here

At first glance it looks acceptable. We have our input image with a semi-transparent rectangle in the middle. However, upon closer inspection, we can observe strange behaviour when mixing alpha channels (marked with arrows):

enter image description here

It seems that alpha is not blended at all which results in original image pixels being only fully opaque or fully transparent.

Perhaps my method of blending transparent PNG with semi-transparent shapes is far from ideal. As a side note, I did try the cv2.addWeighted method but that yielded even worse results.

I would like the solution to be limited to OpenCV and/or Numpy. Any help would be greatly appreciated.

2 Answers

As unlut pointed out this is indeed a duplicate. Just in case someone stumbles on it, Mark Setchell's answer works pretty well:

# get image dimensions
imgHeight, imgWidth = image.shape[:2]

# create empty overlay layer with 4 channels
overlay = np.zeros((imgHeight, imgWidth, 4), dtype = "uint8")

# draw semi-transparent red rectangle
overlay[200:300, 0:imgWidth] = (0, 0, 255, 200)

# Extract the RGB channels
srcRGB = image[...,:3]
dstRGB = overlay[...,:3]

# Extract the alpha channels and normalise to range 0..1
srcA = image[...,3]/255.0
dstA = overlay[...,3]/255.0

# Work out resultant alpha channel
outA = srcA + dstA*(1-srcA)

# Work out resultant RGB
outRGB = (srcRGB*srcA[...,np.newaxis] + dstRGB*dstA[...,np.newaxis]*(1-srcA[...,np.newaxis])) / outA[...,np.newaxis]

# Merge RGB and alpha (scaled back up to 0..255) back into single image
outRGBA = np.dstack((outRGB,outA*255)).astype(np.uint8)

plt.imshow(outRGBA)

simple work with alpha blending: https://learnopencv.com/alpha-blending-using-opencv-cpp-python/

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