Noise removal to create mask in OpenCV - c++

I need to create a mask to retrieve an object (foreground object) based on two related images.
Image 1:
[![enter image description here]
Image 2:
[![enter image description here]
The images contain a foreground object and a background with texture.
The two images are mostly the same except that in image2, the foreground object may have changed a little bit (it could have been rotated, translated or/and scaled).
Using OpenCV, I did the followings:
perform image alignment (using findTransformECC with param cv::MOTION_AFFINE) to get transformation of foreground;
do transformation to image1 (using cv::warpAffine with param cv::INTER_LINEAR + cv::WARP_INVERSE_MAP) based on the transform matrix above;
do absolute diff (cv::absdiff & cv::threshold with param cv::THRESH_BINARY_INV) between image2 and already transformed image1.
I think I am close to my goal but I still can not get clean mask of foreground object due to remaining noises on the background area.
What is the solution to remove all noise on the image_absdiff_invert.png (above) in order to create a clean mask of the foreground object ?

I just tried it.
Using morphological operations is often a bit tricky (trial and error) and gives me this result:
While using a median filter might be a good pre-processing (or maybe even enough for your contour extraction) and gives this result (this is just median blur from the input image, no morphological operations yet):
here's the test code:
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/maskNoise.png", CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat mask = input.clone();
cv::dilate(mask, mask, cv::Mat());
cv::dilate(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
cv::erode(mask, mask, cv::Mat());
//cv::erode(mask, mask, cv::Mat());
//cv::erode(mask, mask, cv::Mat());
//cv::dilate(mask, mask, cv::Mat());
//cv::dilate(mask, mask, cv::Mat());
cv::dilate(mask, mask, cv::Mat());
cv::dilate(mask, mask, cv::Mat());
cv::Mat median;
cv::medianBlur(input, median, 7);
cv::Mat resizedIn;
cv::Mat resizedMask;
cv::Mat resizedMedian;
cv::resize(mask, resizedMask, cv::Size(), 0.5, 0.5);
cv::resize(median, resizedMedian, cv::Size(), 0.5, 0.5);
cv::resize(input, resizedIn, cv::Size(), 0.5, 0.5);
cv::imshow("input", resizedIn);
cv::imshow("mask", resizedMask);
cv::imshow("median", resizedMedian);
cv::imwrite("C:/StackOverflow/Output/maskNoiseMorph.png", mask);
cv::imwrite("C:/StackOverflow/Output/maskNoiseMedian.png", median);
cv::waitKey(0);
return 0;
}

Related

How to extract a portion of an image given a particular color?

I have an image (shown below). I want to extract only the pink colored portion and remove the rest of the image. I have the RGB value of pink stored in an array. Is there any way that I can use bitwise_and on the image and the color so that I can single out the required portion in OpenCV?
Bitwise_and is not the right method for this, since it is bitwise. But there are of course methods in OpenCv for this basic task:
If you know the exact value, you can just use the CmpS method. If you want to find all pink colors within a certain range, use the InRangeS method. Optionally change the colorspace of the image first, e.g. if you want to specify your range in HSV space.
Since your green color is not uniform, but it ranges from:
// in BGR color space
Scalar low(182, 204, 168);
Scalar high(187, 207, 172);
// in HSV color space
Scalar low(72, 43, 204);
Scalar high(72, 45, 207);
you can use the inRange function. You can adjust the ranges according to the color you need to segment.
Usually HSV color space is better for segmentation tasks based on color, but in this case also the BGR color space is good enough.
This code shows how to get the binary mask of the desired color, and how to copy only the masked portion of the original image in both BGR and HSV color space.
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
// Load image
Mat3b img = imread("path_to_image");
{
// BGR color space
// Setup ranges
Scalar low(182, 204, 168);
Scalar high(187, 207, 172);
// Get binary mask
Mat1b mask;
inRange(img, low, high, mask);
// Initialize result image (all black)
Mat3b res(img.rows, img.cols, Vec3b(0, 0, 0));
// Copy masked part to result image
img.copyTo(res, mask);
imshow("Mask from BGR", mask);
imshow("Result from BGR", res);
waitKey();
}
{
// HSV color space
// Convert to HSV
Mat3b hsv;
cvtColor(img, hsv, COLOR_BGR2HSV);
// Setup ranges
Scalar low(72, 43, 204);
Scalar high(72, 45, 207);
// Initialize result image (all black)
// Get binary mask
Mat1b mask;
inRange(hsv, low, high, mask);
// Initialize result image (all black)
Mat3b res(img.rows, img.cols, Vec3b(0, 0, 0));
// Copy masked part to result image
img.copyTo(res, mask);
imshow("Mask from HSV", mask);
imshow("Result from HSV", res);
waitKey();
}
return 0;
}
Example of the mask:
Example of segmented image:

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);

OpenCV - Calculating edge strength of image

I am new to image processing and I need to calculate the strength of edges present in an image. Assume a situation where you have an image and you add blur effect to that image. The strength of the edges of these two images are different. I need to calculate that edge strength for both images separately.
So far I have got the canny edge detection of the image using the code below.
Mat src1;
src1 = imread("D.PNG", CV_LOAD_IMAGE_COLOR);
namedWindow("Original image", CV_WINDOW_AUTOSIZE);
imshow("Original image", src1);
Mat gray, edge, draw;
cvtColor(src1, gray, CV_BGR2GRAY);
Canny(gray, edge, 50, 150, 3);
edge.convertTo(draw, CV_8U);
namedWindow("image", CV_WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey(0);
return 0;
Is there any method to calculate of the strength of this edge image..?
mean will give you the mean value of your image. If you're using Canny as above you can do:
Scalar pixelMean = mean(draw);
To get the mean of only the edge pixels, you would use the image as the mask as well:
Scalar edgeMean = mean(draw, draw);
Unfortunately, since Canny sets all edge pixels to 255, your mean will always be 255. If this is the measure you're looking for, you'll probably want to use Sobel (after Gaussian Blur) and calculate the gradients to get the relative edge strengths.

GrabCut reading mask from PNG file in OpenCV (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.