I have figured out how to do alpha blend with transparency masks, but as with most things OpenCV, I suspect I'm not doing it as efficiently as could be.
I know how to copy an image with a mask onto another using cv::Mat::copyTo()
. But when I do that, the 2nd image is copied 100% onto the destination. What I want is to copy it at 30% or some other value, like in the 3rd "blended" window in this example:
Note how the text "starry night blend test" is partially see-through in that final blended image?
The only way I could figure out how to do that is like this:
// compile with: g++ test.cpp $(pkg-config --libs opencv)
#include <opencv2/opencv.hpp>
int main()
{
const auto fontface = cv::FONT_HERSHEY_PLAIN;
const auto fontscale = 3.0;
const auto fontthickness = 3;
const std::string text("starry night blend test");
const cv::Scalar purple(255, 0, 255);
const cv::Scalar white(255, 255, 255);
cv::Mat background = cv::imread("starrynight.jpg");
// Create the layers on which we'll write the text. Layers start out as
// pure black.
cv::Mat font_layer = cv::Mat::zeros(background.size(), CV_8UC3);
cv::Mat mask_layer = cv::Mat::zeros(background.size(), CV_8UC1); // <- note the mask is 1 channel
// Write the text in colour on the "font" layer. In my actual application
// there are many layers, and it is more complicated than just a line of
// text.
const cv::Point p(20, 100);
cv::putText(font_layer, text, p, fontface, fontscale, purple, fontthickness, cv::LINE_8);
// I also have a mask of the "font" layer. In this example, the easiest
// way to produce that mask is to write out the same text, with the exact
// same options, but onto the single channel mat.
cv::putText(mask_layer, text, p, fontface, fontscale, white, fontthickness, cv::LINE_8);
// The function copyTo() has an optional "mask" parameter which we can use
// to copy parts of an image onto another. In this case, we copy the text
// onto a clone of the image.
cv::Mat tmp = background.clone();
font_layer.copyTo(tmp, mask_layer);
// This is the cool part of the code. We do an alpha blend between the
// two mats. One of which is just the background, and the other is the
// background with bright purple text. When blended, it will appear as
// if the text layer is semi-transparent. 0.3 == 30%
const double alpha = 0.3;
const double beta = 1.0 - alpha;
cv::Mat blended;
cv::addWeighted(tmp, alpha, background, beta, 0, blended);
cv::imshow("tmp" , tmp );
cv::imshow("blended" , blended );
cv::imshow("background" , background);
cv::waitKey(0);
return 0;
}
The thing I don't like about this solution is the multiple copies:
- First I copy the entire background image into "tmp"
- Then I copy the font layer onto tmp using the mask
- Then I "copy" again when I call into
cv::addWeighted()
.
Is this the way I should be doing this, or is there a better solution?