I could not come up with any methods that are significantly different from the approaches you proposed. However, I was able to make some timings that can help you make a decision. All my timings were performed on a 1280 * 720 image on an iMac, limited to finding 100 loops. Of course, the timings will be different on your machine, but relative timings should be informative.
For each test case, the following are declared:
std::vector<std::vector<cv::Point>> cont; // Filled by cv::findContours() cv::Mat labels = cv::Mat::zeros(image.size(), CV_8UC1); std::vector<float> cont_avgs(cont.size(), 0.f); // This contains the averages of each contour
Method 1: 19.0 ms
Method 1 is conceptually the simplest, but it is also the slowest. Each path is indicated by assigning a unique color to each path. The values โโof each labeled component are summed by repeating each pixel in the image.
for (size_t i = 0; i < cont.size(); ++i) { // Labels starts at 1 because 0 means no contour cv::drawContours(labels, cont, i, cv::Scalar(i+1), CV_FILLED); } std::vector<float> counts(cont.size(), 0.f); const int width = image.rows; for (size_t i = 0; i < image.rows; ++i) { for (size_t j = 0; j < image.cols; ++j) { uchar label = labels.data[i*width + j]; if (label == 0) { continue; // No contour } else { label -= 1; // Make labels zero-indexed } uchar value = image.data[i*width + j]; cont_avgs[label] += value; ++counts[label]; } } for (size_t i = 0; i < cont_avgs.size(); ++i) { cont_avgs[i] /= counts[i]; }
Method 3: 15.7 ms
Unmodified method 3 has the simplest implementation and is also the fastest. All contours are filled in for use as a mask to find the average value. The bounding box of each path is calculated, and then the average value is calculated using the mask in the bounding box.
Warning: This method will give incorrect results if any other contours are within the bounding box of the contour of interest.
cv::drawContours(labels, cont, -1, cv::Scalar(255), CV_FILLED); for (size_t i = 0; i < cont.size(); ++i) { cv::Rect roi = cv::boundingRect(cont[i]); cv::Scalar mean = cv::mean(image(roi), labels(roi)); cont_avgs[i] = mean[0]; }
Modified Method 3: 17.8 ms
Making minor changes to Method 3 slightly increases the execution time, but it benefits from providing the correct results regardless of the position of the contour. Each path is individually labeled, and the average is calculated using only the mask for that path.
for (size_t i = 0; i < cont.size(); ++i) { cv::drawContours(labels, cont, i, cv::Scalar(i), CV_FILLED); cv::Rect roi = cv::boundingRect(cont[i]); cv::Scalar mean = cv::mean(image(roi), labels(roi) == i); cont_avgs[i] = mean[0]; }