簡體   English   中英

為具有透明度的 PNG 設置背景顏色

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

我正在加載具有透明平面的 PNG 圖像。 當轉換為灰度時,圖像中的透明區域顯示為黑色,這似乎是默認背景。 我需要它們是白色的。 我能做些什么 ?

[這不是關於如何保持透明度的常見問題。]

最有效的方法(內存和 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 是根據 libpng 將為您生成的數據格式的 RGB 或灰度值。

不幸的是,圍繞 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()

如果您在沒有傳遞IMREAD_UNCHANGED的情況下使用imread讀取 PNG,那么您將擁有 3 通道 BGR 圖像。 如果有第四個 Alpha 通道(0 = 完全透明,255 = 完全可見),那么它會按照文檔中的說明進行裁剪

您在具有透明像素的地方得到黑色像素,這僅僅是因為像素的 BGR 部分呈現黑色。 ( Vec3b(0, 0, 0) )。

如果您不相信,請嘗試以BGR的形式打開( imread IMREAD_UNCHANGED參數)並顯示( imshow然后waitkey下面的兩個圖像:

來自維基百科的原始標志 在此處輸入圖像描述

雖然它們在此頁面或 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 的這個答案

這個方案更輕量級(沒有前景坐標,不需要分配背景圖)。

您可以使用以下代碼

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 的背景顏色的十六進制代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM