I am trying to pass in a HSV frame from a video to the function, but the function does not seem to do anything to it. What am I doing wrong? The function is supposed to go through each pixel, and depending on its hue range supposed to make it black or white, leaving me with a binary image. Instead it doesn't seem to affect the HSV image at all....
Thanks
PS sorry for the bad code formatting, StackOverflow isn't allowing me to post the original format.
void sort (IplImage *skinmask)
{
for (int row=0; row<=skinmask->height;row++)
{
uchar* pixelrow=(uchar*)(skinmask->imageData+(row*(skinmask->widthStep)));
for (int column=0; column<=skinmask->width; column++)
{
if (6<pixelrow[3*column]<36)
{
pixelrow[3*column]=256;
pixelrow[(3*column)+1]=256;
pixelrow[(3*column)+2]=256;
}
else
{
pixelrow[3*column]=0;
pixelrow[(3*column)+1]=0;
pixelrow[(3*column)+2]=0;
}
column++;
}
row++;
}
cvMorphologyEx(skinmask,skinmask,NULL,NULL,CV_MOP_CLOSE,1);
}
Doing an operation like thresholding pixel-by-pixel is usually the wrong way to go about achieving this in OpenCV - there are functions that work on whole image arrays that are simpler and are already optimized for speed.
In this case try first splitting the image to separate out the H/S/V channels, then threshold on the Hue channel to get a mask (you may have to use the intersection of two masks, which you can do using a multiply or "bitwise and") - the resulting mask is your black and white image.
(I realise I've linked to the C++ documentation, but I'm sure you can find the equivalent functions in the old-style OpenCV docs)
Update
Ok, I'll try to write some code to show what I mean. I also found the function I was looking for, which is better than Threshold, it is InRangeS. This lets you put upper and lower bounds on all the channels at once, and it applies them all into your mask for you.
void HSVImageToMask(IplImage * image, cvMat * mask)
/* mask should be the same size as image, and of type CV_8UC1 */
/* e.g. cvMat * mask = cvCreateMat(image->width, image->height, CV8UC1); */
{
double hMin = 6;
double hMax = 36;
double sMin = 10; /* not sure what value you need */
double sMax = 245; /* not sure what value you need */
double vMin = 0;
double vMax = 255;
CvScalar hsvMin = cvScalar(hMin, sMin, vMin);
CvScalar hsvMax = cvScalar(hMax, sMax, vMax);
cvInRangeS(image, hsvMin, hsvMax, mask);
}
PS. I figured out the problem with your original code - you should be using 255 instead of 256 as your "white" value. This method is still better though :)
PPS. We didn't need them after all but for future reference:
"bitwise and":
cvAnd(const CvArr* src1, const CvArr* src2, CvArr* dst)
If you have two black and white masks, this will give you the intersection. Use cvOr to get the Union.
Related
I have a question for us. I'm a newbe of OpenCV and I need to understand if that lib can help me to reach my goals.
I need to use OpenCV to open a Tiff file (big Tiff file) and split it on two different file with a mask like that Mask, in the end the file 1 have pixel black and the file 2 have the negative - pixel white of the original image.
Any ideas or example for me?
Thank you all!
To read the file, you can use the function imread. This stores it in a cv::Mat object. Since your mask is black and white, I would read the mask-image as a grayscale using IMREAD_GRAYSCALE. This gives you each pixel with a value from 0-255. That should cover the first part of your question.
I have to admit I am having trouble understandig your question, but I expect you want to create two images. The first contains all the pixels where your mask has a black pixel. The second one contains an image where in the mask all the pixels are white.
You could look at this thread. Additionally I would like to give you the way that I would do it.
The problem you would run in to is that your .tiff-image has a different type than your chessboard. Tiff is probably CV_8UC3 and chessboard is probably CV_8UC1. But this should be easily solvable.
I think you would probably want to look at each individual pixel and leave the be if, at that same pixel of the chessboard, your color is white. Then if it is not, make that pixel from your original pixel black. I have not tested this, but it would look something like this.
for (int i = 0; i < originalImage.rows; i++) {
for (int j = 0; j < originalImage.cols; j++) {
if (chessboard.at<uchar>(Point(j, i)) != 255) {
originalImage.at<Vec3b>(Point(j, i)) = Scalar(0, 0, 0);
}
else {
// Do nothing.
}
}
}
Scalar is used, since the originalImage has three channels instead of one. I hope this helps!
Try this to create the mask:
cv::Mat tiff;
cv::Mat maskDark = tiff == 0; // comparison like '< 10' also works
cv::Mat maskDark = tiff == 255;
How can I achieve the values of the RGB channels as
Float data type
Intensity range within 0-255
I used CV_32FC4 as the matrix type since I'll perform floating-point mathematical operations to implement Daltonization. I was expecting that the intensity range is the same with the intensity range of the RGB Channels in CV_8UC3, just having a different data type. But when I printed the matrix I noticed that the intensities of the channels are not within 0-255. I realized that it due to the range of the float matrix type.
Mat mFrame(height, width, CV_32FC4, (unsigned char *)pNV21FrameData);
for(int y = 0 ; y < height ; y++){
for(int x = 0 ; x < width ; x++){
Vec4f BGRA = mFrame.at<Vec4f>(y,x);
// Algorithm Implementation
mFrame.at<Vec4f>(y,x) = BGRA;
}
}
Mat mResult;
mFrame.convertTo(mResult, CV_8UC4, 1.0/255.0);
I need to manipulate the pixels like BGRA[0] = BGRA[0] * n; then assign it back to the matrix.
By your comments and the link in it I see that the data comes in BGRA. The data is in uchar.
I assume this from this line:
Mat mResult(height, width, CV_8UC4, (unsigned char *)poutPixels);
To solve this you can create the matrix and then convert it to float.
Mat mFrame(height, width, CV_8UC4, (unsigned char *)pNV21FrameData);
Mat mFloatFrame;
mFrame.convertTo(mFloatFrame, CV_32FC4);
Notice that this will keep the current ranges (0-255) if you need another one (like 0-1) you may put the scaling factor.
Finally you can convert back, but beware that this function does saturate_cast. If you have an specific way you want to manage the overflow or the decimals, you will have to do it before converting it.
Mat mResult;
mFloatFrame.convertTo(mResult, CV_8UC4);
Note that 1.0/255.0 is not there, since the data is already in the range of 0-255 (at least before the operations).
One final comment, the link in your comments use IplImage and other old C (deprecated) versions of OpenCv. If you are working in c++, stick to the c++ versions like Mat. This is not in the code you show here, but in the you linked. This comment is more for you to avoid future headaches.
I am having some issues with my sobel_y (and sobel_x, but I figure they are having the same issue) filter in that it keeps giving me an image that it basically only black and white. I am having to rewrite this function for a class, so no I cannot use the built-in one, and had it working, minus some minor tweaks because the output image looked a little strange with still being black and white even though it was supposed to be converted back. I figured out how to fix that, and in the process I messed with something and broke it and cannot seem to get it back to working even with the black and white image output only. I keep getting a black image, with some white lines here and there near the top. I have tried changing the Mat grayscale type (third parameter) to all different values, as my professor mentioned in the class that we are using 32 bit floating point images, but that did not help either.
Even though the issue occurs after running the Studentfilter2D, I think it is a problem with the grayscaling of the image, although whenever I debug, it seems to work just fine. This is also because I have 2 other filtering functions I had to write that use Studentfilter2D, and they both give me the expected results. My sobel_y function is shown below:
// Convert the image in bgr to grayscale OK to use the OpenCV function.
// Find the coefficients used by the OpenCV function, and give a link where you found it.
// Note: This student function expects the matrix gray to be preallocated with the same width and
// height, but with 1 channel.
void BGR2Gray(Mat& bgr, Mat& gray)
{
// Y = .299 * R + .587 * G + .114 * B, from http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html#cvtcolor
// Some extra assistance, for the third parameter for the InputArray, from http://docs.opencv.org/trunk/modules/core/doc/basic_structures.html#inputarray
// Not sure about the fourth parameter, but was just trying it to see if that may be the issue as well
cvtColor(bgr, gray, CV_BGR2GRAY, 1);
return;
}
// Convolve image with kernel - this routine will be called from the other
// subroutines! (gaussian, sobel_x and sobel_y)
// image is single channel. Do not use the OpenCV filter2D!!
// Implementation can be with the .at or similar to the
// basic method found in the Chapter 2 of the OpenCV tutorial in CANVAS,
// or online at the OpenCV documentation here:
// http://docs.opencv.org/doc/tutorials/core/mat-mask-operations/mat-mask operations.html
// In our code the image and the kernel are both floats (so the sample code will need to change)
void Studentfilter2D (Mat& image, Mat& kernel)
{
int kCenterX = kernel.cols / 2;
int kCenterY = kernel.rows / 2;
// Algorithm help from http://www.songho.ca/dsp/convolution/convolution.html
for (int iRows = 0; iRows < image.rows; iRows++)
{
for (int iCols = 0; iCols < image.cols; iCols++)
{
float result = 0.0;
for (int kRows = 0; kRows < kernel.rows; kRows++)
{
// Flip the rows for the convolution
int kRowsFlipped = kernel.rows - 1 - kRows;
for (int kCols = 0; kCols < kernel.cols; kCols++)
{
// Flip the columns for the convolution
int kColsFlipped = kernel.cols - 1 - kCols;
// Indices of shifting around the convolution
int iRowsIndex = iRows + kRows - kCenterY;
int iColsIndex = iCols + kCols - kCenterX;
// Check bounds using the indices
if (iRowsIndex >= 0 && iRowsIndex < image.rows && iColsIndex >= 0 && iColsIndex < image.cols)
{
result += image.at<float>(iRowsIndex, iColsIndex) * kernel.at<float>(kRowsFlipped, kColsFlipped);
}
}
}
image.at<float>(iRows, iCols) = result;
}
}
return;
}
void sobel_y (Mat& image, int)
{
// Note, the filter parameter int is unused.
Mat mask = (Mat_<float>(3, 3) << 1, 2, 1,
0, 0, 0,
-1, -2, -1) / 3;
//Mat grayscale(image.rows, image.cols, CV_32FC1);
BGR2Gray(image, image);
Studentfilter2D(image, mask);
// Here is the documentation on normalize http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#normalize
normalize(image, image, 0, 1, NORM_MINMAX);
cvtColor(image, image, CV_GRAY2BGR);
return;
}
Like I said, I had this working before, just looking for some fresh eyes to look at it and see what I may be missing. I have been looking at this same code so much for the past 4 days that I think I am just missing things. In case anyone is wondering, I have also tried changing the mask values of the filter, but to no avail.
There are two things that are worth mentioning.
The first is that you are not taking proper care of the type of your matrices/images.
The input to Studentfilter2D in sobel_y is an 8-bit grayscale image of type CV_8UC1 meaning that the data is an array of unsigned char.
Your Studentfilter2D function, however, is indexing this input image as though it was of type float. This means it is picking the wrong pixels to work with.
If the above does not immediately solve your problem, you should consider the range of your final derivative image. Since it is a derivative it will no longer be in the range [0, 255]. Instead, it might even contain negative numbers. When you try to visualize this, you will run into problems unless you first normalize your image.
There are built in functions to do this in OpenCV if you look around in the documentation.
I'm trying to have a webcam take a picture of someone's face in BGR, convert the picture into HSV, and analyze these HSV values that will later be used in a skin detection algorithm. Unfortunately, the picture seems to be analyzed in BGR, even after I try to convert it using cvtColor().
I use the code below to test whether or not I'm using the right color space. Note the part where I try to set saturation and value to 0:
Mat faceROI = findFace(first); //basic Mat, region of interest for face (code not included)
Mat temp;
faceROI.convertTo(temp, CV_8UC3); //making sure this has right no. of channels and such
CvScalar s;
IplImage face_ipl = temp; //new header
IplImage* aNew = cvCreateImage(cvGetSize(&face_ipl), face_ipl.depth, 3);
cvCvtColor(&face_ipl, aNew, CV_BGR2HSV);
for(int x = 0; x < faceROI.cols; x++){
for (int y = 0; y < faceROI.rows; y++){
s = cvGet2D(aNew, x, y);
//vvvvvvvvvvv
s.val[1] = 0; //should be saturation
s.val[2] = 0; //should be value
//^^^^^^^^^^^
cvSet2D(aNew, x, y, s);
}
}
Mat again(aNew); //<--- is this where something is set back to BGR?
imshow("white", again);
cvReleaseImage(&aNew);
This produces a completely blue picture of my face, so it seems likes I'm editing the G and R channels of a BGR image, instead of the S and V channels of an HSV image. (I'd post the image, but this is my first post so I don't have enough reputation yet.)
Does anybody know why this is happening? Any and all thoughts are appreciated.
You're mixing up the C++ Mat style with the old C IplImage*, this makes it confusing to see what exactly is going on. Here is the code to turn inputImage into HSV:
Mat fullImageHSV;
cvtColor(inputImage, fullImageHSV, CV_BGR2HSV);
Be aware that the OpenCV HSV values are H from 0-180 while S and V are from 0-255 while other programs tend to use different values. ALso note that OpenCV is unable to show HSV images normally, this distorts the color because they are being interpreted as RGB.
The plan
My project is able to capture the bitmap of a target window and convert it into an IplImage, and then display that image in a cvNamedWindow, where further processing can take place.
For the sake of testing, I've loaded an image into MSPaint like so:
The user is then allowed to click and drag the mouse over any number of pixels within the image to create a vector<cv::Scalar_<BYTE>> containing these RGB color values.
Then, with the help of ColorRGBToHLS(), this array is then sorted from left to right by hue, like so:
// PixelColor is just a cv::Scalar_<BYTE>
bool comparePixelColors( PixelColor& pc1, PixelColor& pc2 ) {
WORD h1 = 0, h2 = 0;
WORD s1 = 0, s2 = 0;
WORD l1 = 0, l2 = 0;
ColorRGBToHLS(RGB(pc1.val[2], pc1.val[1], pc1.val[0]), &h1, &l1, &s1);
ColorRGBToHLS(RGB(pc2.val[2], pc2.val[1], pc2.val[0]), &h2, &l2, &s2);
return ( h1 < h2 );
}
//..(elsewhere in code)
std::sort(m_colorRange.begin(), m_colorRange.end(), comparePixelColors);
...and then shown in a new cvNamedWindow, which looks something like:
The problem
Now, the idea here is to create a binary threshold image (or "mask") where this selected range of colors become white, and the rest of the source image becomes black... similar to the way the "Select By Color" tool operates in GIMP, or the "magic wand" tool works in Photoshop... except instead of limiting ourselves to a specific contoured selection, we are literally operating on the image as a whole.
I've read into cvInRangeS, and it sounds like it's precisely what I need.
However, and for whatever reason, the thresholded image always ends up being totally black...
VOID ShowThreshedImage(const IplImage* src, const PixelColor& min, const PixelColor& max)
{
IplImage* imgHSV = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
cvCvtColor(src, imgHSV, CV_RGB2HLS);
cvNamedWindow("T1");
cvShowImage("T1", imgHSV); // <-- Shows up like the image below
IplImage* imgThreshed = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvInRangeS(imgHSV, min, max, imgThreshed);
cvNamedWindow("T2");
cvShowImage("T2", imgThreshed); // <-- SHOWS UP PITCH BLACK!
}
This is what the "T1" window ends up looking like (which I suppose is correct?):
Bearing in mind that because the color range vector is stored as RGB (and that OpenCV internally reverses this order into BGR), I have converted the min/max values into HLS before passing them into ShowThreshedImage() like so:
CvScalar rgbPixelToHSV(const PixelColor& pixelColor)
{
WORD h = 0, s = 0, l = 0;
ColorRGBToHLS(RGB(pixelColor.val[2], pixelColor.val[1], pixelColor.val[0]), &h, &l, &s);
return PixelColor(h, s, l);
}
//...(elsewhere in code)
if(m_colorRange.size() > 0)
m_minHSV = rgbPixelToHSV(m_colorRange[0]);
if(m_colorRange.size() > 1)
m_maxHSV = rgbPixelToHSV(m_colorRange[m_colorRange.size() - 1]);
ShowThreshedImage(m_imgSrc, m_minHSV, m_maxHSV);
...But even without this conversion and simply passing RGB values instead, the result is still an entirely black image. I've even tried manually plugging in certain min/max values, and the best result I got was a few lit pixels (albeit, the incorrect ones).
The question:
What am I doing wrong here?
Is there something that I don't understand about the cvInRangeS method?
Do I need to step through each and every single color in order to properly threshold the selected range out of the source image?
Are there any other ways of accomplishing this?
Thank you for your time.
Update:
I have discovered that cvInRangeS expects all values for min to be lower than that of max. But when a range of colors are selected, there doesn't appear to be any guarantee that this will be the case, often resulting in a black thresholded image.
And swapping values to enforce this rule may result in unwanted colors within the new range (in some cases, this could include all colors instead of just the desired ones).
So I suppose the real question here would be:
"How do you segment an array of RGB colors, and use them to threshold an image?"
Your problem might be caused by the simple fact that OpenCV maintains a different range for values than for instanc MSpaint. For instance the HSV color space in paint is 360,100,100 while in OpenCV it is 180,255,255. Check your input values in openCV bu outputting the pixel value when clicking on a certain pixel. inRangeS should be the correct tool for the job. That said, in RGB it should work just as well because the range is the same as in paint.
cvSetMouseCallback("MyWindow", mouseEvent, (void*) &myImage);
void mouseEvent(int evt, int x, int y, int flags, void *param) {
if (evt == CV_EVENT_LBUTTONDOWN) {
printf("%d %d\n", x, y);
IplImage* imageSource = (IplImage*) param;
Mat image(imageSource);
cout << "Image cols " << image.cols << " rows " << image.rows << endl;
Mat imageHSV;
cvtColor(image, imageHSV, CV_BGR2HSV);
Vec3b p = imageHSV.at<Vec3b > (y, x);
char text[20];
sprintf(text, "H=%d, S=%d, V=%d", p[0], p[1], p[2]);
cout << text << endl;
}
}
When you have an idea about the HSV values by using this values, use these as lower and upper bounds for the in range method after converting the image to HSV by using cvtColor(image, imageHSV, CV_BGR2HSV). That should make you able to get the desired result.
It is not going to be too inefficient to iterate through every pixel. That is exactly what cvInRangeS would do - see this: http://docs.opencv.org/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#the-efficient-way (I do this all the time and it is instantaneous for reasonable size images).
I would treat the color in the array as points in 3D RGB space. Find two color points that specify a prism that includes all other color points. That is just finding the min and max of all r,g, and b values. If this idea is not ok then you might have to check every image pixel against every pixel in the vector.
Then for each pixel in the image: result is black if (pixel.r < min.r) || (pixel.r > max.r) || (pixel.g < min.g) || (pixel.g > max.g) || (pixel.b < min.b) || (pixel.b > max.b), result is the pixel value otherwise.
This all should be very easy, so long as it is actually what you want.