1

I'm finding the contour of a thresholded image and drawing it like so:

self.disc_contour = cv2.findContours(self.thresh.copy(), cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)[1] 
cv2.drawContours(self.original_image, self.disc_contour, -1, (0,255,0), 2)

and I get the contour as desired:

(ignore the inner circle. The outer part is the contour in context)

enter image description here

But if I change self.disc_contour in the drawContour function to self.disc_contour[0] I get the following result:

enter image description here

What could be the reason?

Community
  • 1
  • 1
  • 1
    The second parameter should be a list of contours -- i.e. a list of lists of points. I think that `self.disc_contour[0]` is only a single contour, and it seems like openCV treats each point as separate curve. Try `[self.disc_contour[0]]` instead. (or `self.disc_contour[0:1]`). – Dan Mašek Mar 30 '16 at 15:10
  • Another thing i've just noticed -- `cv2.findContours` returns a pair: `(contours, hierarchy)`. You take the second element (index 1), which is the hiearchy, but treat it as the contour list? Oh, or perhaps you're using CV 3.x rather than 2.4.x ? It seems that the API changed slightly. Could you clarify this and add a working example to let us reproduce it? – Dan Mašek Mar 30 '16 at 15:27
  • @DanMašek, your solution worked. If you post it as an answer, I'll accept it. Also, yes, I'm using Opencv 3.1. Can you please explain why your solution works? Thanks! –  Mar 30 '16 at 15:49

1 Answers1

5

NB: Specific to OpenCV 3.x

The second result from cv2.findContours is a list of contours. The second parameter of cv.drawContours should be a list of contours.

A contour is represented as a list (or array) of points. Each point is a list of coordinates.

There are multiple ways how to draw only a single contour:

import cv2

src_img = cv2.imread("blob.png")
gray_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2GRAY)

contours = cv2.findContours(gray_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)[1]

print(contours)

# Choose one:    

# Draw only first contour from the list
cv2.drawContours(src_img, contours, 0, (0,255,0), 2)
# Pass only the first contour (v1)
cv2.drawContours(src_img, [contours[0]], -1, (0,255,0), 2)
# Pass only the first contour (v2)
cv2.drawContours(src_img, contours[0:1], -1, (0,255,0), 2)


cv2.imshow("Contour", src_img)
cv2.waitKey()

Sample input image:

When we inspect the result of cv2.findContours, the reason why you were seeing dots becomes apparent -- there are 4 levels of nesting.

[
    array([
        [[ 95,  61]], # Point 0
        [[ 94,  62]], # Point 1
        [[ 93,  62]],
        ... <snip> ...
        [[ 98,  61]],
        [[ 97,  61]],
        [[ 96,  61]]
    ]) # Contour 0
]

According to the definitions at the beginning of this answer, we can see that the points in this case are wrapped in an additional list, e.g. [[ 98, 61]]. OpenCV apparently deals with this correctly - I suppose this was intended as a feature.

If we remove the outer list by using only the first element of contours, we effectively turn each point into a separate contour containing a single point.

array([
    [
        [ 95,  61] # Point 0
    ], # Contour 0
    [
        [ 94,  62] # Point 0
    ], # Contour 1
    ... and so on
])
Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • Is `[self.disc_contour[0]]` a way of preserving coordinates while also removing the outer list? Because I just noticed, that while the contour is no longer drawn dotted, points are still missing. Is there a way I can retrieve all the coordinates of a contour? –  Mar 30 '16 at 17:25
  • The contour will remain unchanged. It makes a new list containing only the first element of the original list. Element in this case being one contour. What's `len(self.disc_contour)` in that scenario? What if you select the contour to draw using the third parameter to `cv2.drawContours`? Is it possible that some contours overlap? How did you figure out you're missing some points? – Dan Mašek Mar 30 '16 at 17:33
  • I figured out I'm missing some points because there's no same y coordinate as the centroid, or even x coordinate for that matter. Also, `len(self.disc_contour)` is 1. –  Mar 30 '16 at 17:51
  • In that case `self.disc_contour == [self.disc_contour[0]]`. I don't think there's anything guaranteeing that a contour would contain a point with the same x or y coordinate as the centroid. – Dan Mašek Mar 30 '16 at 17:57
  • No, I mean if I find a centroid, (x, y), there's obviously a point on the contour where the y coordinate matches (right above and below the centroid), and a point where the x coordinate matches (to the left and right of the centroid) –  Mar 30 '16 at 18:07
  • Oh, right, I see now -- it seems as if `cv2.CHAIN_APPROX_NONE` is not taking effect, and you're expecting to see all those border points, my bad. – Dan Mašek Mar 30 '16 at 18:27
  • Right, great catch. Is there an alternative for `cv2.CHAIN_APPROX_NONE` so I can get all the points? Or is there a way I can obtain all the coordinates of the contour that is drawn - that is the complete contour, rather than the dotted one. –  Mar 30 '16 at 18:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107773/discussion-between-dan-masek-and-stack). – Dan Mašek Mar 30 '16 at 18:30