How to absdiff() a grayscale image with a mask in CV_8UC1 - c++

invert_mask is CV_8UC1 with some filled contours (pixel value 255) and src is grayscale
I want to invert the colors under the mask in src
The ouput image almost looks right except for the areas under the mask.. They are just black. It looks like only the white color is turned to black but the black color stays black
I don't want to binarize the src image because I have to do some processing on it after this
cv::absdiff(invert_mask, 255 - src, src);
src = 255 - src;
If I do this the white color is converted to black but the black/gray color is untouched
cv::absdiff(invert_mask, src, src);
src
invert_mask

To achieve the desired color replacement, the following short snippet using simple thresholding should do the trick:
cv::Mat src = cv::imread("src.png", cv::IMREAD_GRAYSCALE);
cv::Mat invert_mask = cv::imread("invert_mask.png", cv::IMREAD_GRAYSCALE);
cv::Mat textInMask = src.clone().mul(invert_mask / 255);
cv::threshold(textInMask, textInMask, 200, 255, cv::THRESH_BINARY);
cv::Mat output = src.clone() + invert_mask - textInMask;
Output image:
The quality may be improved, this is just the basic concept.

Related

OpenCV - segmenting tree from the image

I am trying to segment the color green in the HSV-color space. I have this image of a tree and I would only like the upper part of the tree to be left.
This is the image I am starting from and the mask I obtain is just an entirely black image
This is my current code:
Mat input = imread(image_location);
imshow("input img",input); waitKey(0);
//convert image to HSV
Mat input_hsv;
cvtColor(input,input_hsv,COLOR_BGR2HSV);
vector<Mat>channels;
split(input_hsv, channels);
Mat H = channels[0];
Mat S = channels[1];
Mat V = channels[2];
Mat mask2;
inRange(input_hsv, Scalar(70, 0, 0), Scalar(143, 255, 255), mask2);
imshow("mask2", mask2);waitKey(0);
Normally the color green in HSV ranges from +/- 70 to 140.
But it doesn't seem to work at all. Could somebody help?
You are working in 8U. Thus, the H component which is normally in degrees [0,360) is compressed to fit 255 by halving.
See docs: 8-bit images: V←255V,S←255S,H←H/2(to fit to 0 to 255)
So the original H green range [70,140] should be halved to [35,70].

how to change the black pixels in the below image to red pixels using opencv libraries with c++

I want to change the black pixels in the image to red pixels, such that the ball should look white and red. I want to use OpenCV libraries and code it in C++. I have tried converting the image to RGB.
Common approach is to threshold the image, so in your case you would say that each pixel with an intensity less than some threshold will be considered as being black and then recolored to red. One way to find a good threshold (that divides the image's pixel into two classes ("more black" and "more white") is OTSU thresholding:
int main()
{
cv::Mat input = cv::imread("../inputData/ball_thresholding.jpg");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
cv::Mat mask;
// compute inverse thresholding (dark areas become "active" pixel in the mask) with OTSU thresholding:
double grayThres = cv::threshold(gray, mask, 0, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
// color all masked pixel red:
input.setTo(cv::Scalar(0,0,255), mask);
// compute median filter to remove the whitish black parts and darker white parts
cv::imshow("input", input);
cv::waitKey(0);
return 0;
}
Giving this mask:
and this result:
For this image, the threshold that was computed by OTSU is 127, which means that each grayscale pixel intensity of 127 or less (or less than 127, I'm not sure) will be recolored to red.
If you want to keep the shading effect withing the black/red region, you can remove input.setTo(cv::Scalar(0,0,255), mask); lind and replace it by:
// keep the shading:
for(int j=0; j<input.rows; ++j)
for(int i=0; i<input.cols; ++i)
{
if(mask.at<unsigned char>(j,i))
{
input.at<cv::Vec3b>(j,i)[2] = 255;
}
}
which will result int:
cv::Mat imBW = imread('bwImg.jpg',CV_LOAD_IMAGE_GRAYSCALE);
cv::Mat RGB_img = cv::Mat(imBW.rows, imBW.cols, CV_8UC3);
cv::Mat R_channel = 255-imBW;
cv::Mat B_channel = cv::Mat::zeros(imBW.rows, imBW.cols, CV_8UC1);
cv::Mat G_channel = cv::Mat::zeros(imBW.rows, imBW.cols, CV_8UC1);
vector<cv::Mat> channels;
channels.push_back(B_channel);
channels.push_back(G_channel);
channels.push_back(R_channel);
cv::merge(channels, RGB_img);

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:

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

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.