Dan
Dan

Reputation: 872

OpenCV - Drawing contours as they are

I have what seems to be like a rather simple question - I have an image from which I'm extracting contours using the following code -

import numpy as np
import cv2

def findAndColorShapes(inputFile):
    # Find contours in the image
    im = cv2.imread(inputFile)
    imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    ret,thresh = cv2.threshold(imgray,127,255,0)
    contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

this find contours in the image very well, and then I draw them using -

cv2.drawContours(fullIm, [con], -1, (0,255,0), 2)

Some of the shapes are hollow (an outlined circle, for example), while some are filled. I would like to draw the contours the way the appear in the original image. e.g., if the contour is a filled circle, it should be drawn with its filling, and if its just an outline - as an outline.

I tried many things (among them to change the mode in findContours to CHAIN_APPROX_NONE instead of CHAIN_APPROX_SIMPLE), and to change the 5th parameter in drawContours, but non worked.

Edit: Adding a sample image - Left circle should be drawn empty, while the right square should be drawn full.

Left circle should be drawn empty, while the right square should be drawn full

Do you know of anyway it could be done?

Thanks!

Dan

Upvotes: 0

Views: 6740

Answers (3)

Zarokka
Zarokka

Reputation: 3056

This is how you would create a mask image (that is the filled contours) and then "filter" the source image with that mask to get a result.

In this snipped "th" is the threshold image (single channel)

#np comes from import numpy as np
mask = np.zeros(th.shape, np.uint8)  # create a black base 'image'
mask = cv2.drawContours(mask, contours, -1, 255, cv2.FILLED)  # set everything to white inside all contours

result = np.zeros(th.shape, np.uint8)  
result = np.where(mask == 0, result, th)  # set everything where the mask is white to the value of th

Note: findContours manipulates the given image! You may want to pass a copy (np.copy(th)) to it, if you want to use the thresholded image else where.

Upvotes: 0

Dan
Dan

Reputation: 872

If someone will ever need to do something similar one day, this is the code I eventually used. It is not very efficient, but it works well and time is not a factor in this project (notice I used red and green for the contours threshold in cv2.threshold(imgray,220,255,0). You may want to change that) -

def contour_to_image(con, original_image):
    # Get the rect coordinates of the contour
    lm, tm, rm, bm = rect_for_contour(con)

    con_im = original_image.crop((lm, tm, rm, bm))

    if con_im.size[0] == 0 or con_im.size[1] == 0:
        return None

    con_pixels = con_im.load()

    for x in range(0, con_im .size[0]):
        for y in range(0, con_im.size[1]):
            # If the pixel is already white, don't bother checking it
            if con_im.getpixel((x, y)) == (255, 255, 255):
                continue

            # Check if the pixel is outside the contour. If so, clear it
            if cv2.pointPolygonTest(con, (x + lm, y + tm), False) < 0:
                con_pixels[x, y] = (255, 255, 255)

    return con_im

def findAndColorShapes(input_file, shapes_dest_path):
    im = cv2.imread(input_file)
    imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(imgray, 220, 255, 0)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

    i = 0

    for con in contours:
        con_im = contour_to_image(con, Image.open(input_file))
        if con_im is not None:
            con_im.save(shapes_dest_path + "%d.png"%i)
            i += 1

Where np_to_int() and rect_for_contour() are 2 simple helper functions -

def np_to_int(np_val):
    return np.asscalar(np.int16(np_val))

def rect_for_contour(con):
    # Get coordinates of a rectangle around the contour
    leftmost = tuple(con[con[:,:,0].argmin()][0])
    topmost = tuple(con[con[:,:,1].argmin()][0])
    bottommost = tuple(con[con[:,:,1].argmax()][0])
    rightmost = tuple(con[con[:,:,0].argmax()][0])

    return leftmost[0], topmost[1], rightmost[0], bottommost[1]

Upvotes: 2

Haris
Haris

Reputation: 14043

You can check hierarchy parameter to check whether the contour has child(not filled), or not(filled),

For example,

vector< Vec4i > hierarchy

where for an i-th contour

hierarchy[i][0] = next contour at the same hierarchical level
hierarchy[i][1] = previous contour at the same hierarchical level
hierarchy[i][2] = denotes its first child contour
hierarchy[i][3] = denotes index of its parent contour

If for the contour i there are no next, previous, parent, or nested contours, the corresponding elements of hierarchy[i] will be negative. So for each contour you have to check is there child or not,

And

if child contour-> Draw contour with thickness=1;
if no child contour-> Draw contour with thickness=CV_FILLED;

I think this method will work for the images like you posted.

Also see the answer here might be helpful.

Upvotes: 0

Related Questions