cv2.findContours + cv2.contourArea unexpected result

Viewed 241

Summary

Sample Image

cv.findContours + sum of cv2.contourArea returns 0.0 on this image.

Steps to reproduce

# Create sample image
import numpy as np
a = np.array([
   [0, 0],
   [0, 255]
])

img = np.vstack([np.hstack([a]*100)]*100)
# Show image
from PIL import Image
Image.fromarray(img).show()

Sample Image

import cv2
cv2.__version__
>>> 4.3.0
cnts, hierachy = cv2.findContours(img.astype(np.uint8), 
                                  mode=cv2.RETR_LIST, 
                                  method=cv2.CHAIN_APPROX_NONE)
len(cnts)
>>> 10000
sum(cv2.contourArea(cnt) for cnt in cnts)
>>> 0.0

RTFM

cnts[-1]
>>> array([[[1,1]]], dtype=int32)

The contours are calculated according to the documentation with a border following algorithm [1] This results in the contour describing the inner polygon of a contour. The unexpected part is, calculating the area expects an outer or enclosing polygon of the contour.

The extreme case above is having 10000 very small sized objects with 1px area each. The inner polygon of 1px has area 0. The sum of all contour areas will still be 0, whereas 10000 is expected.

There is a discussion on github issue that this is actually desired behavior and objects with 1px should have area 0. For defect anlaysis, this is just unacceptable.

How to go about finding a consistent area calculation in the general case?

Discarded Solutions

  1. Summing pixel values: Summing over the rendered pixel values in the whole image will give a very accurate total area but not the area for indivual components. Rendering the contours into seperate images and summing there has low performance.

  2. Dilating contours before findContours: Dilating will mess with fine grained contour borders. Further, each contour has to be drawn and dilated separately to not agglomerate multiple contours into one. This once again delivers low perfromance.

  3. Use connectedCompontens instead: connectedComponentsWithStats will only return the pixel count of a component, which is a good approximation for the area but not as accurate as calculating the area of the polygon. E.g. try experimenting with lots of very small triangles / circles.

  4. Use skimage.measure.find_contours: This function will return the marching squares approximation of a contour. This approximaiton method will yield an area of 2px for a polygon constructed form a 1px object.

With the decades of image processing we have gone so far, i am sure there must exist a clean, performant solution to return consistent correct polygon + area calcualtion. I am looking forward to your suggestions.


[1] Satoshi Suzuki and others. Topological structural analysis of digitized binary images by border following. Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.

0 Answers
Related