9
votes

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.

1
Question seems duplicate to me but I can't flag due to bounty: stackoverflow.com/questions/60398939/…unlut
I only wish I could grant the bounty to Mark Setchell from the answer you linked toPono

1 Answers

9
votes

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)