arclength and contourarea wrong results?

I am using cv2's arclength and contourarea in my python project. To verify the results I've applied both functions to a discretized circle with r=100. The circumference of the circle should be around pi2100 = 628,31 and the area should be approximatly pi*100^2=31415.92.

Getting the circumference and area using cv2:

import numpy as np
import cv2

canvas = np.zeros((500, 500), dtype=np.uint8)
circle = cv2.circle(canvas, (250, 250) , 100, 10, -1)

contours, hierarchy = cv2.findContours(circle, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
circumference = cv2.arcLength(cnt, True)
area = cv2.contourArea(cnt)
print("c: %s" % circumference)
print("area: %s" % area)


I would expect the estimations to be close to the theoretical value but it returns circumference=661.75 and area=31134, which both have quite a big error.

I've also estimated the area by simply counting the non-zero pixels:

area_nzp = np.count_nonzero(circle)
print(area_nzp)


Which returns area=31417, being much closer to the theoretical value.

I was wondering if anyone could tell me how to use arclength and contourarea in the "correct way" such that the error will become smaller. Thanks in advance.

Edit: Inspired by LBerger's comment I plotted the error between the error of theoretical surface value of the circle vs ContourArea and the area estimated by counting the non-zero pixels. I do not understand why ContourArea's error can be this big. Does anyone know what actually happens when the function is run?

edit retag close merge delete

your circle is not an ideal one, but a bresenham approximation

try it with a rectangle, and it'll be exact

also, cv2.CHAIN_APPROX_SIMPLE

( 2020-06-23 05:00:13 -0500 )edit

Thanks for your comment. The circle is not ideal but don't you agree that the error is too big? Especially in comparison with the estimation of the area by counting the non-zero pixels

( 2020-06-23 06:10:02 -0500 )edit

Sort by ยป oldest newest most voted

Discrete geometry is not continuous geometry . you can plot theoritical and measure values :

You can estimate surface error using perimeter

For perimeter error is a function of contour length (number of pixel)

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

perimeter = []
surface = []
rayon = np.arange(2,450,10)
for r in rayon:
img = np.zeros((1000,1000),np.uint8)
img = cv.circle(img, (500, 500), r, 255, -1)
ctr, hierarchy = cv.findContours(img, cv.RETR_EXTERNAL,  cv.CHAIN_APPROX_NONE)
if len(ctr) != 1:
print('Must not occured')
for c in ctr:
surface.append(cv.contourArea(c))
perimeter.append(cv.arcLength(c, True))
print(surface)

plt.subplot(2, 1,1)
plt.plot(rayon, surface, 'b',marker = 11)
plt.plot(rayon, np.array(surface) + np.array(perimeter) , 'g',marker = 11)
plt.plot(rayon, rayon**2 * np.pi, 'r',marker = 10)
plt.title('Surface')
plt.ylabel('surface')
plt.legend(['min ContourArea','max ContourArea','theoritical'])
plt.subplot(2, 1,2)
plt.plot(rayon, surface - rayon**2 * np.pi, 'b',marker = 11)
plt.title('Surface error')
plt.ylabel('error')

plt.show()
plt.subplot(2, 1,1)
plt.plot(rayon, perimeter, 'b',marker = 11)
plt.plot(rayon, rayon*2 * np.pi, 'r',marker = 10)
plt.ylabel('perimeter')
plt.legend(['arcLength','theoritical'])
plt.subplot(2, 1,2)
plt.plot(rayon, perimeter - rayon*2 * np.pi, 'b',marker = 11)
plt.title('Perimeter error')
plt.ylabel('error')
plt.show()

more

Thank you for your answer. I understand that we are dealing with a discritized version of a circle, but what I don't understand is how the error can be so big. I made a similar plot as you did for the error between the theoretical area value and the value derived by counting the non-zero pixes (https://imgur.com/OiyyYLU).

( 2020-06-23 05:52:21 -0500 )edit

max surface error is 2piR(pixel^2) where pixel is equal to 1. in % error i/(2Rpixel^2)*100

( 2020-06-23 09:42:11 -0500 )edit