简体   繁体   English

为具有透明度的 PNG 设置背景颜色

[英]Set the background color for a PNG with transparency

I am loading PNG images that have a transparency plane.我正在加载具有透明平面的 PNG 图像。 When converted to grayscale, the transparent areas in the image appear as black, which seems to be the default background.当转换为灰度时,图像中的透明区域显示为黑色,这似乎是默认背景。 I need them to be white instead.我需要它们是白色的。 What can I do ?我能做些什么 ?

[This is not the usual question on how to preserve transparency.] [这不是关于如何保持透明度的常见问题。]

The most effective way (memory and CPU) would be to let libPNG do it, using png_set_background :最有效的方法(内存和 CPU)是让libPNG来做,使用png_set_background

If you don't need, or can't handle, the alpha channel you can call png_set_background() to remove it by compositing against a fixed color.如果您不需要或无法处理 alpha 通道,您可以调用 png_set_background() 通过对固定颜色进行合成来移除它。 Don't call png_set_strip_alpha() to do this - it will leave spurious pixel values in transparent parts of this image.不要调用 png_set_strip_alpha() 来执行此操作 - 它会在该图像的透明部分留下虚假像素值。

 png_set_background(png_ptr, &background_color, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1);

The background_color is an RGB or grayscale value according to the data format libpng will produce for you. background_color 是根据 libpng 将为您生成的数据格式的 RGB 或灰度值。

Unfortunately, the OpenCV wrapper around libPNG doesn't use this, so you'd have to patch in some rudimentary support yourself (hindered by the limited ability to pass additional options to imread ).不幸的是,围绕 libPNG 的 OpenCV 包装器不使用它,因此您必须自己修补一些基本支持(受限于将附加选项传递给imread的能力有限)。

Other possible approach would be to just write your own simple image loader using libPNG for this specific purpose.其他可能的方法是为此特定目的使用 libPNG 编写您自己的简单图像加载器。

If you can afford some waste, load it as BGRA, and do some post-processing.如果你能负担得起一些废物,请将其加载为 BGRA,并进行一些后期处理。 However I'd go a step further than the code referred to by Gabriel and incorporate the color conversion in it.但是,我会比Gabriel引用的代码更进一步,并在其中包含颜色转换。

void remove_transparency(cv::Mat const& source
    , cv::Mat& destination
    , uint8_t background_color)
{
    CV_Assert(source.type() == CV_8UC4);

    destination.create(source.rows, source.cols, CV_8UC1);

    auto it_src(source.begin<cv::Vec4b>()), it_src_end(source.end<cv::Vec4b>());
    auto it_dest(destination.begin<uint8_t>());

    std::transform(it_src, it_src_end, it_dest
        , [background_color](cv::Vec4b const& v) -> uchar
        {
            // Conversion constants taken from cvtColor docs...
            float gray(v[0] * 0.114f + v[1] * 0.587f + v[2] * 0.299f);
            float alpha(v[3] / 255.0f);
            return cv::saturate_cast<uchar>(gray * alpha + background_color * (1 - alpha));
        }
        );
}

Of course, this is still single threaded, so let's leverage cv::parallel_for_ to improve it a bit further.当然,这仍然是单线程的,所以让我们利用cv::parallel_for_进一步改进它。

class ParallelRemoveTransparency
    : public cv::ParallelLoopBody
{
public:
    ParallelRemoveTransparency(cv::Mat const& source
        , cv::Mat& destination
        , uint8_t background_color)
        : source_(source)
        , destination_(destination)
        , background_color_(background_color)
    {
        CV_Assert(source.size == destination.size);
    }

    virtual void operator()(const cv::Range& range) const
    {
        cv::Mat4b roi_src(source_.rowRange(range));
        cv::Mat1b roi_dest(destination_.rowRange(range));

        std::transform(roi_src.begin(), roi_src.end(), roi_dest.begin()
            , [this](cv::Vec4b const& v) -> uint8_t {
                float gray(v[0] * 0.114f + v[1] * 0.587f + v[2] * 0.299f);
                float alpha(v[3] / 255.0f);
                return cv::saturate_cast<uint8_t>(gray * alpha + background_color_ * (1 - alpha));
            }
            );
    }

private:
    cv::Mat const& source_;
    cv::Mat& destination_;
    uint8_t background_color_;
};

void remove_transparency(cv::Mat const& source
    , cv::Mat& destination
    , uint8_t background_color)
{
    CV_Assert(source.type() == CV_8UC4);

    destination.create(source.rows, source.cols, CV_8UC1);

    ParallelRemoveTransparency parallel_impl(source, destination, background_color);
    cv::parallel_for_(cv::Range(0, source.rows), parallel_impl);
}

It turns out you need this in Python.事实证明你在 Python 中需要这个。 Here's a quick little draft of an alternative:这是一个替代方案的快速小草稿:

import numpy as np
import cv2

def remove_transparency(source, background_color):
    source_img = cv2.cvtColor(source[:,:,:3], cv2.COLOR_BGR2GRAY)
    source_mask = source[:,:,3]  * (1 / 255.0)

    background_mask = 1.0 - source_mask

    bg_part = (background_color * (1 / 255.0)) * (background_mask)
    source_part = (source_img * (1 / 255.0)) * (source_mask)

    return np.uint8(cv2.addWeighted(bg_part, 255.0, source_part, 255.0, 0.0))


img = cv2.imread('smile.png', -1)
result = remove_transparency(img, 255)

cv2.imshow('', result)
cv2.waitKey()

If you read a PNG with imread without passing IMREAD_UNCHANGED then you will have a 3 channel BGR image.如果您在没有传递IMREAD_UNCHANGED的情况下使用imread读取 PNG,那么您将拥有 3 通道 BGR 图像。 If there was a fourth alpha channel (0 = fully transparent, 255 = fully visible) then it gets cropped as the documentation put it .如果有第四个 Alpha 通道(0 = 完全透明,255 = 完全可见),那么它会按照文档中的说明进行裁剪

You are getting black pixels where you had transparent pixels simply because the BGR part of the pixel gives a black color.您在具有透明像素的地方得到黑色像素,这仅仅是因为像素的 BGR 部分呈现黑色。 ( Vec3b(0, 0, 0) ). ( Vec3b(0, 0, 0) )。

If you are not convinced, try to open as BGR ( imread wihout IMREAD_UNCHANGED parameter) and display ( imshow then waitkey both images below:如果您不相信,请尝试以BGR的形式打开( imread IMREAD_UNCHANGED参数)并显示( imshow然后waitkey下面的两个图像:

来自维基百科的原始标志 在此处输入图像描述

While they look similar on this page or in Gimp, The first should have a black background whereas the second one should have a red background.虽然它们在此页面或 Gimp 中看起来很相似,但第一个应该有黑色背景,而第二个应该有红色背景。

First solution: use Michael Jepson's overlayImage function第一种解决方案:使用 Michael Jepson 的overlayImage 函数

#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgcodecs.hpp>
int main(int argc, char** argv ) {
   cv::Mat img_4_channels;
   img_4_channels = cv::imread(argv[1], cv::IMREAD_UNCHANGED); // gives 8UC4
   // img_4_channels = cv::imread(argv[1]); // inappropriate: gives 8UC3

   cv::Mat background = cv::Mat(img_4_channels.size(), CV_8UC3, cv::Vec3b(255, 255, 255)); // white background
   overlayImage(background, img_4_channels, img_3_channels, cv::Point2i(0, 0));

   cv::imshow("3 channels", img_3_channels);
}

Second solution: this answer by Rosa Gronchi第二种解决方案:Rosa Gronchi 的这个答案

This solution is more lightweight (no coordinate of foreground, no need to allocate a background image).这个方案更轻量级(没有前景坐标,不需要分配背景图)。

You can use the following code您可以使用以下代码

def read_transparent_png(filename, hexcode):
    image_4channel = cv2.imread(filename, cv2.IMREAD_UNCHANGED)
    alpha_channel = image_4channel[:, :, 3]
    rgb_channels = image_4channel[:, :, :3]
    white_background_image = np.zeros((image_4channel.shape[0], image_4channel.shape[1],3), dtype=np.uint8)
    rgb = tuple(int(hexcode[i:i+2], 16) for i in (0, 2, 4))
    RED, GREEN, BLUE = rgb[0], rgb[1], rgb[2]
    white_background_image[::] = (BLUE, GREEN, RED)
    alpha_factor = alpha_channel[:, :, np.newaxis].astype(np.float32) / 255.0
    alpha_factor = np.concatenate(
        (alpha_factor, alpha_factor, alpha_factor), axis=2)
    base = rgb_channels.astype(np.float32) * alpha_factor
    white = white_background_image.astype(np.float32) * (1 - alpha_factor)
    final_image = base + white
    return final_image.astype(np.uint8)

here hexcode is the hexadecimal code of the colour that you want to set as background for transparent PNG.这里的hexcode是要设置为透明 PNG 的背景颜色的十六进制代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM