Python – Find Area of a OpenCV Contour

image processingopencvpython

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:

  • Read image -> img =cv2.imread(fileName,0)
  • apply Canny filter -> edges = cv2.Canny(img,lowerThreshold,lowerThreshold*2)
  • find contours -> contours, hierarchy = cv2.findContours(edges,cv2.cv.CV_RETR_LIST,cv2.cv.CV_CHAIN_APPROX_NONE)
  • find longest contour
  • ensure contour is closed
  • find moments -> cv2.moments(cnt)

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.
http://www.negative-probability.co.uk/docs/ImageWContour_0.png

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']))

Best Answer

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

Related Topic