GrabCut reading mask from PNG file in OpenCV (C++) - c++

The implementation of this functionality seems pretty straightforward in Python, as shown here: http://docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_grabcut/py_grabcut.html
Yet, when I tried to do exactly the same in C++, I get bad arguments error (for the grabcut function). How to put the mask image in the right format?
I am a newbie at this, so I'd be very thankful if someone could help me understand better. Thank you!
Here's what I have so far:
Mat image;
image= imread(file);
Mat mask;
mask.setTo( GC_BGD );
mask = imread("messi5.png");
Mat image2 = image.clone();
// define bounding rectangle
cv::Rect rectangle(startX, startY, width, height);
cv::Mat result; // segmentation result (4 possible values)
cv::Mat bgModel,fgModel; // the models (internally used)
//// GrabCut segmentation that works, but with a rectangle, not with the mask I need
//cv::grabCut(image, // input image
// result, // segmentation result
// rectangle,// rectangle containing foreground
// bgModel,fgModel, // models
// 1, // number of iterations
// cv::GC_INIT_WITH_RECT); // use rectangle
grabCut( image, mask, rectangle, bgModel, fgModel, 1, GC_INIT_WITH_MASK);
cv::compare(mask,cv::GC_PR_FGD,mask,cv::CMP_EQ);
cv::Mat foreground(image.size(),CV_8UC3,cv::Scalar(255,255,255));
image.copyTo(foreground,mask); // bg pixels not copied
namedWindow( "Display window", WINDOW_AUTOSIZE );
imshow( "Display window", foreground );
waitKey(0);
return 0;
}

It looks like you have misunderstood the guide, repeated here from the linked guide in the question:
# newmask is the mask image I manually labelled
newmask = cv2.imread('newmask.png',0)
# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
this is not what you have done i'm afraid. For a start you seem to have set the mask to the rgb image:
mask = imread("messi5.png");
whereas is should be set to the mask image:
mask = imread("newmask.png",CV_LOAD_IMAGE_GRAYSCALE);
EDIT from comments:
from a pure red mask painted over the image (an actual mask would be better).
maskTmp = imread("messi5.png");
std::vector<cv::Mat> channels(3)
split( messi5, channels);
cv::Mat maskRed = channels[2];
now threshold on the red channel to get your binary mask.

Related

Background Substruction between images using transparent background

I am trying to get the difference between two images using the following code:
Mat getWithoutBackground(Mat img, Mat back)
{
_backgroundSubVal = 50;
cv::Mat diff;
cv::absdiff(img, back, diff);
cv::Mat diff1Channel;
cv::cvtColor(diff, diff1Channel, CV_BGRA2GRAY);
cv::Mat mask = diff1Channel > _backgroundSubVal;
cv::Mat data(img.size(), CV_8UC4, Scalar(255,255,255,0));
// copy masked area
img.copyTo(data, mask);
cv::imwrite(""1.png", data);
return similarRegions;
}
first is the background:
second is the actual image:
and here is the result:
and I am almost getting the correct solution but i would have liked to have a transparent background instead of black.
(I dont want to remove all of the picture's black colors)
I thought that the 4'th parameter in the Scalar should have done the trick for having a transparent background but i guess that I am missing something..
cv::Mat data(img.size(), CV_8UC4, Scalar(255,255,255,0));
any ideas what am i doing wrong?

OpenCV - Cropping non rectangular region from image using C++

How can I crop a non rectangular region from image?
Imagine I have four points and I want to crop it, this shape wouldn't be a triangle somehow!
For example I have the following image :
and I want to crop this from image :
How can I do this?
regards..
The procedure for cropping an arbitrary quadrilateral (or any polygon for that matter) part of an image is summed us as:
Generate a "mask". The mask is black where you want to keep the image, and white where you don't want to keep it
Compute the "bitwise_and" between your input image and the mask
So, lets assume you have an image. Throughout this I'll use an image size of 30x30 for simplicity, you can change this to suit your use case.
cv::Mat source_image = cv::imread("filename.txt");
And you have four points you want to use as the corners:
cv::Point corners[1][4];
corners[0][0] = Point( 10, 10 );
corners[0][1] = Point( 20, 20 );
corners[0][2] = Point( 30, 10 );
corners[0][3] = Point( 20, 10 );
const Point* corner_list[1] = { corners[0] };
You can use the function cv::fillPoly to draw this shape on a mask:
int num_points = 4;
int num_polygons = 1;
int line_type = 8;
cv::Mat mask(30,30,CV_8UC3, cv::Scalar(0,0,0));
cv::fillPoly( mask, corner_list, &num_points, num_polygons, cv::Scalar( 255, 255, 255 ), line_type);
Then simply compute the bitwise_and of the image and mask:
cv::Mat result;
cv::bitwise_and(source_image, mask, result);
result now has the cropped image in it. If you want the edges to end up white instead of black you could instead do:
cv::Mat result_white(30,30,CV_8UC3, cv::Scalar(255,255,255));
cv::bitwise_and(source_image, mask, result_white, mask);
In this case we use bitwise_and's mask parameter to only do the bitwise_and inside the mask. See this tutorial for more information and links to all the functions I mentioned.
You may use cv::Mat::copyTo() like this:
cv::Mat img = cv::imread("image.jpeg");
// note mask may be single channel, even if img is multichannel
cv::Mat mask = cv::Mat::zeros(img.rows, img.cols, CV_8UC1);
// fill mask with nonzero values, e.g. as Tim suggests
// cv::fillPoly(...)
cv::Mat result(img.size(), img.type(), cv::Scalar(255, 255, 255));
img.copyTo(result, mask);

substract region from image and keep the borders

I have a 200x200 pixels image and I want to keep only the data for a certain region inside it.
Check the following image:
The whole out square is 200x200 pixels.I want to remove from it the smaller square(white).So ,keep only the information that is included in the blue area.But , I want to keep the 200x200 dimensions.
I tried:
Mat whiteArea;
whiteArea = ImageInitial( Range(50,200) , Range(50,200) );
Size size(200,200);
Mat dst;
resize(whiteArea,dst,size);
Mat FinalImage;
subtract(ImageInitial,dst,FinalImage);
I am resizing the white area because I want to substract it from the initial image.
My problem is that it gives me the initial image.
Maybe the resize is the problem .but then how to substract 2 different sized images?
try to use subimages or use a mask:
// use a roi (nice if your target area is rectangular and you know the position)
Rect whiteArea = Rect(50,50, 200,200); // creates a roi of the inner rect
Mat FinalImage = ImageInitial.clone();
// now set the roi area to zero:
FinalImage (whiteArea).setTo(Scalar(0,0,0));
// or FinalImage(whiteArea) = FinalImage(whiteArea) - FinalImage(whiteArea);
imshow("version 1 with subimage", FinalImage);
waitkey(0);
// or use a mask (nice if that region can has arbitrary shape etc and you have to extract it first):
Scalar lowerColorBound = Scalar(x,y,z); //some BGR values to find the color you want to eliminate
Scalar upperColorBound = Scalar(a,b,c); //some BGR values to find the color you want to eliminate
Mat mask;
inRange(ImageInitial, lowerColorBound, upperColorBound mask)
// use the mask for subtraction:
subtract(ImageInitial, ImageInitial, FinalImage , mask);
imshow("version 2 with mask", FinalImage);
waitkey(0);

OpenCV keep background transparent during warpAffine

I create a Bird-View-Image with the warpPerspective()-function like this:
warpPerspective(frame, result, H, result.size(), CV_WARP_INVERSE_MAP, BORDER_TRANSPARENT);
The result looks very good and also the border is transparent:
Bird-View-Image
Now I want to put this image on top of another image "out". I try doing this with the function warpAffine like this:
warpAffine(result, out, M, out.size(), CV_INTER_LINEAR, BORDER_TRANSPARENT);
I also converted "out" to a four channel image with alpha channel according to a question which was already asked on stackoverflow:
Convert Image
This is the code: cvtColor(out, out, CV_BGR2BGRA);
I expected to see the chessboard but not the gray background. But in fact, my result looks like this:
Result Image
What am I doing wrong? Do I forget something to do? Is there another way to solve my problem? Any help is appreciated :)
Thanks!
Best regards
DamBedEi
I hope there is a better way, but here it is something you could do:
Do warpaffine normally (without the transparency thing)
Find the contour that encloses the image warped
Use this contour for creating a mask (white values inside the image warped, blacks in the borders)
Use this mask for copy the image warped into the other image
Sample code:
// load images
cv::Mat image2 = cv::imread("lena.png");
cv::Mat image = cv::imread("IKnowOpencv.jpg");
cv::resize(image, image, image2.size());
// perform warp perspective
std::vector<cv::Point2f> prev;
prev.push_back(cv::Point2f(-30,-60));
prev.push_back(cv::Point2f(image.cols+50,-50));
prev.push_back(cv::Point2f(image.cols+100,image.rows+50));
prev.push_back(cv::Point2f(-50,image.rows+50 ));
std::vector<cv::Point2f> post;
post.push_back(cv::Point2f(0,0));
post.push_back(cv::Point2f(image.cols-1,0));
post.push_back(cv::Point2f(image.cols-1,image.rows-1));
post.push_back(cv::Point2f(0,image.rows-1));
cv::Mat homography = cv::findHomography(prev, post);
cv::Mat imageWarped;
cv::warpPerspective(image, imageWarped, homography, image.size());
// find external contour and create mask
std::vector<std::vector<cv::Point> > contours;
cv::Mat imageWarpedCloned = imageWarped.clone(); // clone the image because findContours will modify it
cv::cvtColor(imageWarpedCloned, imageWarpedCloned, CV_BGR2GRAY); //only if the image is BGR
cv::findContours (imageWarpedCloned, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
// create mask
cv::Mat mask = cv::Mat::zeros(image.size(), CV_8U);
cv::drawContours(mask, contours, 0, cv::Scalar(255), -1);
// copy warped image into image2 using the mask
cv::erode(mask, mask, cv::Mat()); // for avoid artefacts
imageWarped.copyTo(image2, mask); // copy the image using the mask
//show images
cv::imshow("imageWarpedCloned", imageWarpedCloned);
cv::imshow("warped", imageWarped);
cv::imshow("image2", image2);
cv::waitKey();
One of the easiest ways to approach this (not necessarily the most efficient) is to warp the image twice, but set the OpenCV constant boundary value to different values each time (i.e. zero the first time and 255 the second time). These constant values should be chosen towards the minimum and maximum values in the image.
Then it is easy to find a binary mask where the two warp values are close to equal.
More importantly, you can also create a transparency effect through simple algebra like the following:
new_image = np.float32((warp_const_255 - warp_const_0) *
preferred_bkg_img) / 255.0 + np.float32(warp_const_0)
The main reason I prefer this method is that openCV seems to interpolate smoothly down (or up) to the constant value at the image edges. A fully binary mask will pick up these dark or light fringe areas as artifacts. The above method acts more like true transparency and blends properly with the preferred background.
Here's a small test program that warps with transparent "border", then copies the warped image to a solid background.
int main()
{
cv::Mat input = cv::imread("../inputData/Lenna.png");
cv::Mat transparentInput, transparentWarped;
cv::cvtColor(input, transparentInput, CV_BGR2BGRA);
//transparentInput = input.clone();
// create sample transformation mat
cv::Mat M = cv::Mat::eye(2,3, CV_64FC1);
// as a sample, just scale down and translate a little:
M.at<double>(0,0) = 0.3;
M.at<double>(0,2) = 100;
M.at<double>(1,1) = 0.3;
M.at<double>(1,2) = 100;
// warp to same size with transparent border:
cv::warpAffine(transparentInput, transparentWarped, M, transparentInput.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
// NOW: merge image with background, here I use the original image as background:
cv::Mat background = input;
// create output buffer with same size as input
cv::Mat outputImage = input.clone();
for(int j=0; j<transparentWarped.rows; ++j)
for(int i=0; i<transparentWarped.cols; ++i)
{
cv::Scalar pixWarped = transparentWarped.at<cv::Vec4b>(j,i);
cv::Scalar pixBackground = background.at<cv::Vec3b>(j,i);
float transparency = pixWarped[3] / 255.0f; // pixel value: 0 (0.0f) = fully transparent, 255 (1.0f) = fully solid
outputImage.at<cv::Vec3b>(j,i)[0] = transparency * pixWarped[0] + (1.0f-transparency)*pixBackground[0];
outputImage.at<cv::Vec3b>(j,i)[1] = transparency * pixWarped[1] + (1.0f-transparency)*pixBackground[1];
outputImage.at<cv::Vec3b>(j,i)[2] = transparency * pixWarped[2] + (1.0f-transparency)*pixBackground[2];
}
cv::imshow("warped", outputImage);
cv::imshow("input", input);
cv::imwrite("../outputData/TransparentWarped.png", outputImage);
cv::waitKey(0);
return 0;
}
I use this as input:
and get this output:
which looks like ALPHA channel isn't set to ZERO by warpAffine but to something like 205...
But in general this is the way I would do it (unoptimized)

Getting masked area to be transparent?

So far i have managed to use masks and get the second image from the first. But what i want is the black area in second image to be transparent (i.e the output i an trying to get is the third image) Here is the code so far. Please advice me on this.
EDIT: Third one is from photoshop
//imwrite parameters
compression_params.push_back(CV_IMWRITE_JPEG_QUALITY);
compression_params.push_back(100);
//reading image to be masked
image = imread(main_img, -1);
//CV_LOAD_IMAGE_COLOR
namedWindow("output", WINDOW_NORMAL);
//imshow("output", image);
//Creating mask image with same size as original image
Mat mask(image.rows, image.cols, CV_8UC1, Scalar(0));
// Create Polygon from vertices
ROI_Vertices.push_back(Point2f(float(3112),float(58)));
ROI_Vertices.push_back(Point2f(float(3515),float(58)));
ROI_Vertices.push_back(Point2f(float(3515),float(1332)));
ROI_Vertices.push_back(Point2f(float(3112),float(958)));
approxPolyDP(ROI_Vertices, ROI_Poly, 1, true);
// Fill polygon white
fillConvexPoly(mask, &ROI_Poly[0] , ROI_Poly.size(), 255, 8, 0);
//imshow("output", mask);
// Create new image for result storage
imageDest = cvCreateMat(image.rows, image.cols, CV_8UC4);
// Cut out ROI and store it in imageDest
image.copyTo(imageDest, mask);
imwrite("masked.jpeg", imageDest, compression_params);
imshow("output", imageDest);
cvWaitKey(0);
This can be done by first setting its alpha value to 0 of the regions that you want to make them fully transparent (255 for others), and then save it to PNG.
To set the alpha value of pixel-(x,y), it can be done:
image.at<cv::Vec4b>(y, x)[3] = 0;
PS: you need to convert it to 4-channel format first if the image is not currently. For example:
cv::cvtColor(image, image, CV_BGR2BGRA);
Updated: It will be easier if you have already computed the mask for the ROI region, where you can simply merge it with the original image (assume having 3 channels) to get the final result. Like:
cv::Mat mask; // 0 for transparent regions, 255 otherwise (serve as the alpha channel)
std::vector<cv::Mat> channels;
cv::split(image, channels);
channels.push_back(mask);
cv::Mat result;
cv::merge(channels, result);