Reputation:
I am loading PNG images that have a transparency plane. 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.]
Upvotes: 8
Views: 5099
Reputation: 1
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.
Upvotes: 0
Reputation: 19071
The most effective way (memory and CPU) would be to let libPNG do it, using 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.
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
).
Other possible approach would be to just write your own simple image loader using libPNG for this specific purpose.
If you can afford some waste, load it as BGRA, and do some post-processing. However I'd go a step further than the code referred to by Gabriel and incorporate the color conversion in it.
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.
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. 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()
Upvotes: 1
Reputation: 4022
If you read a PNG with imread
without passing IMREAD_UNCHANGED
then you will have a 3 channel BGR image.
If there was a fourth alpha channel (0 = fully transparent, 255 = fully visible) then it gets cropped as the documentation put it.
You are getting black pixels where you had transparent pixels simply because the BGR part of the pixel gives a black color. (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:
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.
#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 solution is more lightweight (no coordinate of foreground, no need to allocate a background image).
Upvotes: 0