1

I have two images. In one image, the white pixels are set to be transparent (or colorless) by editing the channel A in RGBA image format. Suppose this image name is image_rgba.png and the other image is image.jpg. Now, I want to put the image_rgba.png image in a specific location of the image.jpg using the following python code

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

image = cv2.imread('image.jpg')
label = cv2.imread('image_rgba.png')
label = cv2.cvtColor(label, cv2.COLOR_BGR2RGBA)

new_image = image.copy()
new_image = cv2.cvtColor(new_image, cv2.COLOR_BGR2RGBA)

new_image[200:290, 300:384, :] = label

When I write this image, the edges of the image_rgba.png which is colorless, are set to be white in the output image (the white edges of the green box in the following image).

enter image description here

I want the white edges of the green box label does not to show and instead the background image shown in that pixels. I found the similar question but it does not help me to solve my problem. I would be appreciated it if anybody can help me.

Mohammadreza Riahi
  • 502
  • 1
  • 6
  • 20
  • 2
    Great question. A task as simple as this can really suck in OpenCV. Spent 1 hour of searching for duplicates, but none of the damn answers explains how to merge a JPG and a PNG with alpha channel of different size. – Thomas Weller Jan 04 '22 at 13:11
  • 1
    Does this answer your question? [How to join png with alpha / transparency in a frame in realtime](https://stackoverflow.com/questions/36921496/how-to-join-png-with-alpha-transparency-in-a-frame-in-realtime) – Dan Mašek Jan 04 '22 at 13:52
  • @DanMašek: almost IMHO. But it does not consider images of different size – Thomas Weller Jan 04 '22 at 13:55
  • 3
    recipe https://github.com/opencv/opencv/issues/20780 – Christoph Rackwitz Jan 04 '22 at 14:47
  • 1
    btw, these questions show up every other week and there are enough high quality answers that this should definitely be closed as a duplicate of any of those. just look for "overlay", "transparency", ... and pay attention to those solutions that merely use the alpha channel as a mask instead of multiplying properly. `addWeighted` is always wrong because its weight is a scalar, not a picture. – Christoph Rackwitz Jan 04 '22 at 16:34

1 Answers1

4

Here's how you can do it. I'm not an OpenCV expert, so there might be simpler approaches. Comments in the code explain what it does. The overall idea is:

  1. Extract the alpha channel from the PNG
  2. Extract a piece of the background with the same size
  3. Alpha blend these
  4. Put the result back into the background
import cv2

def alphaMerge(small_foreground, background, top, left):
    """
    Puts a small BGRA picture in front of a larger BGR background.
    :param small_foreground: The overlay image. Must have 4 channels.
    :param background: The background. Must have 3 channels.
    :param top: Y position where to put the overlay.
    :param left: X position where to put the overlay.
    :return: a copy of the background with the overlay added.
    """
    result = background.copy()
    # From everything I read so far, it seems we need the alpha channel separately
    # so let's split the overlay image into its individual channels
    fg_b, fg_g, fg_r, fg_a = cv2.split(small_foreground)
    # Make the range 0...1 instead of 0...255
    fg_a = fg_a / 255.0
    # Multiply the RGB channels with the alpha channel
    label_rgb = cv2.merge([fg_b * fg_a, fg_g * fg_a, fg_r * fg_a])

    # Work on a part of the background only
    height, width = small_foreground.shape[0], small_foreground.shape[1]
    part_of_bg = result[top:top + height, left:left + width, :]
    # Same procedure as before: split the individual channels
    bg_b, bg_g, bg_r = cv2.split(part_of_bg)
    # Merge them back with opposite of the alpha channel
    part_of_bg = cv2.merge([bg_b * (1 - fg_a), bg_g * (1 - fg_a), bg_r * (1 - fg_a)])

    # Add the label and the part of the background
    cv2.add(label_rgb, part_of_bg, part_of_bg)
    # Replace a part of the background
    result[top:top + height, left:left + width, :] = part_of_bg
    return result

background = cv2.imread('image.jpg')
# Read the image "unchanged" to get the alpha channel as well
label = cv2.imread('image_rgba.png', cv2.IMREAD_UNCHANGED)
result = alphaMerge(label, background, 100, 200)
cv2.imshow("result", result)
cv2.waitKey()

I tested with the following background:

Background

And this foreground:

Foreground

Result as shown by the Python code:

Result

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222