Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Open CV Contour area miscalculation

I am just starting to play with OpenCV and I have found some very strange behaviour from the contourArea function.

See this image.

Original

It has three non connected areas, the left is a grouping of long strokes and on the top center there is a single dot and finally a big square on the right.

When I run my function, I get this result

Contours detected

Contour[0] Area: 221, Length: 70, Colour: Red 
Contour[1] Area: 13772, Length: 480, Colour: Green 
Contour[2] Area: 150, Length: 2370, Colour: Blue 

While I havent actually counted the area of the left part, It seems as if it encompasses much more than 150 pixels and would certainly have a higher value than the dot in the top center, I would say that dot should be able to fit in to the left part at least 10 times. The area of the square does work out.

Square Area
width = 118
height = 116
118 * 116 = 13,688

13,688 is really close to what opencv gave as the area (13,772), the difference is likely measurement error on my behalf. I manually calculated the area of the dot

Dot Area
width = 27
height = 6 
27*6 = 162

Not too far off from what opencv said it would be (221)

Reading from the OpenCV docs page on contourArea it says that it will give wrong results for contours with self intersections. Not really understanding what self intersections are, I made a test image.

ORIGINAL PIC HERE

As you can see I have a rectangle on the left and a cross in the middle and another cross rotated 45 deg. I would expect the cross to have slightly less than double the area of the rectangle due to the overlap in the center.

CONTOURED PIC HERE

Contour[0] Area: 1805, Length: 423, Colour: Red 
Contour[1] Area: 947, Length: 227, Colour: Green 
Contour[2] Area: 1825, Length: 415, Colour: Blue 

As you can see the area of the two crosses are slightly less than double the area of the rectangle. As expected.

I am not interested in capturing the inside of the square or getting a box drawn around the shape on the left and the dot (though it would be tangentially interesting) it's not specifically what I'm asking about in this question.

So my question: Why is the area of my irregular shape severly underestimated?

  1. Am I using the wrong function?
  2. Am I using the right function incorrectly?
  3. Have I found a bug in opencv?
  4. Does self intersections have a meaning that wasn't demonstrated in my test?

I copied most of this code from this tutorial

I have stripped down my code to this self contained example below.

def contour_test(name):
    import cv2 as cv
    colours = [{'name': 'Red ',   'bgr': (0, 0, 255)}, 
               {'name': 'Green ', 'bgr': (0, 255, 0)},
               {'name': 'Blue ',  'bgr': (255, 0, 0)}]
    src = cv.imread(cv.samples.findFile(name))
    src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    src_gray = cv.blur(src_gray, (3,3))
    threshold = 100
    canny_output = cv.Canny(src_gray, threshold, threshold * 2)
    contours, _ = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    # Get the moments
    mu = [None for i in contours]
    for i in range(len(contours)):
        mu[i] = cv.moments(contours[i])
    # Get the mass centers
    mc = [None for i in contours]
    for i in range(len(contours)):
        mc[i] = (mu[i]['m10'] / (mu[i]['m00'] + 1e-5), mu[i]['m01'] / (mu[i]['m00'] + 1e-5))
    # Draw contours
    drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
    for i, j in enumerate(contours):
        colour = colours[i]['bgr']
        cv.drawContours(drawing, contours, i, colour, 2)
        area = int(cv.contourArea(contours[i]))
        length = int(cv.arcLength(contours[i], True))
        print('Contour[{0}] Area: {1}, Length: {2}, Colour: {3}'.format(i, area, length, colours[i]['name']))
like image 829
hamsolo474 - Reinstate Monica Avatar asked Apr 07 '20 07:04

hamsolo474 - Reinstate Monica


People also ask

How to find Area of contour OpenCV?

Contour area is given by the function cv. contourArea() or from moments, M['m00'].

What is contour area in OpenCV?

Contours can be explained simply as a curve joining all the continuous points (along the boundary), having same color or intensity. The contours are a useful tool for shape analysis and object detection and recognition. For better accuracy, use binary images.

What is cv2 boundingRect?

The cv2. boundingRect() function of OpenCV is used to draw an approximate rectangle around the binary image. This function is used mainly to highlight the region of interest after obtaining contours from an image. As per the documentation there are two types of bounding rectangles: Straight Bounding Rectangle.

What is cv2 arcLength?

cv2. arcLength() is used to calculate the perimeter of the contour. If the second argument is True then it considers the contour to be closed. Then this perimeter is used to calculate the epsilon value for cv2. approxPolyDP() function with a precision factor for approximating a shape.


1 Answers

The inner part of the contours the findContours finds is supposed to be of filled with white color.

  • Don't use cv.Canny before findContours (cv.blur is also not required).
  • Make sure the contours are white and not black.
    You may use cv.threshold with cv.THRESH_BINARY_INV option for inverting polarity.
    It is recommended to add cv.THRESH_OTSU option for automatic threshold.

You may replace cv.blur and cv.Canny and cv.findContours(canny_output... with:

_, src_thresh = cv.threshold(src_gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
contours, _ = cv.findContours(src_thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

Result (of top image):

enter image description here

Contour[0] Area: 13531, Length: 476, Colour: Red
Contour[1] Area: 184, Length: 71, Colour: Green
Contour[2] Area: 4321, Length: 1202, Colour: Blue

Here is the complete (updated) code:

import numpy as np

def contour_test(name):
    import cv2 as cv
    colours = [{'name': 'Red ',   'bgr': (0, 0, 255)}, 
               {'name': 'Green ', 'bgr': (0, 255, 0)},
               {'name': 'Blue ',  'bgr': (255, 0, 0)}]
    src = cv.imread(cv.samples.findFile(name))
    src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    #src_gray = cv.blur(src_gray, (3,3))
    #threshold = 100
    #canny_output = cv.Canny(src_gray, threshold, threshold * 2)
    #contours, _ = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

    _, src_thresh = cv.threshold(src_gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
    cv.imshow('src_thresh', src_thresh);cv.waitKey(0);cv.destroyAllWindows()  # Show src_thresh for testing
    contours, _ = cv.findContours(src_thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

    # Get the moments
    mu = [None for i in contours]
    for i in range(len(contours)):
        mu[i] = cv.moments(contours[i])
    # Get the mass centers
    mc = [None for i in contours]
    for i in range(len(contours)):
        mc[i] = (mu[i]['m10'] / (mu[i]['m00'] + 1e-5), mu[i]['m01'] / (mu[i]['m00'] + 1e-5))
    # Draw contours
    drawing = np.zeros((src_thresh.shape[0], src_thresh.shape[1], 3), dtype=np.uint8)
    for i, j in enumerate(contours):
        colour = colours[i]['bgr']
        cv.drawContours(drawing, contours, i, colour, 2)
        area = int(cv.contourArea(contours[i]))
        length = int(cv.arcLength(contours[i], True))
        print('Contour[{0}] Area: {1}, Length: {2}, Colour: {3}'.format(i, area, length, colours[i]['name']))

    cv.imshow('drawing', drawing);cv.waitKey(0);cv.destroyAllWindows()  # Show drawing for testing

contour_test('img.jpg')

I added cv.imshow in two places for testing.

like image 154
Rotem Avatar answered Sep 29 '22 20:09

Rotem