Reputation: 377
Friends! There are two input images. One background image, another mask image. I need to get the colored part of the mask.
What I need to get: background, mask, result image
But I get something completely different: background, mask, result image
My code is in C#:
//Read files
Mat img1 = CvInvoke.Imread(Environment.CurrentDirectory + "\\Test\\All1.jpg");
Mat img = CvInvoke.Imread(Environment.CurrentDirectory + "\\Test\\OriginalMask.jpg");
// Threshold and MedianBlur mask
CvInvoke.Threshold(img, img, 0, 255, Emgu.CV.CvEnum.ThresholdType.BinaryInv);
CvInvoke.MedianBlur(img, img, 13);
// without this conversion, an error appears: (mtype == CV_8U || mtype == CV_8S) && _mask.sameSize(*psrc1)
CvInvoke.CvtColor(img, img, Emgu.CV.CvEnum.ColorConversion.Rgb2Gray);
CvInvoke.BitwiseNot(img1, img1, img);
//Save file
img1.Save(Environment.CurrentDirectory + "\\Test\\Result.jpg");
First question: how can I achieve the result shown in the picture?
The second question: why do I get an error if I do not convert the mask: (mtype == CV_8U || mtype == CV_8S) && _mask.sameSize(*psrc1)
The third question: how to make a transparent background instead of a white background in the final image?
The solution does not have to be in C #. The solution is suitable in any programming language, since the syntax OpenCV is approximately the same. Thank you in advance.
Upvotes: 3
Views: 3754
Reputation: 1067
Same result in Python, if it can inspire you:
import cv2
import numpy as np
# Read files
img1 = cv2.imread("All1.jpg",cv2.IMREAD_COLOR);
img = cv2.imread("OriginalMask.jpg",cv2.IMREAD_GRAYSCALE) # loading in grayscale
# Threshold and MedianBlur mask
_, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # corrected to 127 instead of 0
img = cv2.medianBlur(img, 13)
# fill with white
dest= np.full(np.shape(img1),255,np.uint8)
# Assuming dst and src are of same sizes
# only copy values where the mask has color > 0
dest[img>0] = img1[img>0] # after @T.Kau's suggestion
cv2.imshow('dest',dest)
cv2.waitKey(0)
cv2.destroyAllWindows()
Upvotes: 1
Reputation: 18895
I will use C++ in my answer, since I'm most familiar with it.
Here is my suggestion:
// Load background as color image.
cv::Mat background = cv::imread("background.jpg", cv::IMREAD_COLOR);
// Load mask image as grayscale image.
cv::Mat mask = cv::imread("mask.jpg", cv::IMREAD_GRAYSCALE);
// Start time measurement.
auto start = std::chrono::system_clock::now();
// There are some artifacts in the JPG...
cv::threshold(mask, mask, 128, 255, cv::THRESH_BINARY);
// Initialize result image.
cv::Mat result = background.clone().setTo(cv::Scalar(255, 255, 255));
// Copy pixels from background to result image, where pixel in mask is 0.
for (int x = 0; x < background.size().width; x++)
for (int y = 0; y < background.size().height; y++)
if (mask.at<uint8_t>(y, x) == 0)
result.at<cv::Vec3b>(y, x) = background.at<cv::Vec3b>(y, x);
// End time measurement.
auto end = std::chrono::system_clock::now();
// Output duration duration.
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << elapsed_seconds.count() << "\n";
// Write result.
cv::imwrite("result.png", result);
// Start time measurement.
start = std::chrono::system_clock::now();
// Generate new image with alpha channel.
cv::Mat resultTransparent = cv::Mat(result.size(), CV_8UC4);
// Copy pixels in BGR channels from result to transparent result image.
// Where pixel in mask is not 0, set alpha to 0.
for (int x = 0; x < background.size().width; x++)
{
for (int y = 0; y < background.size().height; y++)
{
resultTransparent.at<cv::Vec4b>(y, x)[0] = result.at<cv::Vec3b>(y, x)[0];
resultTransparent.at<cv::Vec4b>(y, x)[1] = result.at<cv::Vec3b>(y, x)[1];
resultTransparent.at<cv::Vec4b>(y, x)[2] = result.at<cv::Vec3b>(y, x)[2];
if (mask.at<uint8_t>(y, x) != 0)
resultTransparent.at<cv::Vec4b>(y, x)[3] = 0;
else
resultTransparent.at<cv::Vec4b>(y, x)[3] = 255;
}
}
// End time measurement.
end = std::chrono::system_clock::now();
// Output duration duration.
elapsed_seconds = end - start;
std::cout << elapsed_seconds.count() << "\n";
// Write transparent result.
cv::imwrite("resultTransparent.png", resultTransparent);
Results in these two outputs (you won't see the transparency on the second image here on white StackOverflow background):
Upvotes: 3
Reputation: 643
Just to add a small detail to HanseHirse's answer:
If you add a Gaussian blur to the mask (as you did in your question with CvInvoke.MedianBlur(img, img, 13);
) the edges of the mask will be smoother and the output image will look nicer when placed on top of another image.
You can do this by simply setting the fourth channel of the output image directly to the blurred mask.
So instead of
if (mask.at<uint8_t>(y, x) != 0)
resultTransparent.at<cv::Vec4b>(y, x)[3] = 0;
else
resultTransparent.at<cv::Vec4b>(y, x)[3] = 255;
you could try
resultTransparent.at<cv::Vec4b>(y, x)[3] = mask.at<uint8_t>(y, x);
Upvotes: 1