I have an image in two different formats. one is BGR and the other is black and white (there is only black and white, no gray colored pixels). Its the same exact image (same size and pixels). I want to find all the white pixels in the black and white image, mark them down and then find the exact same pixels in the BGR image (obviously they are colored in the BGR image) and color them black.
I tried it but the thing is the black and white image has 1 channel and the BGR one has 3 channels so i failed...
i am using opencv in c++
thanks for your help! :)
for(int y=0;y<inputImage.rows;y++){
for(int x=0;x<inputImage.cols;x++){
Vec3b color = inputImage.at<Vec3b>(Point(x,y));
if(blackWhite.at<uchar>(y,x) == 255){
//cout << "found white pixel\n";
color[0] = 0;
color[1] = 0;
color[2] = 0;
inputImage.at<Vec3b>(Point(x,y)) = color;
}
}
}
inputImage is my BGR image and blackWhite is an image of same size with black and white pixels. both are Mat objects.
i want to go through the blackWhite image and whenever i find a white pixel, color the same pixel from the inputImage image in black color.
My strategy is to construct an array similar to your blackWhite image. This array will be zeros and ones and we'll multiply it with inputImage to get the desired output.
Currently, your blackWhite image looks something like (for example)
blackWhite = 255 255 0 0 ....
....
in pseudocode. If we transform this image to become
newArray = 0 0 1 1 ....
....
You could then use cv::multiply(newArray, inputImage) to get the desired output.
One way to directly transform your existing blackWhite image into newArray would be to perform y = (-1/255)*x + 1 on every pixel in blackWhite. You can accomplish this with cv::Mat::convertTo(outputImage, 8, -1/255, 1)
I have this image in OpenCV imgColorPanel = imread("newGUI.png", CV_LOAD_IMAGE_COLOR);:
When I load it in with grey scale imgColorPanel = imread("newGUI.png", CV_LOAD_IMAGE_GRAYSCALE); it looks like this:
However I want to remove the white background or make it transparent (only it's white pixels), to be looking like this:
How to do it in C++ OpenCV ?
You can convert the input image to BGRA channel (color image with alpha channel) and then modify each pixel that is white to set the alpha value to zero.
See this code:
// load as color image BGR
cv::Mat input = cv::imread("C:/StackOverflow/Input/transparentWhite.png");
cv::Mat input_bgra;
cv::cvtColor(input, input_bgra, CV_BGR2BGRA);
// find all white pixel and set alpha value to zero:
for (int y = 0; y < input_bgra.rows; ++y)
for (int x = 0; x < input_bgra.cols; ++x)
{
cv::Vec4b & pixel = input_bgra.at<cv::Vec4b>(y, x);
// if pixel is white
if (pixel[0] == 255 && pixel[1] == 255 && pixel[2] == 255)
{
// set alpha to zero:
pixel[3] = 0;
}
}
// save as .png file (which supports alpha channels/transparency)
cv::imwrite("C:/StackOverflow/Output/transparentWhite.png", input_bgra);
This will save your image with transparency.
The result image opened with GIMP looks like:
As you can see, some "white regions" are not transparent, this means your those pixel weren't perfectly white in the input image.
Instead you can try
// if pixel is white
int thres = 245; // where thres is some value smaller but near to 255.
if (pixel[0] >= thres&& pixel[1] >= thres && pixel[2] >= thres)
enter image description here
jpg
I want to check the 2nd image to see if the pixel is white, if it is white i should change it into a black pixel, and also i should be able to change the pixel of the same spot in the 2nd image to the 1st image to black or white..
Example:
img at the cooridnate (100,100) the pixel is white from the 2nd image and i should be able to change it into black. Then the 1st img at the same cooridnate (100,100) the pixel would be black and i should be able to change it into white. to reduce the noise.
The below code shows you how to find a point in an image, see if it i white, and change it to black if it is.
Scalar colourInSecondImage = img2.at<uchar>(y,x);
if(colourInSecondImage .val[0]==255 && colourInSecondImage .val[1]==255 && colourInSecondImage .val[2]==255)
{
// Then your point is a white point
img2.at<uchar>(y,x) = Scalar(0,0,0);
}
I'm a little confused by your question, it seems to be that you then want to access the same point in another image and set that to black? Or the same colour? either way you would use the same method as in the code above. change change im2 to img1
This is how you can loop through all your pixel values and manipulate them
for(int r = 0; r < image.rows; r++) {
for(int c = 0; c < image.cols; c++) {
// if pixel is white
if(image.at<uchar>(r,c) == 255) {
image.at<uchar>(r,c) = 0;
}
}
}
//// split channels
split(image,spl);
imshow("spl1",spl[0]);//b
imshow("spl2",spl[1]);//g
imshow("spl3",spl[2]);//r
I'm working in opencv 2.4.0 and C++
I'm trying to do an exercise that says I should load an RGB image, convert it to gray scale and save the new image. The next step is to make the grayscale image into a binary image and store that image. This much I have working.
My problem is in counting the amount of black pixels in the binary image.
So far I've searched the web and looked in the book. The method that I've found that seems the most useful is.
int TotalNumberOfPixels = width * height;
int ZeroPixels = TotalNumberOfPixels - cvCountNonZero(cv_image);
But I don't know how to store these values and use them in cvCountNonZero(). When I pass the the image I want counted from to this function I get an error.
int main()
{
Mat rgbImage, grayImage, resizedImage, bwImage, result;
rgbImage = imread("C:/MeBGR.jpg");
cvtColor(rgbImage, grayImage, CV_RGB2GRAY);
resize(grayImage, resizedImage, Size(grayImage.cols/3,grayImage.rows/4),
0, 0, INTER_LINEAR);
imwrite("C:/Jakob/Gray_Image.jpg", resizedImage);
bwImage = imread("C:/Jakob/Gray_Image.jpg");
threshold(bwImage, bwImage, 120, 255, CV_THRESH_BINARY);
imwrite("C:/Jakob/Binary_Image.jpg", bwImage);
imshow("Original", rgbImage);
imshow("Resized", resizedImage);
imshow("Resized Binary", bwImage);
waitKey(0);
return 0;
}
So far this code is very basic but it does what it's supposed to for now. Some adjustments will be made later to clean it up :)
You can use countNonZero to count the number of pixels that are not black (>0) in an image. If you want to count the number of black (==0) pixels, you need to subtract the number of pixels that are not black from the number of pixels in the image (the image width * height).
This code should work:
int TotalNumberOfPixels = bwImage.rows * bwImage.cols;
int ZeroPixels = TotalNumberOfPixels - countNonZero(bwImage);
cout<<"The number of pixels that are zero is "<<ZeroPixels<<endl;
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.