Reputation: 8714
For some reason that I cannot understand, the open cv function cv2.moments
returns a dictionary with all zero values for the contour I am providing.
Here is a MWE:
contour = [[[271, 67]],
[[274, 67]],
[[275, 68]],
[[278, 68]],
[[279, 69]],
[[283, 69]],
[[284, 70]],
[[287, 70]],
[[288, 71]],
[[291, 71]],
[[292, 72]],
[[295, 72]],
[[292, 72]],
[[291, 71]],
[[288, 71]],
[[287, 70]],
[[284, 70]],
[[283, 69]],
[[279, 69]],
[[278, 68]],
[[275, 68]],
[[274, 67]]]
contour = np.asarray(contour)
moments = cv2.moments(contour)
with the result:
print(moments)
{'m00': 0.0, 'm10': 0.0, 'm01': 0.0, 'm20': 0.0, 'm11': 0.0, 'm02': 0.0, 'm30': 0.0, 'm21': 0.0, 'm12': 0.0, 'm03': 0.0, 'mu20': 0.0, 'mu11': 0.0, 'mu02': 0.0, 'mu30': 0.0, 'mu21': 0.0, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.0, 'nu11': 0.0, 'nu02': 0.0, 'nu30': 0.0, 'nu21': 0.0, 'nu12': 0.0, 'nu03': 0.0}
What is the meaning of this behaviour? I believe this is because the contour is open, but i am not sure. Is there a standard way to get rid of this behaviour by checking if the contour is open or closed beforehand?
Upvotes: 5
Views: 3475
Reputation: 15510
Those are the points of your contour. The GIF doesn't loop back-and-forth, it only plays forward. When the point "climbs back up again", those are not the same points, they are new points that just coincide with previous points. Inspect the points in your contour, you'll see it.
Your contour is not "open". That is not the issue. In OpenCV, all contours are considered closed. They needn't be dense either (1-pixel distance between points).
The issue is that cv.moments()
has a bug and OpenCV contours are weird.
And your contour has no area in the strict mathematical "polygon" sense because it was calculated from a 1-pixel line.
That is the bug in cv.moments()
. It calculates those moments as if the contour were an actual polygon. It should have returned a non-zero area (m00
) for this contour because the contour describes a non-zero-area connected component.
In OpenCV, contours describe the polygon that would have to be drawn with 1-pixel stroke to reproduce the border pixels of that connected component. The contour runs ON the border pixels, through their centers.
That means, if you have a 2 by 2 pixel rectangle, you get contour points on those border pixels, say (1,1), (2,1), (2,2), (1,2). The area of that is just 1 (taken as a mathematical polygon), when it should have been 4 (in OpenCV's notion of a contour). For a plain line, same thing, but now you've got zero area.
And your polygon indeed has zero area. You just don't see it because the line wasn't straight.
OpenCV could have defined contours to be zero-width lines that circumscribe the outer pixels of a connected component, i.e. run exactly along pixel edges. That would have required:
Feel free to submit an issue on OpenCV's github page. The cv.moments()
implementation needs a fix. The fix should probably be an additional flag to the call that distinguishes "contours" from proper polygons. That way, the mathematically correct behavior isn't lost, but instead made optional. Everyone calls that function on contours, so that should be the default.
The one other answer "works" because now cv.moments()
is called on the mask drawn from the contour instead of the contour itself. You can just skip the entire contour calculation. Maybe work with connectedComponents()
instead.
The other other answer's explanation is wrong but it doesn't suggest a fix, only to skip the situation.
Upvotes: 6