5

is there some good and better way to find centroid of contour in opencv, without using built in functions?

Ayesha Khan
  • 155
  • 2
  • 7
  • 12
  • If you have a bunch of points(2d vectors), you should be able to get the centroid by averaging those points: create a point to add all the other points' positions into and then divide the components of that point with accumulated positions by the total number of points. I've just written a basic illustration [here](http://hascanvas.com/contourCentroid). Not entirely sure how well it works with convex hulls though. – George Profenza Nov 15 '11 at 22:26

3 Answers3

7

While Sonaten's answer is perfectly correct, there is a simple way to do it: Use the dedicated opencv function for that: moments()

http://opencv.itseez.com/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=moments#moments

It does not only returns the centroid, but some more statistics about your shape. And you can send it a contour or a raster shape (binary image), whatever best fits your need.

EDIT

example (modified) from "Learning OpenCV", by gary bradsky

CvMoments moments; 
double M00, M01, M10;

cvMoments(contour,&moments); 
M00 = cvGetSpatialMoment(&moments,0,0); 
M10 = cvGetSpatialMoment(&moments,1,0); 
M01 = cvGetSpatialMoment(&moments,0,1); 
centers[i].x = (int)(M10/M00); 
centers[i].y = (int)(M01/M00); 
Sam
  • 19,708
  • 4
  • 59
  • 82
  • thanks, i have tried to do it, using following code, for( ; contours != 0; contours = contours->h_next ) {CvMoments *moments; cvMoments(cc_color, moments, 1); double M01 = cvGetSpatialMoment(moments, 0, 1); double M00 = cvGetSpatialMoment(moments, 0, 0); double yc = M01 / M00; } but it still gives errors.. – Ayesha Khan Nov 16 '11 at 08:43
  • Isn't it supposed to first alloc some space for *moments? Or better use the C++ syntax, it manags the memory fot you. I do not remember the constructor for cvMoments – Sam Nov 16 '11 at 09:39
  • i have even put CvMoments* moments = (CvMoments*) malloc(sizeof(CvMoments)); but it still crashes. – Ayesha Khan Nov 16 '11 at 10:38
  • try the example code from above. It's adapted from "learning ocv" – Sam Nov 16 '11 at 10:51
  • Now, I have done: for( ; contours != 0; contours = contours->h_next ) {std::vector vec(2); CvMoments * myMoments = (CvMoments*)malloc( sizeof(CvMoments) ); cvMoments( contours, myMoments ); vec[0]=(double) myMoments->m10/myMoments->m00; vec[1]=(double) myMoments->m01/myMoments->m00; } is it the correct method? – Ayesha Khan Nov 16 '11 at 11:08
2

What you get in your current piece of code is of course the centroid of your bounding box.

"If you have a bunch of points(2d vectors), you should be able to get the centroid by averaging those points: create a point to add all the other points' positions into and then divide the components of that point with accumulated positions by the total number of points." - George Profenza mentions

This is indeed the right approach for the exact centroid of any given object in two-dimentionalspace.

On wikipedia we have some general forms for finding the centroid of an object. http://en.wikipedia.org/wiki/Centroid

Personally, I would ask myself what I needed from this program. Do I want a thorough but performance heavy operation, or do I want to make some approximations? I might even be able to find an OpenCV function that deals with this correct and efficiently.

Don't have a working example, so I'm writing this in pseudocode on a simple 5 pixel example on a thorough method.

x_centroid = (pixel1_x + pixel2_x + pixel3_x + pixel4_x +pixel5_x)/5
y_centroid = (pixel1_y + pixel2_y + pixel3_y + pixel4_y +pixel5_y)/5

centroidPoint(x_centroid, y_centroid)

Looped for x pixels

Loop j times *sample (for (int i=0, i < j, i++))*
{
    x_centroid = pixel[j]_x + x_centroid
    y_centroid = pixel[j]_x + x_centroid
}
x_centroid = x_centroid/j
y_centroid = y_centroid/j

centroidPoint(x_centroid, y_centroid)

Essentially, you have the vector contours of the type

vector<vector<point>>

in OpenCV 2.3. I believe you have something similar in earlier versions, and you should be able to go through each blob on your picture with the first index of this "double vector", and go through each pixel in the inner vector.

Here is a link to documentation on the contour function http://opencv.itseez.com/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=contours#cv.DrawContours

note: you've tagged your question as c++ visual. I'd suggest that you use the c++ syntax in OpenCV 2.3 instead of c. The first and good reason to use 2.3 is that it is more class based, which in this case means that the class Mat (instead of IplImage) does leak memory. One does not have to write destroy commands all the live long day :)

I hope this shed some light on your problem. Enjoy.

Sonaten
  • 502
  • 2
  • 14
1

I've used Joseph O'Rourke excellent polygon centroid algorithm to great success.

See http://maven.smith.edu/~orourke/Code/centroid.c

Essentially:

  1. For each point in the contour, find the triangle area from the current index polygon xy to the next 2 polygon xy points e.g.: Math.Abs(((X1 - X0) * (Y2 - Y0) - (X2 - X0) * (Y1 - Y0)) / 2)
  2. Add this triangle area to a list TriAreas
  3. Sum the triangle area, and store in SumT
  4. Find the centroid CTx and CTy from this current triangle: CTx = (X0 + X1 + X2) / 3 and CTy = (Y0 + Y1 + Y2) / 3;
  5. Store these 2 centroid values in 2 other lists CTxs CTys.
  6. Finally after performing this with all points in the contour, find the contours centroid x and y using the 2 triangle x and y lists in 5 which is a weighted sum of signed triangle areas, weighted by the centroid of each triangle:

        for (Int32 Index = 0; Index < CTxs.Count; Index++)
        {
            CentroidPointRet.X += CTxs[Index] * (TriAreas[Index] / SumT);
        } 
        // now find centroid Y value
        for (Int32 Index = 0; Index < CTys.Count; Index++)
        {
            CentroidPointRet.Y += CTys[Index] * (TriAreas[Index] / SumT);
        }
    
Jeb
  • 3,689
  • 5
  • 28
  • 45