使用openCV将透明图像叠加到另一个图像上

Ant*_*udd 20 python opencv python-2.7

如何使用python中的openCV将透明PNG覆盖到另一个图像上而不会失去它的透明度?

import cv2

background = cv2.imread('field.jpg')
overlay = cv2.imread('dice.png')

# Help please

cv2.imwrite('combined.png', background)
Run Code Online (Sandbox Code Playgroud)

期望的输出: 在此输入图像描述

资料来源:

背景图片

覆盖

Ben*_*Ben 22

如果性能不是问题,那么您可以迭代叠加层的每个像素并将其应用到背景。这不是很有效,但它确实有助于理解如何使用 png 的 alpha 层。

慢速版本

import cv2

background = cv2.imread('field.jpg')
overlay = cv2.imread('dice.png', cv2.IMREAD_UNCHANGED)  # IMREAD_UNCHANGED => open image with the alpha channel

height, width = overlay.shape[:2]
for y in range(height):
    for x in range(width):
        overlay_color = overlay[y, x, :3]  # first three elements are color (RGB)
        overlay_alpha = overlay[y, x, 3] / 255  # 4th element is the alpha channel, convert from 0-255 to 0.0-1.0

        # get the color from the background image
        background_color = background[y, x]

        # combine the background color and the overlay color weighted by alpha
        composite_color = background_color * (1 - overlay_alpha) + overlay_color * overlay_alpha

        # update the background image in place
        background[y, x] = composite_color

cv2.imwrite('combined.png', background)
Run Code Online (Sandbox Code Playgroud)

结果: 组合图像

快速版

我在尝试将 png 叠加添加到实时视频源时偶然发现了这个问题。上面的解决方案对于这个来说太慢了。通过使用 numpy 的向量函数,我们可以使算法显着加快。

注意:这是我第一次真正涉足 numpy,因此可能有比我想出的更好/更快的方法。

import cv2
import numpy as np

background = cv2.imread('field.jpg')
overlay = cv2.imread('dice.png', cv2.IMREAD_UNCHANGED)  # IMREAD_UNCHANGED => open image with the alpha channel

# separate the alpha channel from the color channels
alpha_channel = overlay[:, :, 3] / 255 # convert from 0-255 to 0.0-1.0
overlay_colors = overlay[:, :, :3]

# To take advantage of the speed of numpy and apply transformations to the entire image with a single operation
# the arrays need to be the same shape. However, the shapes currently looks like this:
#    - overlay_colors shape:(width, height, 3)  3 color values for each pixel, (red, green, blue)
#    - alpha_channel  shape:(width, height, 1)  1 single alpha value for each pixel
# We will construct an alpha_mask that has the same shape as the overlay_colors by duplicate the alpha channel
# for each color so there is a 1:1 alpha channel for each color channel
alpha_mask = np.dstack((alpha_channel, alpha_channel, alpha_channel))

# The background image is larger than the overlay so we'll take a subsection of the background that matches the
# dimensions of the overlay.
# NOTE: For simplicity, the overlay is applied to the top-left corner of the background(0,0). An x and y offset
# could be used to place the overlay at any position on the background.
h, w = overlay.shape[:2]
background_subsection = background[0:h, 0:w]

# combine the background with the overlay image weighted by alpha
composite = background_subsection * (1 - alpha_mask) + overlay_colors * alpha_mask

# overwrite the section of the background image that has been updated
background[0:h, 0:w] = composite

cv2.imwrite('combined.png', background)
Run Code Online (Sandbox Code Playgroud)

快多少?在我的机器上,慢速方法需要约 3 秒,优化方法需要约 30 毫秒。所以大约快了 100 倍!

封装在一个函数中

此函数处理不同尺寸的前景和背景图像,还支持负偏移和正偏移,以任何方向将覆盖层移动到背景图像的边界上。

import cv2
import numpy as np

def add_transparent_image(background, foreground, x_offset=None, y_offset=None):
    bg_h, bg_w, bg_channels = background.shape
    fg_h, fg_w, fg_channels = foreground.shape

    assert bg_channels == 3, f'background image should have exactly 3 channels (RGB). found:{bg_channels}'
    assert fg_channels == 4, f'foreground image should have exactly 4 channels (RGBA). found:{fg_channels}'

    # center by default
    if x_offset is None: x_offset = (bg_w - fg_w) // 2
    if y_offset is None: y_offset = (bg_h - fg_h) // 2

    w = min(fg_w, bg_w, fg_w + x_offset, bg_w - x_offset)
    h = min(fg_h, bg_h, fg_h + y_offset, bg_h - y_offset)

    if w < 1 or h < 1: return

    # clip foreground and background images to the overlapping regions
    bg_x = max(0, x_offset)
    bg_y = max(0, y_offset)
    fg_x = max(0, x_offset * -1)
    fg_y = max(0, y_offset * -1)
    foreground = foreground[fg_y:fg_y + h, fg_x:fg_x + w]
    background_subsection = background[bg_y:bg_y + h, bg_x:bg_x + w]

    # separate alpha and color channels from the foreground image
    foreground_colors = foreground[:, :, :3]
    alpha_channel = foreground[:, :, 3] / 255  # 0-255 => 0.0-1.0

    # construct an alpha_mask that matches the image shape
    alpha_mask = np.dstack((alpha_channel, alpha_channel, alpha_channel))

    # combine the background with the overlay image weighted by alpha
    composite = background_subsection * (1 - alpha_mask) + foreground_colors * alpha_mask

    # overwrite the section of the background image that has been updated
    background[bg_y:bg_y + h, bg_x:bg_x + w] = composite
Run Code Online (Sandbox Code Playgroud)

用法示例:

background = cv2.imread('field.jpg')
overlay = cv2.imread('dice.png', cv2.IMREAD_UNCHANGED)  # IMREAD_UNCHANGED => open image with the alpha channel

x_offset = 0
y_offset = 0
print("arrow keys to move the dice. ESC to quit")
while True:
    img = background.copy()
    add_transparent_image(img, overlay, x_offset, y_offset)

    cv2.imshow("", img)
    key = cv2.waitKey()
    if key == 0: y_offset -= 10  # up
    if key == 1: y_offset += 10  # down
    if key == 2: x_offset -= 10  # left
    if key == 3: x_offset += 10  # right
    if key == 27: break  # escape
Run Code Online (Sandbox Code Playgroud)

偏移骰子

  • 很好的答案。一个小评论 - 您可以利用 numpy 的广播来提高速度。`alpha_mask = alpha_channel[:,:,np.newaxis]` (2认同)

Mal*_*ala 17

这个问题的正确答案太难了,所以即使这个问题真的很老,我还是发布了这个答案。您正在寻找的是“过度”合成,可以在维基百科上找到该​​算法:https : //en.wikipedia.org/wiki/Alpha_compositing

我远不是 OpenCV 的专家,但经过一些实验,这是我发现完成任务的最有效方法:

import cv2

background = cv2.imread("background.png", cv2.IMREAD_UNCHANGED)
foreground = cv2.imread("overlay.png", cv2.IMREAD_UNCHANGED)

# normalize alpha channels from 0-255 to 0-1
alpha_background = background[:,:,3] / 255.0
alpha_foreground = foreground[:,:,3] / 255.0

# set adjusted colors
for color in range(0, 3):
    background[:,:,color] = alpha_foreground * foreground[:,:,color] + \
        alpha_background * background[:,:,color] * (1 - alpha_foreground)

# set adjusted alpha and denormalize back to 0-255
background[:,:,3] = (1 - (1 - alpha_foreground) * (1 - alpha_background)) * 255

# display the image
cv2.imshow("Composited image", background)
cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)

  • @MitchMcMabers 如果您知道 OpenCV 内置可以执行“过度合成”,那么请务必将其发布,因为我毫不怀疑它会明显更快。但是,虽然“addWeighted()”可能比上面的代码快很多,但它实际上并没有做问题所要求的事情。 (8认同)
  • 几乎。imshow 被赋予“background”,其类型为 float64...,但值在 0..255 范围内,因此输出将被溢出。`.astype(np.uint8)` 或除以 255。 (2认同)

小智 14

import cv2

background = cv2.imread('field.jpg')
overlay = cv2.imread('dice.png')

added_image = cv2.addWeighted(background,0.4,overlay,0.1,0)

cv2.imwrite('combined.png', added_image)
Run Code Online (Sandbox Code Playgroud)

  • 这个答案不使用叠加层的 Alpha 通道 (6认同)
  • 我希望两层都为alpha = 1的情况怎么样?例如,我有一个前景,上面有一个球,透明的背景。如果此前景覆盖在纯蓝色背景上,则应遮盖球形部分。我怎样才能做到这一点? (5认同)

Der*_*rzu 8

您需要使用标志 IMREAD_UNCHANGED 打开透明 png 图像

Mat overlay = cv::imread("dice.png", IMREAD_UNCHANGED);
Run Code Online (Sandbox Code Playgroud)

然后拆分通道,将 RGB 分组并使用透明通道作为蒙版,这样做:

/**
 * @brief Draws a transparent image over a frame Mat.
 * 
 * @param frame the frame where the transparent image will be drawn
 * @param transp the Mat image with transparency, read from a PNG image, with the IMREAD_UNCHANGED flag
 * @param xPos x position of the frame image where the image will start.
 * @param yPos y position of the frame image where the image will start.
 */
void drawTransparency(Mat frame, Mat transp, int xPos, int yPos) {
    Mat mask;
    vector<Mat> layers;

    split(transp, layers); // seperate channels
    Mat rgb[3] = { layers[0],layers[1],layers[2] };
    mask = layers[3]; // png's alpha channel used as mask
    merge(rgb, 3, transp);  // put together the RGB channels, now transp insn't transparent 
    transp.copyTo(frame.rowRange(yPos, yPos + transp.rows).colRange(xPos, xPos + transp.cols), mask);
}
Run Code Online (Sandbox Code Playgroud)

可以这样调用:

drawTransparency(background, overlay, 10, 10);
Run Code Online (Sandbox Code Playgroud)

  • 使用透明层作为复制蒙版的好主意...... (2认同)
  • 此解决方案仅使用 alpha 通道作为 **二元** 掩码,而不是作为 **因子**。它无法处理**混合**。 (2认同)

小智 7

自从这个问题出现以来已经有一段时间了,但是我相信这是正确的简单答案,它仍然可以对某些人有所帮助。

background = cv2.imread('road.jpg')
overlay = cv2.imread('traffic sign.png')

rows,cols,channels = overlay.shape

overlay=cv2.addWeighted(background[250:250+rows, 0:0+cols],0.5,overlay,0.5,0)

background[250:250+rows, 0:0+cols ] = overlay
Run Code Online (Sandbox Code Playgroud)

这会将图像覆盖在背景图像上,如下所示:

忽略ROI矩形

在此处输入图片说明

请注意,根据我为其设置的坐标,我在背景图像的x [0-32]和y [250-282]部分中使用了尺寸为400x300的背景图像和尺寸为32x32的重叠图像首先计算混合度,然后将计算出的混合度放到我想要的图像部分。

(叠加层是从磁盘而不是背景图片本身加载的,不幸的是,叠加层图片有其自己的白色背景,因此您也可以在结果中看到这一点)

  • 这个答案不使用叠加层的 Alpha 通道 (4认同)

Cri*_*cia 7

以下代码将使用叠加层图像的Alpha通道将其正确混合到背景图像中,x并使用和y设置叠加层图像的左上角。

import cv2
import numpy as np

def overlay_transparent(background, overlay, x, y):

    background_width = background.shape[1]
    background_height = background.shape[0]

    if x >= background_width or y >= background_height:
        return background

    h, w = overlay.shape[0], overlay.shape[1]

    if x + w > background_width:
        w = background_width - x
        overlay = overlay[:, :w]

    if y + h > background_height:
        h = background_height - y
        overlay = overlay[:h]

    if overlay.shape[2] < 4:
        overlay = np.concatenate(
            [
                overlay,
                np.ones((overlay.shape[0], overlay.shape[1], 1), dtype = overlay.dtype) * 255
            ],
            axis = 2,
        )

    overlay_image = overlay[..., :3]
    mask = overlay[..., 3:] / 255.0

    background[y:y+h, x:x+w] = (1.0 - mask) * background[y:y+h, x:x+w] + mask * overlay_image

    return background
Run Code Online (Sandbox Code Playgroud)

此代码将使背景发生变化,因此,如果您希望保留原始背景图像,请创建一个副本。