# OpenCV calculate center of mass for a single contour

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 ); }


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.

• 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. Commented Nov 19, 2019 at 0:37
• 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;? Commented Nov 19, 2019 at 0:39
• Nvm I figured it out, I'll add a answer for future readers. Commented Nov 19, 2019 at 0:51

You are using moments(contour) and contour only contains outer points of the blob, not the inside. moments then does not give the center of mass but the average of the contour points.

And with CHAIN_APPROX_SIMPLE, you are dealing with a filtered set of contour points, and the moment will be biased towards less filtered parts of the contour.

## 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;