2

how can I calculate percentage of white pixels inside of cv::RotatedRect? I mean, how to access single pixel inside of my cv::RotatedRect. If i'd reach that, i'd know what to do later. Thanks

I've tried solution from this thread, but I've had exceptions. https://stackoverflow.com/a/28780359

    std::vector<cv::RotatedRect> minRect(count.size());
    for (int i = 0; i < count.size(); i++)
    {
        minRect[i] = cv::minAreaRect(cv::Mat(count[i]));
    }
    for (size_t i = 0; i < count.size(); i++){
        if (cv::contourArea(count[i]) > 200) {
            cv::Point2f rect_points[4];
            minRect[i].points(rect_points);
            // Now I'd like to calculate percentage of white pixels inside of RotatedRect, and if value returned by func would be smaller than 30%,continue;
            for (int j = 0; j < 4; j++) {
                cv::line(mask, rect_points[j], rect_points[(j + 1) % 4], (0, 255, 0), 1, 8);
            }
        }

    }

2 Answers2

2

You can:

  1. Work on the sub-image defined by cv::boundingRect
  2. create the mask where all points inside the rotated rect are white with cv::fillConvexPoly
  3. logical AND with the original image
  4. count the number of white pixels with cv::countNonZero

The method proposed by John Henkel works, but in my (very quick) tests it something between 10 and 40 times slower.

Below the code with both methods. You'll find small differences in the result, because the white pixels on the border of the rotated rect are handled differently.

#include <opencv2\opencv.hpp>
#include <chrono>

int main()
{
    // Create binary image with random pixels b/W
    cv::Mat1b img(5000, 5000);
    cv::randu(img, cv::Scalar(0), cv::Scalar(256));
    img = img > 127;

    // Define a rotated rect
    cv::Point2f center(2000, 2000);
    cv::Size2f sz(1000, 500);
    float angle = 30.f;
    cv::RotatedRect rr(center, sz, angle);

    // Get points
    std::vector<cv::Point2f> points(4);
    rr.points(points.data());

    // Work on ROI
    cv::Rect roi = rr.boundingRect();

    // Area 
    float area = rr.size.width * rr.size.height;

    //// DEBUG, Show rect
    //cv::Mat3b out;
    //cv::cvtColor(img, out, cv::COLOR_GRAY2BGR);
    //for (int i = 0; i < 4; ++i) {
    //  cv::line(out, points[i], points[(i + 1) % 4], cv::Scalar(0, 0, 255));
    //}

    {
        // --------------------
        // Method @Miki
        // --------------------

        auto tic = std::chrono::high_resolution_clock::now();

        cv::Mat1b sub_img = img(roi);

        // Create rotated rect mask
        cv::Mat1b mask(roi.size(), uchar(0));
        std::vector<cv::Point> points_in_sub_image(4);
        for (int i = 0; i < 4; ++i) {
            points_in_sub_image[i] = cv::Point(points[i]) - roi.tl();
        }
        cv::fillConvexPoly(mask, points_in_sub_image, cv::Scalar(255));

        // AND sub image with mask
        cv::Mat1b inside_roi = sub_img & mask;

        //// DEBUG, Draw green points
        //for (int r = 0; r < sub_img.rows; ++r) {
        //  for (int c = 0; c < sub_img.cols; ++c) {
        //      if (inside_roi(r, c) > 0)
        //      {
        //          out(r + roi.y, c + roi.x) = cv::Vec3b(0, 255, 0);
        //      }
        //  }
        //}


        // Get actual count
        int cnz = cv::countNonZero(inside_roi);

        auto toc = std::chrono::high_resolution_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(toc - tic);

        float percent_white_pixels = cnz / area;
        std::cout << "percent_white_pixels: " << percent_white_pixels << " in " << elapsed.count() << " us" <<  std::endl;
    }

    {
        // --------------------
        // Method @John Henkel
        // --------------------

        auto tic = std::chrono::high_resolution_clock::now();

        int cnz = 0;
        for (int y = roi.y; y < roi.y + roi.height; ++y) {
            for (int x = roi.x; x < roi.x + roi.width; ++x) {
                if (
                    (img(y, x) > 0) &&
                    (cv::pointPolygonTest(points, cv::Point2f(x, y), false) >= 0.0)
                    ) 
                {
                    // DEBUG, Draw blue points
                    //out(y, x) = cv::Vec3b(255, 0, 0);
                    ++cnz;
                }
            }
        }

        auto toc = std::chrono::high_resolution_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(toc - tic);

        float percent_white_pixels = cnz / area;
        std::cout << "percent_white_pixels: " << percent_white_pixels << " in " << elapsed.count() << " us" <<  std::endl;
    }

    getchar();
    return 0;
}
Miki
  • 40,887
  • 13
  • 123
  • 202
1

The best way I can think of to get the individual pixels would be to first obtain the bounding box of your rotated rectangle and then iterate through each of the pixels inside the box to see if they are in the rotated rectangle with pointPolygonTest. I'm not sure if there's a more efficient way to do it, but this should give you the results you're looking for.

John Henkel
  • 11
  • 1
  • 1
  • Right now I count all white pixels inside of bounding rect. Perfect situation would be, if mostly all of these points would be inside of my rotated rect ( like whitepixels divided by all pixels would be higher than 85% ). Could you explain please, how to use pointPolygonTest for rotated rectangle? As I can see, it doesn't accept rotated rectangle argument. – Maciej Wroński Dec 02 '18 at 11:31