How to merge two RGBA images

Question:

I’m trying to merge two RGBA images (with a shape of (h,w,4)), taking into account their alpha channels.
Example :
example


What I’ve tried

I tried to do this using opencv for that, but I getting some strange pixels on the output image.

Images Used:

image1.png

and

image2.png

import cv2
import numpy as np
import matplotlib.pyplot as plt

image1 = cv2.imread("image1.png", cv2.IMREAD_UNCHANGED)
image2 = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)

mask1 = image1[:,:,3]
mask2 = image2[:,:,3]
mask2_inv = cv2.bitwise_not(mask2)

mask2_bgra = cv2.cvtColor(mask2, cv2.COLOR_GRAY2BGRA)
mask2_inv_bgra = cv2.cvtColor(mask2_inv, cv2.COLOR_GRAY2BGRA)

# output = image2*mask2_bgra + image1
output = cv2.bitwise_or(cv2.bitwise_and(image2, mask2_bgra), cv2.bitwise_and(image1, mask2_inv_bgra))
output[:,:,3] = cv2.bitwise_or(mask1, mask2)
plt.figure(figsize=(12,12))
plt.imshow(cv2.cvtColor(output, cv2.COLOR_BGRA2RGBA))
plt.axis('off')

Output :
output

So what I figured out is that I’m getting those weird pixels because I used cv2.bitwise_and function (Which btw works perfectly with binary alpha channels).
I tried using different approaches


Question

Is there an approach to do this (While keeping the output image as an 8bit image).

Asked By: SaulGoodMan

||

Answers:

You could use PIL library to achieve this

from PIL import Image

def merge_images(im1, im2):
    bg = Image.open(im1).convert("RGBA")
    fg = Image.open(im2).convert("RGBA")
    x, y = ((bg.width - fg.width) // 2 , (bg.height - fg.height) // 2)
    bg.paste(fg, (x, y), fg)
    # convert to 8 bits (pallete mode)
    return bg.convert("P") 

we can test it using the provided images:

result_image = merge_images("image1.png", "image2.png")
result_image.save("image3.png")

Here’s the result:

image3.png

Answered By: W0rk L0ne

I was able to obtain the expected result in 2 stages.

# Read both images preserving the alpha channel
hh1 = cv2.imread(r'C:Users524316DesktopStackhouse.png', cv2.IMREAD_UNCHANGED)  
hh2 = cv2.imread(r'C:Users524316DesktopStackmemo.png', cv2.IMREAD_UNCHANGED)   

# store the alpha channels only
m1 = hh1[:,:,3]
m2 = hh2[:,:,3]

# invert the alpha channel and obtain 3-channel mask of float data type
m1i = cv2.bitwise_not(m1)
alpha1i = cv2.cvtColor(m1i, cv2.COLOR_GRAY2BGRA)/255.0

m2i = cv2.bitwise_not(m2)
alpha2i = cv2.cvtColor(m2i, cv2.COLOR_GRAY2BGRA)/255.0

# Perform blending and limit pixel values to 0-255 (convert to 8-bit)
b1i = cv2.convertScaleAbs(hh2*(1-alpha2i) + hh1*alpha2i)

Note: In the b=above the we are using only the inverse alpha channel of the memo image

enter image description here

But I guess this is not the expected result. So moving on ….

# Finding common ground between both the inverted alpha channels
mul = cv2.multiply(alpha1i,alpha2i)

# converting to 8-bit
mulint = cv2.normalize(mul, dst=None, alpha=0, beta=255,norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

# again create 3-channel mask of float data type
alpha = cv2.cvtColor(mulint[:,:,2], cv2.COLOR_GRAY2BGRA)/255.0

# perform blending using previous output and multiplied result
final = cv2.convertScaleAbs(b1i*(1-alpha) + mulint*alpha)

enter image description here

Sorry for the weird variable names. I would request you to analyze the result in each line. I hope this is the expected output.

Answered By: Jeru Luke