Not too long ago, I wanted so as to add a white background to a few pictures with a clear background. Naturally, I used Python with OpenCV to automate the method, I wasn’t going to open a picture editor for every of those pictures.
Including a background coloration sounds simple sufficient, proper?
Nicely, on my first try at implementing this, I didn’t fairly succeed and needed to iterate on the answer, so I believed I’d share the method with you.
If you wish to comply with alongside, ensure to put in the opencv-python and numpy bundle in your native Python surroundings. You need to use the next clear picture of an equation for experimentation, you may obtain it from here .
Load Picture with Alpha Channel
In a primary step, we have to load the clear picture together with the alpha channel. If we load the picture usually with cv2.imread with out the required parameter, it’s going to simply ignore the alpha channel and the picture shall be fully black on this case.
img = cv2.imread("equation.png")cv2.imshow("Picture", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Should you have a look at the form of the picture, you’ll discover that there are solely 3 coloration channels for the blue, inexperienced and pink values, however no alpha channel.
# Form of picture so as (H, W, C) with H=peak, W=width, C=coloration
print(img.form)
> (69, 244, 3)
There’s a flag parameter within the imread operate, which defaults to cv2.IMREAD_COLOR. It will at all times convert the picture to a 3 channel BGR picture. To maintain the alpha channel, we have to specify the flag cv2.IMREAD_UNCHANGED.
img = cv2.imread("equation.png", flags=cv2.IMREAD_UNCHANGED)# Form of picture so as (H, W, C) with H=peak, W=width, C=coloration
print(img.form)
> (69, 244, 4)
As you may see now, the alpha channel can also be included and the colour channel order is BGRA, so for every pixel we get 4 values the place the final one is the alpha worth.
Should you attempt to present the picture with imshow, you will notice that it’s nonetheless black. The operate merely ignores the alpha channel, however we all know it’s there. Now we will begin engaged on changing the alpha channel with a coloured background.
Binary Background
In a primary try, we may attempt to substitute the clear pixels of the picture with the background coloration, e.g. white. We will for instance set all of the pixels with greater than 50% transparency to white. For the reason that picture array is represented with 8-bit unsigned integers, the worth of the alpha channel reaches from 0 to 255. Therefore we will substitute all pixels which have an alpha worth of lower than or equal 128 (50% of 256) with white (all 3 coloration channels set to 255).
# Values for background
bg_threshold = 128 # 50% transparency
bg_color = (255, 255, 255) # White# Threshold alpha channel
bg_indices = img[:, :, 3] <= bg_threshold
# Exchange picture at indices with background coloration
img[bg_indices, :3] = bg_color
And right here you may see the end result:
You can even strive it with completely different thresholds, within the excessive case with a threshold of 0, so solely 100% clear pixels are thought of background and therefore coloured white.
Background Mixing
As you might need already seen, each of those pictures don’t look fairly proper. You may most likely discover a higher threshold that makes the picture look first rate, however the underlying drawback is that we’re setting a binary threshold, whereas the opacity / alpha channel is a steady worth. The background coloration ought to really be blended with the unique coloration, so let’s see how we will obtain this.
To mix the unique coloration with a background coloration primarily based on the alpha worth, we wish the next conduct:
- At 100% opacity, we must always apply 100% of the unique coloration
- At 60% opacity, we wish 60% of the unique coloration and 40% of the background coloration.
- At 0% opacity we wish solely the background coloration.
In different phrases, the colour of the ultimate picture for every pixel ought to be as follows, the place alpha is a worth between 0 and 1:
# alpha=0 -> 0% opacity, absolutely clear
# alpha=1 -> 100% opacity, absolutely opaque
color_final = alpha * authentic + (1 - alpha) * background
To attain this, we first need to extract the alpha channel and normalize it. Be certain to transform the info kind to float earlier than normalization, with the unique uint8 information kind we’d solely get 0s and 1s.
NOTE: We divide by 255 as a substitute of 256, for the reason that most worth of an 8-bit integer might be 255, and we wish that to be precisely 1.
alpha = img[:, :, 3].astype(float)
alpha = alpha / 255.0
Subsequent we must always put together the unique 3 coloration channel picture by indexing the primary 3 channels within the BGRA picture. We additionally must convert these values to floats, in any other case we gained’t have the ability to multiply and add them collectively later with the alpha worth.
# BGR for blue, inexperienced, pink
img_bgr_original = img[:, :, :3].astype(float)
We will additionally put together the background picture, consisting of our background coloration at every pixel. Utilizing the full_like operate of numpy, we will repeat a worth, in our case the background coloration, in a manner that leads to the identical form because the enter array, our picture. Once more, utilizing the float information kind.
img_bgr_background = np.full_like(a=img_bgr_original, fill_value=bg_color, dtype=float)
Now we’re virtually able to multiply with our system from above. In reality, let’s strive it and see what occurs, so you may also perceive why we have to do an additional step.
img_blended = alpha * img_bgr_original + (1 - alpha) * img_bgr_background
If we run this, we get the next error:
img_blended = alpha * img_bgr_original + (1 - alpha) * img_bgr_background
~~~~~~^~~~~~~~~~~~~~~~~~
ValueError: operands couldn't be broadcast along with shapes (69,244) (69,244,3)
The issue is, that our alpha picture has a form (69, 244) of dimension 2, there’s no coloration channel dimension. Whereas the photographs have a coloration channel dimension (69, 244, 3), the dimensionality is 3. To repair this, we wish to ensure each arrays have 3 dimensions, then we will multiply them collectively. To do that, we will broaden the dimension of our alpha array utilizing the np.expand_dims operate.
print(f"{img.form=}") # img.form=(69, 244, 4)
print(f"{img_bgr_original.form=}") # img_bgr_original.form=(69, 244, 3)
print(f"{img_bgr_background.form=}") # img_bgr_background.form=(69, 244, 3)
print(f"{alpha.form=}") # alpha.form=(69, 244)alpha = np.expand_dims(alpha, axis=-1)
print(f"{alpha.form=}") # alpha.form=(69, 244, 1) <- see the extra dimension right here!
This idea is extraordinarily vital however not fairly simple to grasp. I encourage you to spend a while to grasp and experiment with the array shapes.
Now we’re lastly capable of calculate the blended picture with the code from above. One last step we’d like earlier than we will present the picture, is convert it again to an 8-bit unsigned integer array.
img_blended = alpha * img_bgr_original + (1 - alpha) * img_bgr_background
img_blended = img_blended.astype(np.uint8)cv2.imshow("Blended Background", img_blended)
Now we will visualize the blended picture and see the clear end result:
We will additionally change the background coloration to an orange coloration for instance, and add some padding across the picture with the background coloration.
bg_color = (200, 220, 255) # Orange
bg_padding = 20...
img_blended = cv2.copyMakeBorder(
img_blended, bg_padding, bg_padding, bg_padding, bg_padding, cv2.BORDER_CONSTANT, worth=bg_color
)
...
On this mission, you realized easy methods to add a background layer to a picture with transparency. We first explored a binary technique that makes use of a threshold to set the background coloration within the picture. Nevertheless, this strategy didn’t produce visually passable outcomes. In a second try, we used an strategy that blends the background coloration primarily based on the alpha worth. This resulted in clear pictures with a naturally wanting background. We seemed into some particulars concerning picture dimensionalities and labored by a typical error that may occur when working with numpy arrays.
The total supply code is obtainable from GitHub under. I hope you realized one thing!