Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to count all non-zero pixels within each polygon area efficiently?

Tags:

c++

opencv

I want to calculate numbers of all white pixels within every polygon area efficiently.

Given some processes:

// some codes for reading gray image
// cv::Mat gray = cv::imread("gray.jpg");

// given polygons
// vector< vector<cv::Point> > polygons;

cv::Mat cropped;
cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
cv::fillPoly(mask, polygons, cv::Scalar(255));
cv::bitwise_and(gray, gray, cropped, mask);

cv::Mat binary;
cv::threshold(cropped, binary, 20, 255, CV_THRESH_BINARY);

So until now, we can get a image with multiple polygon areas(say we have 3 areas) which have white( with value 255) pixels. Then after some operations we expect to get a vector like:

// some efficient operations
// ...

vector<int> pixelNums;

The size of pixelNums should be same with polygons which is 3 here. And if we print them we may get some outputs like(the values are basically depended on the pre-processes):

index: 0; value: 120
index: 1; value: 1389
index: 2; value: 0

Here is my thought. Counting every pixels within every polygon area with help of cv::countNonZero, but I need to call it within a loop which I don't think it's a efficient way, isn't it?

vector<int> pixelNums;
for(auto polygon : polygons)
{
  vector< vector<cv::Point> > temp_polygons;
  temp_polygons.push_back(polygon);

  cv::Mat cropped;
  cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
  cv::fillPoly(mask, temp_polygons, cv::Scalar(255));
  cv::bitwise_and(gray, gray, cropped, mask);

  cv::Mat binary;
  cv::threshold(cropped, binary, 20, 255, CV_THRESH_BINARY);

  pixelNums.push_back(cv::countNonZero(binary));
}

If you have some better ways, please kindly answer this post. Here I say better way is consuming as little time as you can just in cpu environment.


1 Answers

There are some minor improvements that can be done, but all of them combined should provide a decent speedup.

  1. Compute the threshold only once
  2. Make most operations on smaller images, using the bounding box of your polygon to get the region of interest
  3. Avoid unneeded copies in the for loop, use const auto&

Example code:

#include <vector>
#include <opencv2/opencv.hpp>

int main()
{
    // Your image
    cv::Mat1b gray = cv::imread("path/to/image", cv::IMREAD_GRAYSCALE);

    // Your polygons
    std::vector<std::vector<cv::Point>> polygons
    {
        { {15,120}, {45,200}, {160,160}, {140, 60} },
        { {10,10}, {15,30}, {50,25}, {40, 15} },
        // etc...
    };

    // Compute the threshold just once
    cv::Mat1b thresholded = gray > 20;

    std::vector<int> pixelNums;
    for (const auto& polygon : polygons)
    {
        // Get bbox of polygon
        cv::Rect bbox = cv::boundingRect(polygon);

        // Make a new (small) mask 
        cv::Mat1b mask(bbox.height, bbox.width, uchar(0));
        cv::fillPoly(mask, std::vector<std::vector<cv::Point>>{polygon}, cv::Scalar(255), 8, 0, -bbox.tl());

        // Get crop
        cv::Mat1b cropped = thresholded(bbox) & mask;

        // Compute the number of white pixels only on the crop
        pixelNums.push_back(cv::countNonZero(cropped));
    }

    return 0;
}
like image 74
Miki Avatar answered Dec 23 '25 12:12

Miki