2
\$\begingroup\$

I was recently going through some OpenCV code that finds the center of mass in a contour and realized there is potential room for optimisation.

What I noticed looking back through my code is when I calculate the mass I'm using contours.size() which makes me believe that I'm calculating the moments and center of mass for all of my contours.

In my cases this is not needed as I have a function that returns the largest contour that I have found getMaxAreaContourId(contours, imageSize); which returns an int that I can use like contours[largestContour] and largestContour would be set from the result of getMaxAreaContourId

So if I'm not mistaken would I be able to just find the moment and mass from a single contour and completely remove two for loops?

Because Ultimately I end up doing this:

int CenterOfContourX = mc[largestContour].x; int CenterOfContourY = mc[largestContour].y;

Really the end goal is to end up with two integers that represent the center of mass of my contour.

Code to optimise:

// Get the moments
mu.reserve(contours.size());
for( int i = 0; i < contours.size(); i++ )
{ mu[i] = moments( contours[i], false ); }

// Get the mass centers:
std::vector<cv::Point2f> mc( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{ mc[i] = cv::Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); }
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

It seem like you can definitely merge the two loops together.

Also you should avoid iterating using i, instead, use iterators, or the range-based for loop (since C++11).

// if you needed this vector
mu.reserve(contours.size());

// Get the mass centers:
std::vector<cv::Point2f> mc( contours.size() );
for( auto& c : contours )
{
  auto m = moments(c, false); 
  mu.push_back(m); // only if you need the mu vector
  mc.push_back(cv::Point2f( m.m10/m.m00 , m.m01/m.m00 )); 
}

And this can even be simplified to a call to std::transfer with std::back_inserter:

std::transform(
  std::begin(contours),
  std::end(contours),
  std::back_inserter(mc),
  [](const contour& c) -> cv::Point2f {
    auto m = moments(c, false);
    return cv::Point2f( m.m10/m.m00 , m.m01/m.m00 )
  }
);

And of course if you only need to know moments of one countour, there is no need to count it for all, but since you haven't included that part of the code, it's hard to tell anything about it.

\$\endgroup\$
3
  • \$\begingroup\$ That helps a lot. If it's not clear I'm calculating the center of mass on a blob. And yes atm I only need to calculate the center of mass for one blob currently. In the future I might need to process the center of mass for two blobs but as long as we're talking about a small fixed known number of contours I think I can do away with the loop. \$\endgroup\$
    – Jameson
    Nov 19, 2019 at 0:37
  • \$\begingroup\$ One thing that confuses me is how do I store a single Moments? Would I just use: cv::Moments mu; instead of: std::vector<cv::Moments> mu;? \$\endgroup\$
    – Jameson
    Nov 19, 2019 at 0:39
  • \$\begingroup\$ Nvm I figured it out, I'll add a answer for future readers. \$\endgroup\$
    – Jameson
    Nov 19, 2019 at 0:51
1
\$\begingroup\$

This is the improvement I ended up making for anyone interested.

Old code:

std::vector<std::vector<cv::Point> > contours;

int blobX; //Need to be int cannnot be Point2f
int blobY;

findContours( frame, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

// Get the moments
mu.reserve(contours.size());
for( int i = 0; i < contours.size(); i++ )
{ mu[i] = moments( contours[i], false ); }

// Get the mass centers:
std::vector<cv::Point2f> mc( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{ mc[i] = cv::Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); }

int largestContour = getMaxAreaContourId(contours, imageSize); //Finds largest contour
blobX = mc[largestContour].x;
blobY = mc[largestContour].y;

New code:

std::vector<std::vector<cv::Point> > contours;

int blobX; //Need to be int cannnot be Point2f
int blobY;

findContours( frame, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

int largestContour = getMaxAreaContourId(contours, imageSize); //Finds largest contour
cv::Moments mu;
mu = moments( contours[largestContour], false );
blobX = mu.m10/mu.m00;
blolbY = mu.m01/mu.m00;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.