【问题标题】:Set the background color for a PNG with transparency为具有透明度的 PNG 设置背景颜色
【发布时间】:2017-08-10 10:51:40
【问题描述】:

我正在加载具有透明平面的 PNG 图像。当转换为灰度时,图像中的透明区域显示为黑色,这似乎是默认背景。我需要它们是白色的。我能做什么?

[这不是关于如何保持透明度的常见问题。]

【问题讨论】:

  • 透明平面是否均匀? (即 alpha 分量可以取多少值?两个?)第一个想法:draw the image over a white background image 在转换为灰度之前。第二个想法:首先转换为灰度,然后遍历每个像素,并在新图像中将所有像素在原始图像中的alpha component 低于给定阈值的所有像素变为白色。
  • @GabrielDevillers:其实我不知道。我正在阅读 PNG 并获得带有黑色像素的灰度图像。我没有看到 alpha 平面。
  • @YvesDaoust 你如何加载图像?我猜是灰度,根据你的描述。您需要使用IMREAD_UNCHANGED 加载它,以便获得完整的 4 通道图像,并对其进行一些后期处理。
  • @DanMašek:是的,作为最后的手段,我会这样做。但我又试了一次:有没有办法指定黑色以外的背景颜色?
  • @YvesDaoust 我相信你的问题没有任何意义:如果你放弃 alpha 通道,那么你会得到一个由剩下的 3 个 RGB 通道定义的颜色。在您的情况下,透明(可能完全透明)像素的颜色是黑色,因此结果。换句话说:任何透明像素都具有 RGB 颜色,如果像素完全透明,则该颜色不可见,在您的情况下,透明像素的颜色为黑色。

标签: python opencv alpha-transparency


【解决方案1】:

最有效的方法(内存和 CPU)是让libPNG 来做,使用png_set_background

如果您不需要或无法处理您可以调用的 Alpha 通道 png_set_background() 通过对固定的合成来删除它 颜色。不要调用 png_set_strip_alpha() 来执行此操作 - 它会离开 此图像透明部分中的虚假像素值。

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

background_color 是一个 RGB 或灰度值,根据 数据格式 libpng 将为您生成。

不幸的是,围绕 libPNG 的 OpenCV 包装器不使用此功能,因此您必须自己修补一些基本支持(受限于将其他选项传递给 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 的解决方案。
  • 啊,我明白了。问题中没有语言标签,因此根据您的个人资料,我认为您需要 C++ :)
  • @YvesDaoust 添加了 Python 解决方案。不确定您是否可以轻松地做得更好。
【解决方案2】:

如果您读取带有imread 的PNG 而没有通过IMREAD_UNCHANGED,那么您将获得一个3 通道的BGR 图像。 如果有第四个 Alpha 通道(0 = 完全透明,255 = 完全可见),那么它会被裁剪作为文档put it

您在具有透明像素的地方得到黑色像素,这仅仅是因为像素的 BGR 部分呈现黑色。 (Vec3b(0, 0, 0))。

如果您不相信,请尝试打开 作为 BGRimread 没有IMREAD_UNCHANGED 参数)并显示(imshow 然后waitkey 两个图像如下:

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

第一个解决方案:使用 Michael Jepson 的overlayImage function

#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);
}

第二个解决方案:this answer by Rosa Gronchi

这个方案更轻量级(没有前景坐标,不需要分配背景图片)。

【讨论】:

    猜你喜欢
    • 2011-12-22
    • 2011-06-26
    • 2023-03-22
    • 1970-01-01
    • 2021-07-02
    • 2011-02-11
    • 1970-01-01
    • 2011-09-24
    • 2011-04-14
    相关资源
    最近更新 更多