Access pixel value of mask using opencv - c++

I got a problem where I need to access pixels of a opencv Mat image container.
I use opencv inRange function to create a mask. In that mask I need to check the value of different pixels, but I won't receive the values I expect to receive.
// convert image to hsv for better color-detection
cv::Mat img_hsv, maskR, maskY, mask1, mask2;
cv::cvtColor(image, img_hsv, cv::COLOR_BGR2HSV);
// Gen lower mask (0-5) and upper mask (175-180) of RED
cv::inRange(img_hsv, cv::Scalar(0, 50, 20), cv::Scalar(5, 255, 255), mask1);
cv::inRange(img_hsv, cv::Scalar(175, 50, 20), cv::Scalar(180, 255, 255), mask2);
// Merge the masks
cv::bitwise_or(mask1, mask2, maskR);
after that I try to read the pixel values where I got extremely high values and even nans, but most of them zeros, which is expected as the mask is only black and white
if (maskR.at<double>(position.x, position.y) == 255)
is there something I'm missing? I tried with double, uchar, int and float
when I print the mask, I can clearly see the 0 and 255 entries(no nans or strange numbers), but when I access them with the at() function, I wont get the same results.
The coordinates of the pixels should be in the range of the Mat as the dimension of the mask is 1080x1920 and non of the coordinates reach over that.
I got the dimension by using cv::size

I finally found the answer to my own question.
It works when I use uchar:
maskR.at<uchar>(position.x, position.y) == 255
I thought this wouldn't work because printing this with std::cout wouldn't give me an output, but the reason for that is that I forgot to cast uchar so it could be printed in the console

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 create new image using cv::Mat

I'm new to opencv and i'm trying on some sample codes.
in one code, Mat gr(row1,col1,CV_8UC1,scalar(0));
int x = gr.at<uchar> (row,col);
And in another one,
Mat grHistrogram(301,260,CV_8UC1,Scalar(0,0,0));
line(grHistrogram,pt1,pt2,Scalar(255,255,255),1,8,0);
Now my question is if i used scalar(0) instead of scalar(0,0,0) in second code, The code doesn't work.
1.Why this happening since, Both create a Mat image structure.
2.what is the purpose of const cv:Scalar &_s.
I search the Documentaion from Opencv site (opencv.pdf,opencv2refman.pdf) and Oreilly's Opencv book. But couldn't find a explained answer.
I think i'm using the Mat(int _rows,int _cols,int _type,const cv:Scalar &_s) struct.
First, you need the following information to create the image:
Width: 301 pixels
Height: 260 pixels
Each pixel value (intensity) is 0 ~ 255: an 8-bit unsigned integer
Supports all RGB colors: 3 channels
Initial color: black = (B, G, R) = (0, 0, 0)
You can create the Image using cv::Mat:
Mat grHistogram(260, 301, CV_8UC3, Scalar(0, 0, 0));
The 8U means the 8-bit Usigned integer, C3 means 3 Channels for RGB color, and Scalar(0, 0, 0) is the initial value for each pixel. Similarly,
line(grHistrogram,pt1,pt2,Scalar(255,255,255),1,8,0);
is to draw a line on grHistogram from point pt1 to point pt2. The color of line is white (255, 255, 255) with 1-pixel thickness, 8-connected line, and 0-shift.
Sometimes you don't need a RGB-color image, but a simple grayscale image. That is, use one channel instead of three. The type can be changed to CV_8UC1 and you only need to specify the intensity for one channel, Scalar(0) for example.
Back to your problem,
Why this happening since, both create a Mat image structure?
Because you need to specify the type of the Mat. Is it a color image CV_8UC3 or a grayscale image CV_8UC1? They are different. Your program may not work as you think if you use Scalar(255) on a CV_8UC3 image.
What is the purpose of const cv:Scalar &_s ?
cv::Scalar is use to specify the intensity value for each pixel. For example, Scalar(255, 0, 0) is blue and Scalar(0, 0, 0) is black if type is CV_8UC3. Or Scalar(0) is black if it's a CV_8UC1 grayscale image. Avoid mixing them together.
You can create single channel image or multi channel image.
creating single channel image : Mat img(500, 1000, CV_8UC1, Scalar(70));
creating multi channel image : Mat img1(500, 1000, CV_8UC3, Scalar(10, 100, 150));
you can see more example and detail from following page.
https://progtpoint.blogspot.com/2017/01/tutorial-3-create-image.html

OpenCV Remove smaller contours

I want to identify and extract the contour of the largest leaf of the following image using OpenCV and C++.
I applied Canny edge detector to the image and got the following result.
Canny(img_src, img_edge_detected, 20, 60, 3);
Now I want to extract the largest contour (largest leaf) form the image and draw the contour line, but the problem here is the edge line of the largest leaf is not continuous. So I looked in to dialate and morphological close but using those functions I couldn't get a good result to extract the area. Is there any way to get the largest contour in such image?
Note that here I cannot use template matching or any masking kind of things because my final intention is to built a system where a user can upload an image and get the species of the plant. So the system doesn't have any prior idea about the shape of the leaf that user is going to upload.
Please tell me how to find and draw the largest contour here if it is possible.
Thanks.
cant you use hsv color threshoding to track only that leaf and then you can straight away use minmaxloc function to get the area of the largest contour.just an idea try doing it like that.it will work.good luck
Same thing i will do in java please convert it into c++, here BGR to convert HSV then after apply the combination of the yellow, green and brown with specified range and simply perfom bitwise or operation. it will be give to you not zero pixles using opencv function Core.findNonZero(Mat src, Mat dst);
Imgproc.cvtColor(mRgba, mHSV, Imgproc.COLOR_BGR2HSV, 4);
//Yellow
Core.inRange(mHSV, new Scalar(25, 80, 80), new Scalar(36, 255, 255), yellow);
//Green
Core.inRange(mHSV, new Scalar(37, 80, 80), new Scalar(70, 255, 255), green);
//Brown
Core.inRange(mHSV, new Scalar(10, 80, 80), new Scalar(30, 200, 200), brown);
// logical OR mask
Core.bitwise_or(yellow, green, green);
Core.bitwise_or(green, brown, mask);
Imgproc.dilate(mask, mask, new Mat());
// Find non zero pixels
pts = Mat.zeros(mask.size(), mask.type());
Core.findNonZero(mask, pts);
return mask;

C++ | Change color in cv::mat with setTo

I have a cv::Mat file with vec3b values. These values are colours from an image. I'd like to change some colours in that image.
I know the setTo() function for normal matrix manipulation, but how do I use it for my Mat file?
I tried something like this:
image = image.setto(Vec3b(0,0,0), image == Vec3b(255,0,0))
Thx!
Given an image image, we want to find all the pixels in image that are equal to Scalar(255,0,0) and then set these pixels to Scalar(0,0,0).
First we need to obtain mask, such that a location in mask is set to 255 if the corresponding location in image equals Scalar(255,0,0), otherwise it is set to 0. This can be achieved with inRange() function.
Mat mask;
inRange(image, Scalar(255,0,0), Scalar(255,0,0), mask);
Now apply setTo() function to image.
image.setTo(Scalar(0,0,0), mask);

efficiently threshold red using HSV in OpenCV

I'm trying to threshold red pixels in a video stream using OpenCV. I have other colors working quite nicely, but red poses a problem because it wraps around the hue axis (ie. HSV(0, 255, 255) and HSV(179, 255, 255) are both red). The technique I'm using now is less than ideal. Basically:
cvInRangeS(src, cvScalar(0, 135, 135), cvScalar(20, 255, 255), dstA);
cvInRangeS(src, cvScalar(159, 135, 135), cvScalar(179, 255, 255), dstB);
cvOr(dstA, dstB, dst);
This is suboptimal because it requires a branch in the code for red (potential bugs), the allocation of two extra images, and two extra operations when compared to the easy case of blue:
cvInRangeS(src, cvScalar(100, 135, 135), cvScalar(140, 255, 255), dst);
The nicer alternative that occurred to me was to "rotate" the image's colors, so that the target hue is at 90 degrees. Eg.
int rotation = 90 - 179; // 179 = red
cvAddS(src, cvScalar(rotation, 0, 0), dst1);
cvInRangeS(dst1, cvScalar(70, 135, 135), cvScalar(110, 255, 255), dst);
This allows me to treat all colors similarly.
However, the cvAddS operation doesn't wrap the hue values back to 180 when they go below 0, so you lose data. I looked at converting the image to CvMat so that I could subtract from it and then use modulus to wrap the negative values back to the top of the range, but CvMat doesn't seem to support modulus. Of course, I could iterate over every pixel, but I'm concerned that that's going to be very slow.
I've read many tutorials and code samples, but they all seem to conveniently only look at ranges that don't wrap around the hue spectrum, or use solutions that are even uglier (eg. re-implementing cvInRangeS by iterating over every pixel and doing manual comparisons against a color table).
So, what's the usual way to solve this? What's the best way? What are the tradeoffs of each? Is iterating over pixels much slower than using built-in CV functions?
This is kind of late, but this is what I'd try.
Make the conversion: cvCvtColor(imageBgr, imageHsv, CV_RGB2HSV);
Note, RGB vs Bgr are purposefully being crossed.
This way, red color will be treated in a blue channel and will be centered around 170. There would also be a flip in direction, but that is OK as long as you know to expect it.
You can calculate Hue channel in range 0..255 with CV_BGR2HSV_FULL. Your original hue difference of 10 will become 14 (10/180*256), i.e. the hue must be in range 128-14..128+14:
public void inColorRange(CvMat imageBgr, CvMat dst, int color, int threshold) {
cvCvtColor(imageBgr, imageHsv, CV_BGR2HSV_FULL);
int rotation = 128 - color;
cvAddS(imageHsv, cvScalar(rotation, 0, 0), imageHsv);
cvInRangeS(imageHsv, cvScalar(128-threshold, 135, 135),
cvScalar(128+threshold, 255, 255), dst);
}
You won't believe but I had exactly the same issue and I solved it using simple iteration through Hue (not whole HSV) image.
Is iterating over pixels much slower than using built-in CV functions?
I've just tried to understood cv::inRange function but didn't get it at all (it seems that author used some specific iteration).
There is a really simple way of doing this.
First make two different color ranges
cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);
Then combine the two masks using addWeighted
cv::Mat red_hue_mask;
cv::addWeighted(lower_red_hue_range, 1.0, upper_red_hue_range, 1.0, 0.0, red_hue_mask);
Now you can just apply the mask to the image
cv::Mat result;
inputImageMat.copyTo(result, red_hue_mask);
I got the idea from a blog post I found
cvAddS(...) is equivalent, at element level, to:
out = static_cast<dest> ( in + shift );
This static_cast is the problem, because is clips/truncates the values.
A solution would be to shift the data from (0-180) to (x, 255), then apply a non-clipping add with overflow:
out = uchar(in + (255-180) + rotation );
Now you should be able to use a single InRange call, just shift your red interval according to the above formula