David S
David S

Reputation: 57

How to get pixel value from the contoured image with mask?

I tried to extract average pixel values (R, G, B) from the contoured image. However, my problem is when I applied the code below, something strange values were observed.

int main(){
cv::Mat star = imread("C:\\Users\\PC\\Desktop\\star\\starcircle.png");
cv::Mat mask = cv::Mat::zeros(star.rows, star.cols, CV_8UC1);
cv::Mat frame;
double b, g, r = 0.0;

cv::imshow("Original", star);

cv::cvtColor(star, frame, CV_BGR2HSV);

cv::inRange(frame, cv::Scalar(29, 220, 220), cv::Scalar(30, 255, 255), mask);

cv::imshow("mask", mask);

cv::Mat result = cv::Mat(star.rows, star.cols, CV_8UC1, star.type());
result.setTo(cv::Scalar(0, 0, 0));

star.copyTo(result, mask);  

cv::Scalar temp = mean(mask);

cout << "avg_R: " << temp[2] << " \n"; // red value
cout << "avg_G: " << temp[1] << " \n"; // green value
cout << "avg_B: " << temp[0] << " \n\n"; // blue value 

cv::imshow("result", result);
cv::waitKey(-1);
return 0;

}

And I got the correct images for the result like below.

enter image description here

I want to read pixel values only for yellow part, not for outside of mask.

And I have another code for read out pixel values in the yellow parts, but it showed the same result.

int main(){
cv::Mat star = imread("C:\\Users\\PC\\Desktop\\star\\starcircle.png");
cv::Mat mask = cv::Mat::zeros(star.rows, star.cols, CV_8UC1);
cv::Mat frame;
double b, g, r = 0.0;

cv::imshow("Original", star);

cv::cvtColor(star, frame, CV_BGR2HSV);

cv::inRange(frame, cv::Scalar(29, 220, 220), cv::Scalar(30, 255, 255), mask);

cv::imshow("mask", mask);

cv::Mat result = cv::Mat(star.rows, star.cols, CV_8UC1, star.type());
result.setTo(cv::Scalar(0, 0, 0));

star.copyTo(result, mask);  

int hei = star.rows;
int wid = star.cols;

int corow = hei * wid;

double b, g, r = 0.0;

for (int x = 0; x < hei; x++) {
    for (int y = 0; y < wid; y++) {
        if (mask.at<unsigned char>(x, y) > 0) {
            b += result.at<Vec3b>(x, y)[0];
            g += result.at<Vec3b>(x, y)[1];
            r += result.at<Vec3b>(x, y)[2];

        }
        else {

        }


    }
}

cout << "$$ Red(R), Green(G), Blue(B) $$" << " \n\n";
cout << "avg_R: " << r / corow << " \n"; // red value
cout << "avg_G: " << g / corow << " \n"; // green value
cout << "avg_B: " << b / corow << " \n\n"; // blue value

}

Please help me to revise the error.

Thank you in advance.

Upvotes: 0

Views: 1522

Answers (3)

Miki
Miki

Reputation: 41765

A few things:

  • Your variables names and Mat types are at least confusing. Use proper names for the variables, and use Mat_<T> whenever possible (I'd say always).
  • To get the mean you should divide by the number of pixels in the mask, not by total number of pixels.
  • you should consider using cv::mean
  • you need cv::waitKey() to actually see your cv::imshow

Check the code:

#include <opencv2\opencv.hpp>

int main()
{
    cv::Mat3b star = cv::imread("path/to/image");   
    cv::imshow("Original", star);

    cv::Mat3b hsv;
    cv::cvtColor(star, hsv, cv::COLOR_BGR2HSV);

    cv::Mat1b mask;
    cv::inRange(hsv, cv::Scalar(29, 220, 220), cv::Scalar(30, 255, 255), mask);
    cv::imshow("mask", mask);

    // Change to 'false' to see how to use the 'cv::mask' approach
    if (true)
    {
        double blue, green, red = 0.0;
        int counter = 0;
        for (int r = 0; r < star.rows; r++)
        {
            for (int c = 0; c < star.cols; c++)
            {
                if (mask(r, c) > 0)
                {
                    ++counter;
                    blue += star(r, c)[0];
                    green += star(r, c)[1];
                    red += star(r, c)[2];
                }
            }
        }

        // Avoid division by 0
        if (counter > 0)
        {
            blue /= counter;
            green /= counter;
            red /= counter;
        }

        std::cout << "$$ Red(R), Green(G), Blue(B) $$" << " \n\n";
        std::cout << "avg_R: " << red << " \n"; 
        std::cout << "avg_G: " << green << " \n"; 
        std::cout << "avg_B: " << blue << " \n\n"; 
    }
    else
    {
        cv::Scalar mean_value = cv::mean(star, mask);
        double blue = mean_value[0];
        double green = mean_value[1];
        double red = mean_value[2];

        std::cout << "$$ Red(R), Green(G), Blue(B) $$" << " \n\n";
        std::cout << "avg_R: " << red << " \n"; // red value
        std::cout << "avg_G: " << green << " \n"; // green value
        std::cout << "avg_B: " << blue << " \n\n"; // blue value
    }
    cv::waitKey();
}

Upvotes: 2

api55
api55

Reputation: 11420

I see several errors in your code:

cv::Mat result = cv::Mat(star.rows, star.cols, CV_8UC1, star.type());
result.setTo(cv::Scalar(0, 0, 0));
star.copyTo(result, mask);  
cv::Scalar temp = mean(mask);

If result is of type CV_8UC1 then you copyTo one channel? (The C1 from CV_8U means one channel). Then you use star.type() where the value to be set should be... You do also the mean to a mask, which will give you a scalar with only one channel set, since it is a binary image of type CV_8UC1... for it to work, it should be:

cv::Mat result = cv::Mat(star.rows, star.cols, star.type(), cv::Scalar::all(0));
star.copyTo(result, mask);  
cv::Scalar temp = mean(result);

For the second part, it is ok to add it like that, however if you have not fixed the previous error... I think it should give you segmentation error at some point or weird results if you are lucky. Finally the result part you have this:

cout << "$$ Red(R), Green(G), Blue(B) $$" << " \n\n";
cout << "avg_R: " << r / corow << " \n"; // red value
cout << "avg_G: " << g / corow << " \n"; // green value
cout << "avg_B: " << b / corow << " \n\n"; // blue value

but corow should be the non zero points of the mask, so it should be:

corow = cv::countNonZero(mask);
cout << "$$ Red(R), Green(G), Blue(B) $$" << " \n\n";
cout << "avg_R: " << r / corow << " \n"; // red value
cout << "avg_G: " << g / corow << " \n"; // green value
cout << "avg_B: " << b / corow << " \n\n"; // blue value

if not it will give you a smaller number, since it is divided with a a number that includes the black points which do not contribute.

As an extra note, you should use more OpenCV functions... in this case cv::mean does the same thing, if not you can simplify it with sum and divide like:

 cv::Scalar summed = cv::sum(result); 
 cv::Scalar mean = summed / static_cast<double>(cv::countNonZero(mask));
 std::cout << "$$ Red(R), Green(G), Blue(B) $$" << std::endl << std::endl;
 std::cout << "avg_R: " << mean[2] << std::endl; // red value
 std::cout << "avg_G: " << mean[1] << std::endl; // green value
 std::cout << "avg_B: " << mean[0] << std::endl << std::endl; // blue value

This is assuming you did the star.copyTo(result, mask); line

Upvotes: 1

Nuzhny
Nuzhny

Reputation: 1927

  1. Read about cv::Mat::at: first row, second col. Not (x, y)!

  2. See you on cv::mean: it can work with mask.

  3. Right initialization: double b = 0.0, g = 0.0, r = 0.0;

  4. int corow = 0; And inside loop ++corow if mask > 0.

Upvotes: -1

Related Questions