OpenCV: Remove doubled contours on outlines of shapes without using RETR_EXTERNAL

Viewed 353

Open CV will register both an inner and an outer contour for an outline of a polygon.

Running with the test code below

import cv2
import numpy as np

def extract_contours():
    path = 'test.png'
    blank = np.zeros((184,184,3), np.uint8)
    blank[:] = (255,255,255)
    raw = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    raw = 255-raw
    img = cv2.cvtColor(raw, cv2.COLOR_BGR2GRAY)
    contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    print(len(contours))
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area > 400: 
            approx = cv2.approxPolyDP(cnt, 0.009 * cv2.arcLength(cnt, True), True)
            cv2.drawContours(blank, [approx], 0, (0, 0, 255), 1)

    cv2.imwrite('contours.png', blank)

extract_contours()

On the image

hollow square

will yield two sets of contours on the outer and inner edge as shown in

double contours

Is there any fast way to collapse the two sets of contours into a single contour, preferably the average of the two? Using I am fairly new to CV2 and computer vision in general so I don't know a lot of the tricks. I would rather not use RETR_EXTERNAL since I do not want to miss out on any nested shapes.

1 Answers

You can use the hierarchy variable you defined (when calling the cv2.findContours method) to determine whether a contour is on the exterior of the outline or the interior:

import cv2
import numpy as np

def extract_contours():
    path = 'test.png'
    blank = np.zeros((184, 184, 3), np.uint8)
    blank[:] = (255, 255, 255)
    raw = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    raw = 255 - raw
    img = cv2.cvtColor(raw, cv2.COLOR_BGR2GRAY)
    contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    for cnt, hrc in zip(contours, hierarchy[0]):
        area = cv2.contourArea(cnt)
        if area > 400: 
            approx = cv2.approxPolyDP(cnt, 0.009 * cv2.arcLength(cnt, True), True)
            if hrc[2] < 0:
                cv2.drawContours(blank, [approx], 0, (0, 0, 255), 1)
            elif hrc[3] < 0:
                cv2.drawContours(blank, [approx], 0, (0, 255, 0), 1)

    cv2.imwrite('contours.png', blank)

extract_contours()

Resulting image:

enter image description here

Drawing the contour in between the exterior and interior contours:

import cv2
import numpy as np

def extract_contours():
    path = 'test.png'
    blank = np.zeros((184, 184, 3), np.uint8)
    blank[:] = (255, 255, 255)
    raw = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    raw = 255 - raw
    img = cv2.cvtColor(raw, cv2.COLOR_BGR2GRAY)
    contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    exte = None
    inte = None
    for cnt, hrc in zip(contours, hierarchy[0]):
        area = cv2.contourArea(cnt)
        if area > 400: 
            approx = cv2.approxPolyDP(cnt, 0.009 * cv2.arcLength(cnt, True), True)
            if hrc[2] < 0:
                exte = approx.squeeze()
            elif hrc[3] < 0:
                inte = approx.squeeze()
    exte = exte[np.lexsort(exte.T)]
    inte = inte[np.lexsort(inte.T)]
    box = cv2.convexHull((exte[exte[:, 0].argsort()] + inte[inte[:, 0].argsort()]) // 2)
    cv2.drawContours(blank, [box], -1, (0, 0, 255), 1)
    cv2.imwrite('contours.png', blank)

extract_contours()

Resulting image:

enter image description here

Related