Reputation: 872
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.
Do you know of anyway it could be done?
Thanks!
Dan
Upvotes: 0
Views: 6740
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
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
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