4

I was just trying to understand what a contour means and what is the values stored when we create a contour using cv.FindContours function in OpenCV ( I am using OpenCV 2.3.1 and Python). I used following simple image for test:

enter image description here

After contour finding,i applied following commands in ipython:

In [8]: contour
Out[8]: <cv2.cv.cvseq at 0x90a31a0>

In [10]: list(contour)
Out[10]: 
 [(256, 190),
  (255, 191),
  (112, 191),
  (255, 191),
  (256, 190),
  (257, 191),
  (257, 190)]

First command says, contour is an cvSeq object.

I marked these points on the image, which give me following image(red marked circles are the points):

enter image description here

I don't understand what does it means.

So my question is what does the values in the result of second command (ie, list(contour)) denotes?


EDIT: Following is the code i used.

import cv
img = cv.LoadImage('simple.jpeg')
imgg = cv.LoadImage('simple.jpeg',cv.CV_LOAD_IMAGE_GRAYSCALE)
storage = cv.CreateMemStorage(0)
contours = cv.FindContours(imgg,storage,cv.CV_RETR_TREE,cv.CV_CHAIN_APPROX_SIMPLE,(0,0))
print list(contours)
for i in list(contours):
    cv.Circle(img,i,5,(0,0,255),1)
cv.ShowImage('img',img)
cv.WaitKey(0)
Abid Rahman K
  • 51,886
  • 31
  • 146
  • 157
  • What `method` are you using? `CV_CHAIN_APPROX_SIMPLE`? (In that case the docs say "an up-right rectangular contour is encoded with 4 points", but it doesn't seem so...) – mathematical.coffee Jan 30 '12 at 07:16
  • This is the command i used: `contours = cv.FindContours(img, storage, cv.CV_RETR_TREE, cv.CV_CHAIN_APPROX_SIMPLE, (0,0))` – Abid Rahman K Jan 30 '12 at 07:18
  • Hmm, when I do what you do, I get the four corners back out. Can you post your full code that reproduces this? `z=np.zeros((100,200)).astype('uint8'); cv2.rectangle(z,(20,30),(60,80),255,-1); cs=cv.FindContours(cv.fromarray(z),cv.CreateMemStorage(),mode=cv.CV_RETR_TREE,method=cv.CV_CHAIN_APPROX_SIMPLE); list(cs)` returns `[(20, 30), (20, 80), (60, 80), (60, 30)]` as expected. – mathematical.coffee Jan 30 '12 at 07:30
  • edited the question with code. – Abid Rahman K Jan 30 '12 at 07:37

2 Answers2

4

Ok, I've had a look at your picture, and you are getting the vertices of each region. It took me a while to work out because I use the cv2 interface not the cv one.

A few things:

  • your input image simple.jpeg has multiple grayscale values in it besides 0 and 255, most likely due to the jpeg compression.
  • hence, you get multiple regions out of your FindContours, for the different grey levels.
  • cv.FindContours returns multiple linked sequences, and you have to iterate through them to get all the econtours.
  • The contour that you get out for your example is one of the border ones.

To demonstrate let's draw all the contours.

contours = cv.FindContours(imgg,storage,cv.CV_RETR_TREE,cv.CV_CHAIN_APPROX_SIMPLE,(0,0))
colours = [ (0,255,0,0),   # green
            (255,0,0,0),   # blue
            (255,255,0,0), # cyan
            (0,255,255,0), # yellow
            (255,0,255,0), # magenta
            (0,0,255,0)]   # red 
i=0
while contours:
    cv.DrawContours(img, contours, colours[i], colours[i], 0, thickness=-1)
    i = (i+1) % len(colours)
    contours = contours.h_next() # go to next contour
cv.ShowImage('img',img)
cv.WaitKey(0)

contours drawn

So we see that the first contour you had with list(contours) in your original question is that small green strip down the bottom of the square, and the points you got correspond to its vertices.

The reason there are all these weirdo tiny contours around th edges of the rectangle and in the corners are because (I'd guess) of compression artefacts introduced by saving your image as a JPEG, which is lossy. If you saved your square with a lossless format (say PNG or TIFF) you'd get just one contour being defined by the corners of the rectangle.

Lessons learned:

  • cv.FindContours does give the "vertices" of each contour
  • you need contours = contours.h_next() to iterate through each contour
  • if you save as JPEG expect artefacts! Use TIFF/PNG/something lossless instead!
mathematical.coffee
  • 55,977
  • 11
  • 154
  • 194
  • Did you check your code? The while loop falls into an infinite loop. I converted my image to png and checked again with `list(contours)`. Now it clearly gave me 4 corners of that square. Good. So what exactly `cv.DrawContours` does? Join all these corners with straight lines? – Abid Rahman K Jan 30 '12 at 12:18
  • I did check my code, that's how I generated the picture - worked for me. [`cv.DrawContours`](http://opencv.itseez.com/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=drawcontours#cv.DrawContours) .. draws contours. The `thickness=-1` means to fill the contour in (default it just draws the border). – mathematical.coffee Jan 30 '12 at 12:37
  • It works fine when i removed the while loop. Otherwise, infinite loop. – Abid Rahman K Jan 30 '12 at 14:05
4

I did few more works on output of list(contour) to get an understanding about contour based on the answer provided above by mathematical.coffee.

1) I did a mistake on my test image. I thought it was a binary image while actually it was a grayscale image with some other colors also. ( Thanks to mathematical.coffee). So i changed image to pure black-and-white-only image so that i would get only one contour and tested again. This time, list(contour) gave a result of 4 values which, when drawn on image, were four corners of that box.

New output of image in question

So when we use 'cv.DrawContours' function, lines are drawn joining all these vertices. So i made an assumption that cv.FindContours stores the position of the vertices of contour which is actually a polygon.

2) To test again, i took another image which is a T-shape .

test-image-2

For this, i expect a list of 8 values which are 8 corners of T.

Output of image 2

`list(contour)' prints following list which contains 10 values. (2 extra values may be due to errors in my drawing)

[(92, 58), (92, 108), (174, 108), (175, 109), (175, 239), (225, 239), (225, 109), (226, 108), (285, 108), (285, 58)]

This means cv.FindContours create cvseq object. Inside it stores the values as i assumed above.

3) Above examples finds only one contour. What will be the condition when multiple contours are found out? I didn't clearly understand the concept of multiple linked sequences as explained by mathematical.coffee. So to test that, i took third image.

test image 3

Now cv.FindContours finds three contours. Remember, each contour is list of 4 corners of boxes. These three lists are stored in a single cvseq object and pointer points to first contour only, ie , list of vertices of first box only. So with above code, corners of only one box is drawn.

To get list of second vertices, we use the contour.h_next function ( Thanks to mathematical.coffee, i didn't know its function until now). Now it points to second box's contour. Thus we iterate through all list in it.

So i added a simple while loop as follows:

while contours:
   print list(contours)
   for i in list(contours):
       cv.Circle(img,i,5,(0,0,255),3)
   contours = contours.h_next()

And i got three list corresponding to three boxes' corners:

[(196, 237), (196, 279), (357, 279), (357, 237)]
[(141, 136), (141, 201), (346, 201), (346, 136)]
[(33, 39), (33, 92), (206, 92), (206, 39)]

And the output image :

Output of image 3

So you can expect what will be output of a circle, "which has a large number of vertices".

Well, everything is simple now. I couldn't get a grasp of contour values. That is why, all this mess. Thanks.

UPDATE - 1:

More details about contour in new cv2 module have given here : Contours -1 : Getting Started

UPDATE - 2:

All these explanation is correct with respect to cv2.CHAIN_APPROX_SIMPLE. But if we use cv2.CHAIN_APPROX_NONE instead, we get all the points on the contour. It is explained in detail with examples in this article : Contours - 5 : Hierarchy

Abid Rahman K
  • 51,886
  • 31
  • 146
  • 157