# finding centroid of a mask

hi, i have a mask obtained by threshold function. i wonder if i can find its centroid with builtin function, now i'm doing manual:

    float sumx=0, sumy=0;
float num_pixel = 0;
for(int x=0; x<difference.cols; x++) {
for(int y=0; y<difference.rows; y++) {
int val = difference.at<uchar>(y,x);
if( val >= 50) {
sumx += x;
sumy += y;
num_pixel++;
}
}
}
Point p(sumx/num_pixel, sumy/num_pixel);


but probabilly is there a better way..

edit retag close merge delete

Sort by » oldest newest most voted

You should use moments() function.

Moments m = moments(difference, false);
Point p1(m.m10/m.m00, m.m01/m.m00);


Code of sample:

int main(int argc, char *argv[])
{
Mat difference;
threshold(box, difference, 200, 255, CV_8UC1);
float sumx=0, sumy=0;
float num_pixel = 0;
for(int x=0; x<difference.cols; x++) {
for(int y=0; y<difference.rows; y++) {
int val = difference.at<uchar>(y,x);
if( val >= 50) {
sumx += x;
sumy += y;
num_pixel++;
}
}
}
Point p(sumx/num_pixel, sumy/num_pixel);
cout << Mat(p) << endl;

Moments m = moments(difference, false);
Point p1(m.m10/m.m00, m.m01/m.m00);
cout << Mat(p1) << endl;

circle(difference, p, 5, Scalar(128,0,0), -1);
imshow("difff", difference);
waitKey(0);
return 0;
}

more

hi there!!this code is perfect!!but i cant understand this line if( val >= 50)..val means intensity..why does it need to be 50? thanks

( 2013-09-19 09:06:45 -0500 )edit

It is an arbitrary threshold.

( 2014-11-07 10:11:02 -0500 )edit

Which method is faster? Manually computing the center of mass or by using moments? I tried to compute it by iterating over the matrix and the fps of the video stream dropped from 16 to 4.

( 2015-01-05 01:34:50 -0500 )edit

hi sorry for the my English but i'm Italian,First of all congratulations to the code but one thing I'm not going in circle past p1 instead of p? Thanks for the answer previosly!

( 2015-09-26 04:45:21 -0500 )edit

Not only you are calculating moments here but you also perform threshold operation. Both operations can be done by OpenCV. First by function compare() and second by function moments().

Mat tmp;
compare(difference,Scalar(50),tmp,CMP_GE);
Moments m = moments(tmp,true);
Point p(m.m10/m.m00, m.m01/m.m00);


Note that you should use function moments() with flag 'true', i.e. each non-zero value in image should be treated as '1'. Otherwise moments() will use original values from image for calculation of moments. In this case it will result in factor of 255 for all moments. It is not a problem when you are calculating centroids because when you divide two values this factor will cancel itself, but if you use it for anything else you will get wrong results. For example m.m00 will be 255 times bigger than actual area.

And on other topic, you are traversing pixels of image column by column, intead of row by row. This may incur huge penalty on your time performance (up to order of magnitude in some cases).

Edit: Just thought that this can be done in even cleaner way:

Moments m = moments((difference>=50),true);
Point2d p(m.m10/m.m00, m.m01/m.m00);

more

You are talking about mask than above code is working fine but generally speaking you should take care about difference between geometric centre and centre of mass.

In geometric centre all points has same weight while in centre of mass each point is weighted with its intensity.

You should note that image moments calculates centre of mass.

For an image mask, geometric and centre of mass are the same because relevant pixels in mask has same intensity but generally speaking this is not true.

here is the code:

// GET GEOMETRIC CENTRE
//---------------------

// You could use inRange function

Moments m = moments(mask); // true flag is not needed because is a mask
Point geometricCentre(m.m10/m.m00, m.m01/m.m00);

// GET CENTRE OF MASS
//---------------------
Mat tmp(difference.size(),difference.type());
tmp.setTo(0);
difference.copyTo(tmp,mask);  // copy only wanted pixels
//matrix multiplication might be used if mask is 0/1

// tmp is a gray mask now
m = moments(tmp,false);   // avoid true flag for centre of mass
Point massCentre(m.m10/m.m00, m.m01/m.m00);


In addition true flag can be used to calculate geometric center on a gray scale mask

m = moments(tmp,true);   // true flag is needed because is a gray mask
Point geometricCentreAgain(m.m10/m.m00, m.m01/m.m00);

more

Official site

GitHub

Wiki

Documentation