Detailed explanation of alpha fusion (alpha compositing)

Introduction to alpha fusion

Alpha fusion (alpha compositing) is a commonly used technology in image processing. The common formula is as follows:
C O = α C A + ( 1 − α ) C B (1) C_O= \alpha C_A + (1-\alpha)C_B \tag{1} CO=αCA+(1α)CB(1)
inside C A C_A CA C B C_B CB represents the color to be blended (RGB), C O C_O CO represents the output color, α \alpha α represents the fusion factor, which takes the value [0, 1].

习惯上 C A C_A CA represents the foreground, C B C_B CB indicates the background. α \alpha α is used to characterize the foreground color C A C_A CAVisibility (transparency) in the result, α \alpha αEtsudai, display C A C_A CAThe greater the proportion accounts for in the results. When α = 1 \alpha=1 a=When 1, the result is exactly equal to C A C_A CA (completely opaque); otherwise α = 0 \alpha=0 a=0时, C A C_A CACompletely invisible in the results (completely transparent).

When alpha fusion was first proposed, it was used to solve the problem of material fusion in cartoon production with the assistance of computers. This technology was invented to deal with the transparency relationship between overlapping materials.

The earliest alpha fusion is formula (1), which was proposed by Smith et al. in the 1970s. It is also the most common formula in our various textbooks. The biggest feature of this formula is that there is only one α \alpha α, generally we think that α \alpha αUser description C A C_A CA transparency, while the background C B C_B CBThe transparency of defaults to 1 and must be 1. However, if the two colors to be merged each have their own α \alpha α,即代么 α A \alpha_A aA α B \alpha_B aB, then the above formula does not apply. Later, Wallace proposed a more general formula in 1981 to solve α A \alpha_A aA α B \alpha_B aB exists at the same time. Until this time, scholars have been dealing with the transparency relationship of materials when they are naturally stacked, which is the so-called "over" mode, that is,A over B. Later, Porter further promoted this technology in 1984, proposed the concept of alpha premultiplication (premultiplied alpha), and developed several other fusion modes such as in, out, atop, xor.

But no matter how the technology itself develops, one major premise must always be remembered when applying alpha fusion: in order to obtain the final screen rendering result, there must be a completely opaque "background". This background can be a white background in ppt or a gray grid in Photoshop. Because α \alpha α is not a physical thing, but a conceptual thing invented to assist calculations in order to deal with transparency relationships. There is no display in our monitor α \alpha The physical component of α, the monitor can only display colors. Therefore, no matter how many materials there are, and how complex their transparency and overlapping relationships are, they must ultimately fall on a completely opaque background to obtain pure color information before it can be sent to the monitor for display.

The gray grid in the picture below is the "completely opaque background" mentioned above.
Insert image description here

Understanding the alpha channel

Some image formats now support four channels (R, G, B, A), the most typical of which is png images, in which A is the so-called alpha channel, which is used to control the transparency of color information (R, G, B). So sometimes we refer to the alpha channel as the transparency channel.

The picture below is a simple material. The left picture is RGBA, the middle is RGB, and the right picture is alpha (A). The brighter the alpha channel, the larger the value.
You can see that the RGB color of the image is the same in all locations, but we can adjust the transparency of each area of ​​the image relative to the background through the alpha channel.
Insert image description here

Portrait cutout is generally achieved through the alpha channel. The material below uses 00148.png in the open source data set FFHQ. Similarly, the left picture is RGBA, the middle is RGB, and the right picture is alpha (A).
Nowadays, the mask (i.e. alpha channel) of the cutout is generally implemented through the convolutional neural network segmentation algorithm. The output of the segmentation algorithm is a probability value, which is generally mapped by the sigmoid function. White in the mask means almost 100% visible, black means almost 0% visible. Since the value range of the sigmoid function is (0, 1), that is to say, in addition to almost pure white and pure black, there will also be grayscale values ​​​​such as 0.2 and 0.5. These values ​​​​are distributed at the transition between pure white and pure black. Areas naturally form feathered edges, which can make the segmentation results have an anti-aliasing effect.
Insert image description here
During fusion, if the alpha of the cutout is processed incorrectly, it will often cause some bad cases on the edges, such as the typical white edge and black edge problem and aliasing problem. For example, if the mask we use for keying is a binary result that is either 0 or 1, then the above problems will occur.
If we set all the places where mask is not equal to 0 in the above picture to 1 (if mask >0: maks = 1), then use it for keying and place it on a green background , you can get the following picture.
In the picture below, the left picture is the result, the middle is the original picture, and the right picture is the binary mask.
If you zoom in on the left image below, you can clearly see the jagged edges. The hair cutout is not detailed enough and there are problems such as redundant edge information.
Please add image description

We subtract the binary mask from the normal mask, and we can feel the existence of the gray transition edge more clearly:
In the picture below, the left picture is binary mask, the middle is the normal mask, and the right image is the gray edge obtained by subtracting the normal mask from the binarization.
Please add image description

alpha compositing: over mode

Over mode is to stack one layer after another. The material placed on top will block the material placed below to a certain extent. The degree of occlusion is determined by the transparency of the material above.

First define a representation method: if there is material A ( α A \alpha_{A} aA),B( α B \alpha_{B} aB),C( α C \alpha_{C} aC),..., and the background Z ( α Z ≡ 1 \alpha_{Z} \equiv 1 aZ1), then AZ means stacking material A on top of background Z, ABZ means stacking material B on top of Z, and then stacking A on top of B. And so on.

以公式(1)的方式,有:
C A B Z = C A ( B Z ) = α A C A + ( 1 − α A ) C B Z = α A C A + ( 1 − α A ) ( α B C B + ( 1 − α B ) C Z ) = α A C A + ( 1 − α A ) α B C B + ( 1 − α A ) ( 1 − α B ) C Z (2) \begin{aligned} C_{ABZ} &= C_{A(BZ)} \\[2ex] &= \alpha_{A} C_A + (1-\alpha_{A})C_{BZ} \\[2ex] &= \alpha_{A} C_A + (1-\alpha_{A})(\alpha_{B} C_B + (1-\alpha_{B})C_Z) \\[2ex] &= \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B + (1-\alpha_{A}) (1-\alpha_{B})C_Z \end{aligned} \tag{2} CABZ=CA(BZ)=aACA+(1aA)CBZ=aACA+(1aA)(αBCB+(1aB)CZ)=aACA+(1aA)αBCB+(1aA)(1aB)CZ(2)

Formula (1) has a limitation, that is, whoever is in the position of merging the background must have alpha equal to 1. For example, in the above formula, when BZ is merged, B is placed on top of a completely opaque Z. The result must be completely opaque. At this time, the correct result can be obtained by stacking A again using formula (1). Therefore, under this limitation, we must start from the background and fuse upward layer by layer to get the correct result, which brings a lot of inflexibility to the fusion. For example, we already have a material pool, in which the materials all have transparency information. Now we need to fuse multiple materials to get a new material with transparency information. In this task, there is no such thing as a "background", so at this time, we use the formula (1 ) simply cannot complete the task. For another example, if we want to make the same rotation for both A and B, then according to formula (1), we must first rotate B, then place it on Z, then rotate A, and then place it on BZ, that is to say Even though the rotations of A and B are the same, we still have to do two rotations.

The reason is that only one is allowed in formula (1) α \alpha α is not equal to 1, as background α \alpha α must equal 1. Now we hope that both objects to be fused can have α \alpha α

The fusion result at this time can be expressed as C A B C_{AB} CAB, its transparency is expressed as α A B \alpha_{AB} aAB, both quantities are currently unknown, and the final result can be achieved according to the following fusion process:
C A B Z = C ( A B ) Z = α A B C A B + ( 1 − α A B ) C Z (3) \begin{aligned} C_{ABZ} &= C_{(AB)Z} \\[2ex] &= \alpha_{AB} C_{AB} + (1-\alpha_{AB })C_{Z} \\[2ex] \end{aligned} \tag{3} CABZ=C(AB)Z=aABCAB+(1aAB)CZ(3)

Customize C Z C_Z CZ, the results of formulas (2) and (3) must be equal, so C Z C_Z CZThe coefficient phase and bias term of must be equal respectively, and we get:
α A B C A B = α A C A + ( 1 − α A ) α B C B ( 1 − α A B ) = ( 1 − α A ) ( 1 − α B ) \begin{aligned} \alpha_{AB} C_{AB} &= \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B\ \[2ex] (1-\alpha_{AB}) &= (1-\alpha_{A}) (1-\alpha_{B}) \\[2ex] \end{aligned} aABCAB(1aAB)=aACA+(1aA)αBCB=(1aA)(1aB)

Solutions have to:

α A B = α A + α B − α A α B = α A + α B ( 1 − α A ) C A B = α A C A + ( 1 − α A ) α B C B α A B (4) \begin{aligned} \alpha_{AB} &= \alpha_{A} + \alpha_{B} - \alpha_{A} \alpha_{B} \\[2ex] &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) \\[4ex] C_{AB} &=\frac { \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B}{\alpha_{AB}}\\[2ex] \end{aligned} \tag{4} aABCAB=aA+aBaAaB=aA+aB(1aA)=aABaACA+(1aA)αBCB(4)

It is obtained through the physical concept of reflection/transmittance in the article of Wallace (1981), but it can also be derived based on the above formula.

The picture below is the result of fusion according to formula (1) (2), with a pure white background.
Top left: green circle stacked on top of white background, top right: red square stacked on top of "top left".
Bottom left: the red square is stacked on the white background, bottom right: the green circle is stacked on the "bottom left".
Insert image description here

The picture below is the result calculated according to formula (3) (4), and the background is also pure white. It is easy to see that the results calculated in this way are exactly the same as the results in the figure above.
Left: Red square stacked on green circle, stacked on white background.
Right: A green circle overlaid on a red square on a white background.
Insert image description here

The figure below only uses formula (4) to calculate the result of the fusion of two materials with transparency information, and displays the color and alpha of the two separately.
Top left: Red square stacked on green circle. Bottom left: green circle overlaid on red square
Middle column: color
Right column: alpha

Insert image description here
PS: The middle column of the above picture can be seen to a certain extent as the result of rendering under an incorrect alpha. At this time, there is a lack of layering brought by transparency information.

alpha premultiplication(alpha 预乘)

Slightly change formula (4) to get:
α A B C A B = α A C A + ( 1 − α A ) α B C B \alpha_{AB}C_{AB} = \ alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B aABCAB=aACA+(1aA)αBCB
If not
C X ′ = α X C X (5) C'_X=\alpha_{X} C_X \tag{5} CX=aXCX(5)

那么有:
α A B = α A + α B ( 1 − α A ) C A B ′ = C A ′ + C B ′ ( 1 − α A ) (6) \begin{aligned} \alpha_{AB} &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) \\[4ex] C'_{AB} &=C'_A + C'_B (1-\alpha_{A}) \end{aligned} \tag{6} aABCAB=aA+aB(1aA)=CA+CB(1aA)(6)

Formula (5) is alpha premultiplication, that is, multiplying the original color by its own alpha. Formula (6) is the over mode formula under alpha premultiplication.
When we read literature, if alpha premultiplication is done, it is usually called premultiplied alpha. If it is not done, it is called straight alpha.

使用alpha预乘要特别注意:在计算的各个阶段,包括融合背景在内,对alpha预乘的处理要统一,否则得不到正确的渲染结果。

Most image browsing software displays images based on straight alpha, so we must pay special attention to this when saving images to disk and write data in straight alpha format. If we write the result of premultiplied alpha, it will appear dark when displayed in the image viewing software.
The left picture below is straight alpha, and the right picture is premultiplied alpha. It is obvious that the right picture is dark.
Insert image description here

Advantages of alpha premultiplication

Compute acceleration

In the case of alpha premultiplication, α X \alpha_X aXCalculated sum of C X ′ C'_X CXThe calculation coefficients of are the same, so a quaternion can be further recorded as:
C X R G B A = ( C X ′ , α X ) C^{RGBA}_X = (C' _X, \alpha_X) CXRGBA=(CX,aX)
那么有:
C A B R G B A = C A R G B A + C B R G B A ( 1 − α A ) (7) C^{RGBA}_{AB} = C^{RGBA}_A + C^{RGBA}_B (1-\alpha_{A}) \tag{7} CABRGBA=CARGBA+CBRGBA(1aA)(7)

That is to say, the two calculation formulas of formula (6) can be combined into one calculation formula of formula (7), which can play a role in calculation acceleration for a language like OpenGL with quaternion definition.

Prevent color bleeding

When we do some spatial dimension operations, such as image interpolation, blur, etc., if we use unpremultiplied alpha, it may cause color bleeding, especially where alpha is close to 0. As shown below, the image is blurred without alpha premultiplication. The color of the original image is blue where alpha=0, and red where alpha=1. Due to the effect of alpha, only red can be seen in the rendering result before blurring. , but after blurring, you can clearly see blue leakage, which is not very intuitive.
Insert image description here

And if you do blurring with alpha premultiplication, the final result looks much more normal (picture below).
Insert image description here

Disadvantages of alpha premultiplication

Color accuracy lost

At present, most commonly used color pictures or video formats record color in a quantified manner, such as 8-bit for SDR, 10-bit for HDR, etc. After alpha is premultiplied, and then converted into quantized results for storage, it will lead to the loss of color accuracy. The smaller the alpha value, the more serious the loss of color accuracy. In the extreme case, when alpha=0, the original color will be completely lost.

For example, in the case of 8bit, alpha = 10, and there are three original color values ​​​​145, 150, and 155 respectively. Then the three colors are pre-multiplied (normalized and pre-multiplied) under the same alpha:
(145 / 255) * (10 / 255) = 0.022299< a i=2> (150 / 255) * (10 / 255) = 0.023068 (155 / 255) * (10 / 255) = 0.023837

The result still needs to be stored in 8bit, so the rounded value is converted to 8bit:
round(0.022299 * 255) = round(5.68) = 6
round(0.023068 * 255) = round(5.88) = 6
round(0.023837 * 255) = round(6.08) = 6

It can be seen that the originally differentiated colors become the same number under alpha premultiplication, and there is no longer distinction.

In many cases, since small alpha will cause transparency to increase, that is, visibility will decrease, so losing some precision in color will not bring a strong difference in visual perception. However, 如果我们面对的是调色工作,一般不能在alpha预乘的情况下去实施, because for most cases, what we want is the left side of the following formula (color first and then alpha premultiply), which is the same as the right side (alpha premultiply first and then color) Generally not equal.
α ∗ A d j u s t ( c o l o r ) ≠ A d j u s t ( α ∗ c o l o r ) \alpha*Adjust(color) \ne Adjust(\alpha*color) aAdjust(color)=Adjust(αcolor)

If the input we get is alpha premultiplied, we should perform inverse alpha premultiplication before color grading. The method is:

if alpha != 0:
	color = color / alpha
else:
	color = 0

Associative law of alpha compositing over pattern

When there are multiple materials with transparency channels, in the case of formula (1), the correct result can only be obtained by merging layer by layer from the bottom (canvas) up. In the case of formula (4) or (6), as long as the stacking order remains unchanged, we can decide the priority of fusion arbitrarily and get the correct result in the end. This brings great convenience, not only allowing us to use multiple transparency materials to generate new transparency materials through fusion, but also saving some common calculations. For example, two materials AB need to be rotated the same. In the case of formula (1), B must be rotated first and blended onto the canvas, and then A must be rotated and blended onto the canvas again. At this time, two rotations are required; and If you use formula (4) (6), you can first fuse AB, then do a rotation, and then merge it onto the canvas. At this time, you only need to do one rotation.

The following uses formula (6) to make a simple associative law derivation. Comparing the following formulas (8) (9), we can see that C A ( B C ) ′ = C ( A B ) C ′ C'_{A(BC)} = C'_{(AB)C} CA(BC)=C(AB)C,If the axis of the axis ABC is divided by the graph, the axis is
α A ( B C ) = α A + α B C ( 1 − α A ) = α A + [ . α B + α C ( 1 − α B ) ] ( 1 − α A ) = α A + α B ( 1 − α A ) + α C ( 1 − α A ) ( 1 − α B ) C A ( B C ) ′ = C A ′ + C B C ′ ( 1 − α A ) = C A ′ + [ C B ′ + C B C ′ ( 1 − α B ) ] ( 1 − α A ) = C A ′ + C B ′ ( 1 − α A ) + C C ′ ( 1 − α A ) ( 1 − α B ) (8) \begin{aligned} \alpha_{A(BC)} &= \alpha_{A} + \alpha_{BC}(1-\alpha_{A} ) \\[2ex] &=\alpha_{A} + [ \alpha_{B} + \alpha_{C}(1-\alpha_{B})](1-\alpha_{A}) \\[2ex ] &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) + \alpha_{C}(1-\alpha_{A})(1-\alpha_{B}) \\ [4ex] C'_{A(BC)} &= C'_{A} + C_{BC}(1-\alpha_{A}) \\[2ex] & = C'_{A} + [ C'_{B} + C'_{C} (1-\alpha_{B}) ] (1-\alpha_{A}) \\[ 2ex] &= C'_{A} + C'_{B}(1-\alpha_{A}) + C'_{C}(1-\alpha_{A})( 1-\alpha_{B}) \end{aligned} \tag{8} aA(BC)CA(BC)=aA+aBC(1aA)=aA+[αB+aC(1aB)](1aA)=aA+aB(1aA)+aC(1aA)(1aB)=CA+CBC(1aA)=CA+[CB+CC(1aB)](1aA)=CA+CB(1aA)+CC(1aA)(1aB)(8)

α ( A B ) C = α A B + α C ( 1 − α A B ) = α A + α B ( 1 − α A ) + α C ( 1 − [ α A + α B ( 1 − α A ) ] ) = α A + α B ( 1 − α A ) + α C ( 1 − α A ) ( 1 − α B ) C ( A B ) C ′ = C A B ′ + C C ′ ( 1 − α A B ) = C A ′ + C B ′ ( 1 − α A ) + C C ′ ( 1 − [ α A + α B ( 1 − α A ) ] ) = C A ′ + C B ′ ( 1 − α A ) + C C ′ ( 1 − α A ) ( 1 − α B ) (9) \begin{aligned} \alpha_{(AB)C} &= \alpha_{AB} + \alpha_{C}(1-\alpha_{AB}) \\[2ex] &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) + \alpha_{C}(1-[ \alpha_{A} + \alpha_{B}(1-\alpha_{A})]) \\[2ex] &= \alpha_{A} + \alpha_{B} (1-\alpha_{A}) + \alpha_{C}(1-\alpha_{A})(1-\alpha_{B}) \\[4ex] C'_{(AB)C} &= C'_{AB} + C'_{C} (1-\alpha_{AB}) \\[2ex] &= C'_{A} + C'_{B}(1-\alpha_{A}) + C'_{C}(1-[ \alpha_{A} + \alpha_{B}(1-\alpha_{A})]) \\[2ex] &= C'_{A} + C'_{B} (1-\alpha_{A}) + C'_{C} (1-\alpha_{A})(1-\alpha_{B}) \end{aligned} \tag{9} a(AB)CC(AB)C=aAB+aC(1aAB)=aA+aB(1aA)+aC(1[αA+aB(1aA)])=aA+aB(1aA)+aC(1aA)(1aB)=CAB+CC(1aAB)=CA+CB(1aA)+CC(1[αA+aB(1aA)])=CA+CB(1aA)+CC(1aA)(1aB)(9)

Alpha compositing for other modes

There are four other less commonly used alpha compositing modes, namely in, out, atop, and xor. Porter (1984) defined the above modes based on the concepts of reflection and transmission of light.

The following figure is an example of various modes in Wikipedia
Insert image description here
Wikipedia does not provide a calculation formula. For calculation, you need to refer to the literature of Porter (1984), as follows:
Insert image description here

Insert image description here

统一的公式是(straight alpha):
α A B = α A F A + α B F B C A B =   α A F A C A + α B F B C B α A B (10) \begin{aligned} \alpha_{AB} &= \alpha_{A}F_{A} + \alpha_{B}F_{B} \\[4ex] C_{AB} &=\frac { \ \alpha_{A} F_{A} C_{A} + \alpha_{B} F_{B} C_{B}} {\alpha_{AB}}\\[2ex] \end{aligned} \tag{10} aABCAB=aAFA+aBFB=aAB aAFACA+aBFBCB(10)

The value of F is as follows:
over: F A = ​​1 , F B = 1 − α A F_A=1, F_B=1-\alpha_A FA=1,FB=1aA
in: F A = α B , F B = 0 F_A = \alpha_B, F_B=0 FA=aB,FB=0
out: F A = 1 − α B , F B = 0 F_A=1-\alpha_B,F_B=0 FA=1aB,FB=0
atop: F A = α B , F B = 1 − α A F_A=\alpha_B,F_B=1-\alpha_A FA=aB,FB=1aA
xor: F A = 1 − α B , F B = 1 − α A F_A=1-\alpha_B,F_B=1-\alpha_A FA=1aB,FB=1aA

The picture below shows the results of in, out, atop, and xor from left to right. The upper row has no underlying canvas, and the lower row uses a grid canvas to facilitate observation and comparison of effects.
Insert image description here

Experimental code

utils.py

# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np


def remove_file(filename):
    """
    Remove a file.
    """
    if os.path.exists(filename):
        os.remove(filename)


def float_to_uint8(image, scale=255.):
    return np.uint8(np.clip(np.round(image * scale), 0, 255))


def concat_color_alpha(color, alpha):
    alpha = np.squeeze(alpha)[..., np.newaxis]
    image = np.concatenate((color, alpha), axis=2)
    return image


def alpha_premultiply(image, alpha):
    alpha = np.squeeze(alpha)
    result = image * alpha[..., np.newaxis]
    return result


def mix(color1, color2, alpha):
    """
    Naive alpha blending.
    """
    if not isinstance(alpha, (int, float)):
        alpha = np.squeeze(alpha)[..., np.newaxis]
    res = color1 * (1.0 - alpha) + color2 * alpha
    return res


def alpha_compositing(fg_color,
                      fg_alpha,
                      bg_color,
                      bg_alpha,
                      mode='over',
                      premultiplied_input=False,
                      premultiplied_output=False):
    if not isinstance(fg_alpha, (int, float)):
        fg_alpha = np.squeeze(fg_alpha)[..., np.newaxis]
    if not isinstance(bg_alpha, (int, float)):
        bg_alpha = np.squeeze(bg_alpha)[..., np.newaxis]

    if mode == 'over':
        fg_fraction = 1.
        bg_fraction = 1. - fg_alpha
    elif mode == 'in':
        fg_fraction = bg_alpha
        bg_fraction = 0.
    elif mode == 'out':
        fg_fraction = 1. - bg_alpha
        bg_fraction = 0.
    elif mode == 'atop':
        fg_fraction = bg_alpha
        bg_fraction = 1. - fg_alpha
    elif mode == 'xor':
        fg_fraction = 1. - bg_alpha
        bg_fraction = 1. - fg_alpha
    else:
        raise ValueError(
            "mode must be one of ['over', 'in', 'out', 'atop', 'xor']")

    out_alpha = fg_alpha * fg_fraction + bg_alpha * bg_fraction
    if premultiplied_input:
        out_color = fg_fraction * fg_color + bg_fraction * bg_color
    else:
        out_color = fg_alpha * fg_fraction * fg_color + \
                    bg_alpha * bg_fraction * bg_color

    if not premultiplied_output:
        out_color = out_color / np.maximum(out_alpha, 1e-6)

    return out_color, out_alpha

media.py

# -*- coding: utf-8 -*-
import cv2
import numpy as np

from utils import float_to_uint8
from utils import concat_color_alpha


def decomposite_00148():
    image_wo_bg = cv2.imread(r'portrait\00148-remove-bg-small.png',
                             cv2.IMREAD_UNCHANGED)
    height, width = image_wo_bg.shape[:2]
    ori_image = cv2.imread(r'.\portrait\00148.png', cv2.IMREAD_UNCHANGED)
    image = cv2.resize(ori_image, dsize=(width, height), fx=None, fy=None,
                       interpolation=cv2.INTER_AREA)

    color = image_wo_bg[..., :3]
    alpha = image_wo_bg[..., -1]

    binary_alpha = alpha.copy()
    binary_alpha[binary_alpha > 0] = 255
    alpha_edge = binary_alpha - alpha

    image_small = concat_color_alpha(image, alpha)

    cv2.imwrite(r'.\portrait\00148-small.png', image)
    cv2.imwrite(r'.\portrait\color.png', color)
    cv2.imwrite(r'.\portrait\alpha.png', alpha)
    cv2.imwrite(r'.\portrait\binary-alpha.png', binary_alpha)
    cv2.imwrite(r'.\portrait\alpha-edge.png', alpha_edge)
    cv2.imwrite(r'.\portrait\00148-remove-bg-small-new.png', image_small)


def generate_ellipse_media():
    height = 600
    width = 800

    alpha = np.zeros(shape=[height, width], dtype=np.float_)
    half_min_size = int(min(width, height) // 2)
    cv2.ellipse(alpha,
                center=(width // 2, height // 2),
                axes=(int(half_min_size * 0.8), int(half_min_size * 0.6)),
                angle=0,
                startAngle=0,
                endAngle=360,
                color=1.0,
                thickness=-1,
                lineType=cv2.LINE_AA)

    result = np.ones(shape=(height, width, 4), dtype=np.float_)
    index = alpha > 0.99
    result[index, :3] = (0., 0., 1.)
    result[~index, :3] = (1., 0., 0.)
    result[..., -1] = alpha
    cv2.imwrite(r'.\media\ellipse.png', float_to_uint8(result))


def generate_circle_media():
    fg_color = (0., 1., 0.)
    height = 600
    width = 800
    half_min_size = int(min(width, height) // 2)

    alpha = np.zeros(shape=[height, width], dtype=np.float_)
    cv2.circle(alpha,
               center=(width // 2 + 50, height // 2 + 80),
               radius=int(half_min_size * 0.55),
               color=1.0,
               thickness=-1,
               lineType=cv2.LINE_AA)

    radius = int(half_min_size * 0.3) // 2 * 2 + 1
    alpha = cv2.GaussianBlur(alpha,
                             ksize=(radius, radius),
                             sigmaX=radius / 2.5,
                             sigmaY=radius / 2.5,
                             borderType=cv2.BORDER_REFLECT_101)
    alpha = alpha / np.max(alpha)
    alpha = 0.2 + alpha * 0.4

    result = np.ones(shape=(height, width, 4), dtype=np.float_)
    result[..., :3] = fg_color
    result[..., -1] = alpha
    result = float_to_uint8(result)
    cv2.imwrite(r'.\media\green-circle.png', result)
    cv2.imwrite(r'.\media\green-circle-color.png', result[..., :3])
    cv2.imwrite(r'.\media\green-circle-alpha.png', result[..., -1])

    result[..., :3] = result[..., :3] * alpha[..., np.newaxis]
    cv2.imwrite(r'.\media\green-circle-premultiply.png', result)
    cv2.imwrite(r'.\media\green-circle-premultiply-color.png', result[..., :3])


def generate_square_media():
    fg_color = (0., 0., 1.)
    height = 600
    width = 800
    half_min_size = int(min(width, height) // 2)

    alpha = np.zeros(shape=[height, width], dtype=np.float_)
    base_square_points = np.array([
        [0, 0],
        [0, 1],
        [1, 1],
        [1, 0]
    ])
    offset = (half_min_size // 2, half_min_size // 4)
    edge_size = half_min_size
    square_points = base_square_points * edge_size + offset

    cv2.fillPoly(alpha, [square_points], 1.0, lineType=cv2.LINE_AA)

    radius = int(half_min_size * 0.3) // 2 * 2 + 1
    alpha = cv2.boxFilter(alpha, ddepth=-1, ksize=(radius, radius),
                          borderType=cv2.BORDER_REFLECT_101)
    alpha = alpha / np.max(alpha)
    alpha = 0.2 + alpha * 0.3

    result = np.ones(shape=(height, width, 4), dtype=np.float_)
    result[..., :3] = fg_color
    result[..., -1] = alpha
    result = float_to_uint8(result)
    cv2.imwrite(r'.\media\red-square.png', result)
    cv2.imwrite(r'.\media\red-square-color.png', result[..., :3])
    cv2.imwrite(r'.\media\red-square-alpha.png', result[..., -1])


def read_portrait_media():
    scale = 255.
    color = cv2.imread(r'.\portrait\color.png', cv2.IMREAD_UNCHANGED)
    alpha = cv2.imread(r'.\portrait\alpha.png', cv2.IMREAD_UNCHANGED)
    binary_alpha = cv2.imread(r'.\portrait\binary-alpha.png',
                              cv2.IMREAD_UNCHANGED)

    color = color / scale
    alpha = alpha / scale
    binary_alpha = binary_alpha / scale
    return color, alpha, binary_alpha


def read_color_media():
    scale = 255.
    red_square = cv2.imread(r'.\media\red-square.png', cv2.IMREAD_UNCHANGED)
    green_circle = cv2.imread(r'.\media\green-circle.png',
                              cv2.IMREAD_UNCHANGED)
    red_square = red_square / scale
    green_circle = green_circle / scale
    return red_square, green_circle


def read_ellipse_media():
    scale = 255.
    image = cv2.imread(r'.\media\ellipse.png', cv2.IMREAD_UNCHANGED)
    image = image / scale
    return image

main.py

# -*- coding: utf-8 -*-
import cv2
import numpy as np

from utils import float_to_uint8
from utils import alpha_premultiply
from utils import concat_color_alpha
from utils import mix
from utils import alpha_compositing

from media import decomposite_00148
from media import generate_circle_media
from media import generate_square_media
from media import generate_ellipse_media

from media import read_ellipse_media
from media import read_portrait_media
from media import read_color_media


def alpha_premultiply_00148():
    color, alpha, binary_alpha = read_portrait_media()

    # premultiply alpha
    premultiply_alpha = alpha_premultiply(color, alpha)
    premultiply_alpha = float_to_uint8(premultiply_alpha)
    cv2.imwrite(r'.\portrait\premultiply-alpha.png', premultiply_alpha)

    # premultiply binary alpha
    premultiply_binary_alpha = alpha_premultiply(color, binary_alpha)
    premultiply_binary_alpha = float_to_uint8(premultiply_binary_alpha)
    cv2.imwrite(r'.\portrait\premultiply-binary-alpha.png',
                premultiply_binary_alpha)


def alpha_premultiply_leak():
    image = read_ellipse_media()
    height, width = image.shape[:2]
    half_min_size = int(min(width, height) // 2)
    radius = int(half_min_size * 0.3) // 2 * 2 + 1
    straight_blur = cv2.GaussianBlur(image,
                                     ksize=(radius, radius),
                                     sigmaX=radius / 2.5,
                                     sigmaY=radius / 2.5,
                                     borderType=cv2.BORDER_REFLECT_101)
    straight_blur = float_to_uint8(straight_blur)

    premultiplied_image = image.copy()
    premultiplied_image[..., :3] = image[..., :3] * image[..., -1:]
    premultiplied_blur = cv2.GaussianBlur(premultiplied_image,
                                          ksize=(radius, radius),
                                          sigmaX=radius / 2.5,
                                          sigmaY=radius / 2.5,
                                          borderType=cv2.BORDER_REFLECT_101)
    premultiplied_blur = float_to_uint8(premultiplied_blur)

    image = float_to_uint8(image)
    premultiplied_image = float_to_uint8(premultiplied_image)

    cv2.imwrite(r'.\media\ellipse-straight-color.png', image[..., :3])
    cv2.imwrite(r'.\media\ellipse-alpha.png', image[..., -1])
    cv2.imwrite(r'.\media\ellipse-straight-blur.png', straight_blur)
    cv2.imwrite(r'.\media\ellipse-straight-blur-color.png',
                straight_blur[..., :3])
    cv2.imwrite(r'.\media\ellipse-blur-alpha.png',
                straight_blur[..., -1])
    cv2.imwrite(r'.\media\ellipse-premultiplied-color.png',
                premultiplied_image[..., :3])
    cv2.imwrite(r'.\media\ellipse-premultiplied-blur-color.png',
                premultiplied_blur[..., :3])

    premultiplied_blur = premultiplied_blur / 255.
    index = premultiplied_blur[..., -1] > 0
    premultiplied_blur[index, :3] = premultiplied_blur[index, :3] / \
                                    premultiplied_blur[index, -1:]
    premultiplied_blur = float_to_uint8(premultiplied_blur)
    cv2.imwrite(r'.\media\ellipse-premultiplied-blur.png', premultiplied_blur)


def naive_alpha_blending():
    color, alpha, binary_alpha = read_portrait_media()

    bg_color = np.array([0, 1., 0.])

    # naive alpha blending using normal alpha
    alpha_blending = mix(bg_color, color, alpha)
    alpha_blending = float_to_uint8(alpha_blending)
    cv2.imwrite(r'.\portrait\alpha-blending-with-bg.png', alpha_blending)

    # naive alpha blending using binary_alpha
    binary_alpha_blending = mix(bg_color, color, binary_alpha)
    binary_alpha_blending = float_to_uint8(binary_alpha_blending)
    cv2.imwrite(r'.\portrait\binary-alpha-blending-with-bg.png',
                binary_alpha_blending)


def alpha_blending_color_media():
    red_square, green_circle = read_color_media()
    bg_color = (1., 1., 1.)

    bg_green = mix(bg_color, green_circle[..., :3], green_circle[..., -1])
    bg_green_red = mix(bg_green, red_square[..., :3], red_square[..., -1])
    bg_green = float_to_uint8(bg_green)
    bg_green_red = float_to_uint8(bg_green_red)

    bg_red = mix(bg_color, red_square[..., :3], red_square[..., -1])
    bg_red_green = mix(bg_red, green_circle[..., :3], green_circle[..., -1])
    bg_red = float_to_uint8(bg_red)
    bg_red_green = float_to_uint8(bg_red_green)

    cv2.imwrite(r'.\media\alpha-blending-bg-green.png', bg_green)
    cv2.imwrite(r'.\media\alpha-blending-bg-green-red.png', bg_green_red)
    cv2.imwrite(r'.\media\alpha-blending-bg-red.png', bg_red)
    cv2.imwrite(r'.\media\alpha-blending-bg-red-green.png', bg_red_green)


def alpha_compositing_over_portrait():
    color, alpha, binary_alpha = read_portrait_media()

    bg_color = np.array([0, 1., 0.])
    bg_alpha = 1.0

    # composite - 0: alpha compositing, no premultiply, bg_alpha = 1.0
    composited_color, composited_alpha = alpha_compositing(
        color, alpha, bg_color, bg_alpha)
    image = concat_color_alpha(composited_color, composited_alpha)
    image = float_to_uint8(image)
    cv2.imwrite(r'.\portrait\alpha-compositing-over-bg-alpha=1.0.png', image)

    # composite - 0: alpha compositing, no premultiply, bg_alpha = 0.5
    bg_alpha = 0.5
    composited_color, composited_alpha = alpha_compositing(
        color, alpha, bg_color, bg_alpha)
    image = concat_color_alpha(composited_color, composited_alpha)
    image = float_to_uint8(image)
    cv2.imwrite(r'.\portrait\alpha-compositing-over-bg-alpha=0.5.png', image)


def alpha_compositing_over_color_media():
    red_square, green_circle = read_color_media()
    bg_color = (1., 1., 1.)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1])
    red_over_green = concat_color_alpha(color, alpha)
    red_over_green_over_bg, _ = alpha_compositing(red_over_green[..., :3],
                                                  red_over_green[..., -1],
                                                  bg_color,
                                                  1.0)

    red_over_green = float_to_uint8(red_over_green)
    red_over_green_over_bg = float_to_uint8(red_over_green_over_bg)
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green.png',
                red_over_green)
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green-color.png',
                red_over_green[..., :3])
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green-alpha.png',
                red_over_green[..., -1])
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green_over_bg.png',
                red_over_green_over_bg)

    color, alpha = alpha_compositing(green_circle[..., :3],
                                     green_circle[..., -1],
                                     red_square[..., :3],
                                     red_square[..., -1])
    green_over_red = concat_color_alpha(color, alpha)
    green_over_red_over_bg, _ = alpha_compositing(green_over_red[..., :3],
                                                  green_over_red[..., -1],
                                                  bg_color,
                                                  1.0)

    green_over_red = float_to_uint8(green_over_red)
    green_over_red_over_bg = float_to_uint8(green_over_red_over_bg)
    cv2.imwrite(r'.\media\alpha-compositing-green-over-red.png',
                green_over_red)
    cv2.imwrite(r'.\media\alpha-compositing-green-over-red-color.png',
                green_over_red[..., :3])
    cv2.imwrite(r'.\media\alpha-compositing-green-over-red-alpha.png',
                green_over_red[..., -1])
    cv2.imwrite(r'.\media\alpha-compositing-green_over_red_over_bg.png',
                green_over_red_over_bg)


def alpha_compositing_other_modes():
    red_square, green_circle = read_color_media()

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='in')
    red_in_green = concat_color_alpha(color, alpha)
    red_in_green = float_to_uint8(red_in_green)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='out')
    red_out_green = concat_color_alpha(color, alpha)
    red_out_green = float_to_uint8(red_out_green)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='atop')
    red_atop_green = concat_color_alpha(color, alpha)
    red_atop_green = float_to_uint8(red_atop_green)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='xor')
    red_xor_green = concat_color_alpha(color, alpha)
    red_xor_green = float_to_uint8(red_xor_green)

    cv2.imwrite(r'.\media\red_in_green.png', red_in_green)
    cv2.imwrite(r'.\media\red_out_green.png', red_out_green)
    cv2.imwrite(r'.\media\red_atop_green.png', red_atop_green)
    cv2.imwrite(r'.\media\red_xor_green.png', red_xor_green)


if __name__ == '__main__':
    decomposite_00148()
    generate_circle_media()
    generate_square_media()
    generate_ellipse_media()
    alpha_premultiply_00148()
    alpha_premultiply_leak()
    naive_alpha_blending()
    alpha_blending_color_media()
    alpha_compositing_over_portrait()
    alpha_compositing_over_color_media()
    alpha_compositing_other_modes()

clear.py

# -*- coding: utf-8 -*-
from utils import remove_file

if __name__ == '__main__':
    # generated by media.py
    remove_file(r'.\portrait\00148-small.png')
    remove_file(r'.\portrait\color.png')
    remove_file(r'.\portrait\alpha.png')
    remove_file(r'.\portrait\binary-alpha.png')
    remove_file(r'.\portrait\alpha-edge.png')
    remove_file(r'.\portrait\00148-remove-bg-small-new.png')

    remove_file(r'.\media\ellipse.png')

    remove_file(r'.\media\green-circle.png')
    remove_file(r'.\media\green-circle-color.png')
    remove_file(r'.\media\green-circle-alpha.png')
    remove_file(r'.\media\green-circle-premultiply.png')
    remove_file(r'.\media\green-circle-premultiply-color.png')

    remove_file(r'.\media\red-square.png')
    remove_file(r'.\media\red-square-color.png')
    remove_file(r'.\media\red-square-alpha.png')

    # generated by main.py
    remove_file(r'.\portrait\premultiply-alpha.png')
    remove_file(r'.\portrait\premultiply-binary-alpha.png')

    remove_file(r'.\media\ellipse-straight-color.png')
    remove_file(r'.\media\ellipse-alpha.png')
    remove_file(r'.\media\ellipse-straight-blur.png')
    remove_file(r'.\media\ellipse-straight-blur-color.png')
    remove_file(r'.\media\ellipse-blur-alpha.png')
    remove_file(r'.\media\ellipse-premultiplied-color.png')
    remove_file(r'.\media\ellipse-premultiplied-blur.png')
    remove_file(r'.\media\ellipse-premultiplied-blur-color.png')

    remove_file(r'.\portrait\alpha-blending-with-bg.png')
    remove_file(r'.\portrait\binary-alpha-blending-with-bg.png')
    remove_file(r'.\portrait\alpha-compositing-over-bg-alpha=1.0.png')
    remove_file(r'.\portrait\alpha-compositing-over-bg-alpha=0.5.png')
    remove_file(r'.\media\alpha-compositing-red-over-green.png')
    remove_file(r'.\media\alpha-compositing-red-over-green-color.png')
    remove_file(r'.\media\alpha-compositing-red-over-green-alpha.png')
    remove_file(r'.\media\alpha-compositing-red-over-green_over_bg.png')
    remove_file(r'.\media\alpha-compositing-green-over-red.png')
    remove_file(r'.\media\alpha-compositing-green-over-red-color.png')
    remove_file(r'.\media\alpha-compositing-green-over-red-alpha.png')
    remove_file(r'.\media\alpha-compositing-green_over_red_over_bg.png')
    remove_file(r'.\media\alpha-blending-bg-green.png')
    remove_file(r'.\media\alpha-blending-bg-green-red.png')
    remove_file(r'.\media\alpha-blending-bg-red.png')
    remove_file(r'.\media\alpha-blending-bg-red-green.png')

    remove_file(r'.\media\red_in_green.png')
    remove_file(r'.\media\red_out_green.png')
    remove_file(r'.\media\red_atop_green.png')
    remove_file(r'.\media\red_xor_green.png')

References

https://en.wikipedia.org/wiki/Alpha_compositing
https://ciechanow.ski/alpha-compositing/
https://graphics.stanford.edu/papers/merging-sig81/
https://graphics.stanford.edu/papers/merging-sig81/wallace-merging-sig81.pdf
https://dl.acm.org/doi/pdf/10.1145/964965.808606

Guess you like

Origin blog.csdn.net/bby1987/article/details/133936301