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

8
我正在加载具有透明层的PNG图像。将其转换为灰度图像时,图像中的透明区域显示为黑色,默认背景色。我需要它们变成白色。我该怎么做?
[这不是关于如何保留透明度的常规问题。]

1
透明平面是否均匀?(即 alpha 组件可以取多少个值?两个?)第一个想法:在转换为灰度图像之前,在白色背景图像上绘制图像。第二个想法:先转换为灰度图像,然后迭代每个像素,并在新图像中将原始图像中其 alpha 组件 低于给定阈值的所有像素变为白色。 - Gabriel Devillers
@GabrielDevillers:实际上我不知道。我正在读取PNG文件,并获得带有黑色像素的灰度图像。我看不到Alpha平面。 - user1196549
@YvesDaoust 你如何加载图像?根据您的描述,我猜测是灰度。您需要使用“IMREAD_UNCHANGED”加载它,这样您就可以获得完整的4通道图像,并对其进行一些后处理。 - Dan Mašek
@DanMašek:是的,作为最后的办法,我会这样做。但我再试一次:有没有一种指定背景颜色而不是黑色的方法? - user1196549
据我所知,PngDecoder 中没有支持这方面的内容。我猜需要在某个地方调用 png_set_background - Dan Mašek
显示剩余5条评论
3个回答

1

最有效的方式(内存和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. Don't call png_set_strip_alpha() to do this - it will leave spurious pixel values in transparent parts of this image.

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.

不幸的是,OpenCV对libPNG的包装器并未使用此功能,因此您需要自己补丁来支持一些基本功能(受限于无法向imread传递其他选项的能力)。

另一个可能的方法是,只需编写自己的简单图像加载程序,以便针对特定目的使用libPNG。

如果您可以承担某些浪费,请将其加载为BGRA,并进行一些后期处理。但是,我会比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));
        }
        );
}

当然,目前这仍然是单线程的,因此让我们利用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);
}

原来你需要用Python实现这个功能。这里有一个替代方案的简短草稿:
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()

感谢提供所有有价值的信息。我正在使用Python进行开发,因此实现这个方案有些困难,这就是为什么我正在寻找更OpenCVish(指类似于OpenCV风格的解决方案)的方法。 - user1196549
啊,我明白了。问题中没有语言标签,所以根据你的个人资料,我假设你想要C++ :) - Dan Mašek
@YvesDaoust 添加了一个Python解决方案。不确定你能否轻松地做得更好。 - Dan Mašek

0

如果你使用imread读取PNG文件时没有传递IMREAD_UNCHANGED参数,那么你将得到一个三通道的BGR图像。如果有第四个alpha通道(0表示完全透明,255表示完全可见),那么它会被裁剪掉,正如文档所述

你在原本是透明像素的地方得到了黑色像素,这是因为像素的BGR部分给出了黑色颜色(Vec3b(0, 0, 0))。

如果你还不相信,请尝试以BGR格式打开(使用imread而不带IMREAD_UNCHANGED参数),并显示(使用imshow然后waitkey)下面的两张图片:

original logo from wikipedia enter image description here

虽然它们在此页面或Gimp中看起来相似,但第一个应该有黑色背景,而第二个应该有红色背景。

第一种解决方案:使用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);
}

第二种解决方案:Rosa Gronchi的这个答案

这个解决方案更加轻量级(没有前景的坐标,也不需要分配背景图像)。


0
您可以使用以下代码。
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)

这里的hexcode是您想要设置为透明PNG背景的颜色的十六进制代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接