Reputation: 1518
On a recent set of images, my OpenCV code stopped finding the correct area of a contour. This appears to happen when the contour is not closed. I have tried to ensure the contour is closed to no avail.
Edit: The problem is that there are gaps in the contour.
Background: I have a series of images of a capsule in a channel and I want to measure the area of the shape as well as the centroid from the moments.
Problem: When the contour is not closed, the moments are wrong.
Edit: When I have gaps, the contour is not of the whole shape and hence the incorrect area.
What I do:
A working example with test images can be found here.
There is a question regarding closing a contour but neither of the suggestions worked. Using cv2.approxPolyDP does not change the results, although it should return a closed contour. Adding the first point of the contour as the last, in order to make it closed, also does not resolve the issue.
An example of an image with the contour draw on it is below. Here, the area is determined as 85 while in an almost identical image it is 8660, which is what it should be.
Any advice would be appriciated.
Code:
img =cv2.imread(fileName,0)
edges = cv2.Canny(img,lowerThreshold,lowerThreshold*2)
contours, hierarchy = cv2.findContours(edges,cv2.cv.CV_RETR_LIST,cv2.cv.CV_CHAIN_APPROX_NONE) #cv2.cv.CV_CHAIN_APPROX_NONE or cv2.cv.CV_CHAIN_APPROX_SIMPLE
#Select longest contour as this should be the capsule
lengthC=0
ID=-1
idCounter=-1
for x in contours:
idCounter=idCounter+1
if len(x) > lengthC:
lengthC=len(x)
ID=idCounter
if ID != -1:
cnt = contours[ID]
cntFull=cnt.copy()
#approximate the contour, where epsilon is the distance to
#the original contour
cnt = cv2.approxPolyDP(cnt, epsilon=1, closed=True)
#add the first point as the last point, to ensure it is closed
lenCnt=len(cnt)
cnt= np.append(cnt, [[cnt[0][0][0], cnt[0][0][1]]])
cnt=np.reshape(cnt, (lenCnt+1,1, 2))
lenCntFull=len(cntFull)
cntFull= np.append(cntFull, [[cntFull[0][0][0], cntFull[0][0][1]]])
cntFull=np.reshape(cntFull, (lenCntFull+1,1, 2))
#find the moments
M = cv2.moments(cnt)
MFull = cv2.moments(cntFull)
print('Area = %.2f \t Area of full contour= %.2f' %(M['m00'], MFull['m00']))
Upvotes: 4
Views: 14602
Reputation: 1
An alternate approach is to use the contour points to find the area. Here nContours has previously been found thru cvFindContours(). I have used MFC CArray here. You can use std::vector alternatively.
////////////////////////////////////////////
CvSeq* MasterContour = NULL;
if (nContours > 1)
{
// Find the biggest contour
for (int i = 0; i < nContours; i++)
{
CvRect rect = cvBoundingRect(m_contour, 1);
if (rect.width > rectMax.width)
MasterContour = m_contour;
if (m_contour->h_next != 0)
m_contour = m_contour->h_next;
else
break;
}
}
else
MasterContour = m_contour;
arOuterContourPoints.RemoveAll();
CArray<CPoint, CPoint> arOuterTrackerPoints;
for (int i = 0; i < MasterContour->total; i++)
{
CvPoint *pPt;
pPt = (CvPoint *)cvGetSeqElem(MasterContour, i);
arOuterContourPoints.Add(CPoint(pPt->x, pPt->y));
}
int nOuterArea = 0;
for (int i = 0; i < arOuterContourPoints.GetSize(); i++)
{
if (i == (arOuterContourPoints.GetSize() - 1))
nOuterArea += (arOuterContourPoints[i].x * arOuterContourPoints[0].y - arOuterContourPoints[0].x * arOuterContourPoints[i].y);
else
nOuterArea += (arOuterContourPoints[i].x * arOuterContourPoints[i+1].y - m_arOuterContourPoints[i+1].x * m_arOuterContourPoints[i].y);
}
nOuterAreaPix = abs(nOuterArea / 2.0);
/////////////////////////////////////////////////////////////
Upvotes: 0
Reputation: 113
Having dealt with a similar problem, an alternative solution (and arguably simpler with less overhead) is to use the morphology opening functionality, which performs an erosion followed by a dilation. If you turn this into a binary image first, perform the opening operation, and the do the Canny detection, that should do the same thing, but without having to iterate with the filter. The only thing you will have to do is play with the kernel size a couple times to identify an appropriate size without losing too much detail. I have found this to be a fairly robust way of making sure the contours are closed.
Morphological operations documentation
Upvotes: 1
Reputation: 1518
My problem was, as @HugoRune pointed out, that there are gaps in the countour. The solution is to close the gaps.
I found it difficult to find a general method to close the gaps, so I iterativly change the threshold of the Canny filter and performing morphological closing until a closed contour is found.
For those struggeling with the same problem, there are several good answers how to close contours, such as this or this
Upvotes: 3