[英]How to set the background color when rendering a PNG using PythonMagick
[英]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 中看起来很相似,但第一个应该有黑色背景,而第二个应该有红色背景。
#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);
}
这个方案更轻量级(没有前景坐标,不需要分配背景图)。
您可以使用以下代码
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.