Edgar H
Edgar H

Reputation: 1518

How to close contour over outline rather than edge - OpenCV

Tl;DR: How to measure area enclosed by contour rather than just the contour line itself

I want to find the outline of the object in the below image and have a code that works for most cases. Original Image

Thresholding and adpative thresholding do not work reliably as the ligthing changes. I use a Canny edge detection and check the area to ensure I found the proper contour. However, once in a while, when there is a gap that cannot be closed by morphological closing, the shape is correct but the area is of the contour line instead of the whole object.

Canny Contour Output

What I usually do is use convexHull, as it returns a contour around the object. However, in this case the object curves inwards along the top and convexHull isn't a good approximation to the area anymore.

Contour after Convex Hull

I tried using approxPolyDP but the area that gets returned is of the contour line rather than the object.

How can I get the approxPolyDP to return a similar closed contour around the object, just like the convexHull function does?

Code illustrating this using the above picture:

import cv2
img = cv2.imread('Img_0.jpg',0)
cv2.imshow('Original', img)

edges = cv2.Canny(img,50,150) 
cv2.imshow('Canny', edges)

contours, hierarchy = cv2.findContours(edges,cv2.cv.CV_RETR_EXTERNAL,cv2.cv.CV_CHAIN_APPROX_NONE)

cnt = contours[1] #I have a function to do this but for simplicity here by hand

M = cv2.moments(cnt) 
print('Area = %f \t' %M['m00'], end="")

cntHull = cv2.convexHull(cnt, returnPoints=True)
cntPoly=cv2.approxPolyDP(cnt, epsilon=1, closed=True)
MHull = cv2.moments(cntHull)
MPoly = cv2.moments(cntPoly)
print('Area after Convec Hull = %f \t Area after apporxPoly = %f \n' %(MHull['m00'], MPoly['m00']), end="")

x, y =img.shape
size = (w, h, channels) = (x, y, 1)
canvas = np.zeros(size, np.uint8)
cv2.drawContours(canvas, cnt, -1, 255)
cv2.imshow('Contour', canvas)

canvas = np.zeros(size, np.uint8)
cv2.drawContours(canvas, cntHull, -1, 255)
cv2.imshow('Hull', canvas)

canvas = np.zeros(size, np.uint8)
cv2.drawContours(canvas, cntPoly, -1, 255)
cv2.imshow('Poly', canvas)

The output from the code is

Area = 24.500000    Area after Convec Hull = 3960.500000     Area after apporxPoly = 29.500000 

Upvotes: 3

Views: 3908

Answers (1)

Aaron
Aaron

Reputation: 11075

Here's a very promising ppt from geosensor.net that discusses several algorithms. My recommendation would be to use the swing arm method with a limited radius.

Another completely un-tested, off the wall idea I have is to scan across the image by row and column (more directions increase accuracy) and color in the regions between line intersections:

          _______
         /-------\
        /---------\
--------+---------+------ (fill between 2 intersections)     
        |         |
        |
--------+---------------- (no fill between single intersection)
         \
          -------

the maximum error would then decrease as the number of line directions scanned increases (more than 90 and 45 degrees). Getting a final area would then be as simple as a pixel count.

Upvotes: 1

Related Questions