Using Hierarchy in Findcontours () in Opencv

Using hierarchy in findContours () in OpenCV?

The hierarchy returned by findContours has the following form:
hierarchy[idx][{0,1,2,3}]={next contour (same level), previous contour (same level), child contour, parent contour}

CV_RETR_CCOMP, returns a hierarchy of outer contours and holes.
This means elements 2 and 3 of hierarchy[idx] have at most one of these not equal to -1: that is, each element has either no parent or child, or a parent but no child, or a child but no parent.

An element with a parent but no child would be a boundary of a hole.

That means you basically go through hierarchy[idx] and draw anything with hierarchy[idx][3]>-1.

Something like (works in Python, but haven't tested the C++. Idea is fine though.):

findContours( image, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );

if ( !contours.empty() && !hierarchy.empty() ) {

// loop through the contours/hierarchy
for ( int i=0; i<contours.size(); i++ ) {

// look for hierarchy[i][3]!=-1, ie hole boundaries
if ( hierarchy[i][3] != -1 ) {
// random colour
Scalar colour( (rand()&255), (rand()&255), (rand()&255) );
drawContours( outImage, contours, i, colour );
}
}
}

Python OpenCV Contour Tree Hierarchy Structure

The main confusion here is probably the fact that the hierarchy returned is a numpy array with more dimensions than necessary. On top of that, it looks like the Python FindContours function returns a tuple that is a LIST of contours, and and NDARRAY of the hierarchy...

You can get a sensible array of hierarchy information that is more in line with the C docs by just taking hierarchy[0]. It would then be an appropriate shape to zip, for example, with the contours.

Below is an example that, will draw the outermost rectangles in green and the innermost rectangles in red on this image:

Sample Image

Output:

Sample Image

Note, by the way, that the wording in the OpenCV docs is a little ambiguous, but hierarchyDataOfAContour[2] describes the children of that contour (if it is negative then that is an inner contour), and hierarchyDataOfAContour[3] describes the parents of that contour (if it is negative then that is an exterior contour).

Also note: I looked into implementing the algorithm that you referred to in the OCR paper, and I saw that FindContours was giving me a lot of repeats of near-identical contours. This would complicate the finding of "Edge Boxes" as the paper describes. This may be because the Canny thresholds were too low (note that I was playing around with them as described in the paper), but there may be some way to reduce that effect or just look at the average deviation of the four corners of all the boxes and eliminate duplicates...

import cv2
import numpy

# Load the image
img = cv2.imread("/ContourTest.PNG")

# Split out each channel
blue, green, red = cv2.split(img)

def medianCanny(img, thresh1, thresh2):
median = numpy.median(img)
img = cv2.Canny(img, int(thresh1 * median), int(thresh2 * median))
return img

# Run canny edge detection on each channel
blue_edges = medianCanny(blue, 0.2, 0.3)
green_edges = medianCanny(green, 0.2, 0.3)
red_edges = medianCanny(red, 0.2, 0.3)

# Join edges back into image
edges = blue_edges | green_edges | red_edges

# Find the contours
contours,hierarchy = cv2.findContours(edges, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

hierarchy = hierarchy[0] # get the actual inner list of hierarchy descriptions

# For each contour, find the bounding rectangle and draw it
for component in zip(contours, hierarchy):
currentContour = component[0]
currentHierarchy = component[1]
x,y,w,h = cv2.boundingRect(currentContour)
if currentHierarchy[2] < 0:
# these are the innermost child components
cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),3)
elif currentHierarchy[3] < 0:
# these are the outermost parent components
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),3)

# Finally show the image
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV Contouring hierarchy

Here is the solution to detect inner rectangle and extract its left corner point, height and width.

import cv2
import numpy as np

def is_rect_contour(contour):
peri = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.01 * peri, True)
if len(approx) == 4:
return True
else:
return False

image = cv2.imread("./rect.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
thresh = cv2.threshold(blurred, 40, 255, cv2.THRESH_BINARY_INV)[1]

cnts = cv2.findContours(thresh.copy(), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0]
refined_contours = []
for cnt in cnts:
if is_rect_contour(cnt):
refined_contours.append(cnt)
inner_rect_cnt = min(refined_contours, key=cv2.contourArea)
(x, y, w, h) = cv2.boundingRect(inner_rect_cnt)
print("x: {}, y:{}, widhth:{}, height:{}".format(x, y, w, h))
cv2.drawContours(image, [inner_rect_cnt], -1, (0, 255, 0), 2)

cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output:

x: 425, y:126, widhth:1104, height:720

Result image

OpenCV contours and hierarchy

Your approach to the problem is wrong. You are supposed to invert the binary image and then perform contour operations. This is because the contours are formed only around the white regions.

This is what I did:

ret, thresh = cv2.threshold(img, 100, 255, 1)

Sample Image

Now I performed the contour operation. I used the cv2.RETR_EXTERNAL option to neglect contours inside the letters

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

Sample Image

Now I obtained the bounding box as you wanted:

for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),1)

Sample Image

Opencv xamarin findContours - how to use hierarchy

In case someone needs an answer to this, I figured it out some time ago, but didn't have the time to post it.

for(int i = 0; i >= 0;)
{
...
double[] contourInfo = hierarchy.Get(0, i);
i = (int)contourInfo[0]; // this gives next sibling
}

Turned out to be quite easy after you understand the esplaination here: http://docs.opencv.org/3.1.0/d9/d8b/tutorial_py_contours_hierarchy.html#gsc.tab=0

Hope this helps to somebody.

Dealing with null hierarchy in openCV findcontours

As posted in the comments above this was the issue:

If you feed it a completely black image, then there certainly won't be
any contours for it to find (quite simple to reproduce, that was the
first thing I tried and a reason why I'm asking these things). | Based
on your edits, you didn't do len(bigContours0), but rather
len(bigContours0[0]) -- that's a significant difference. If the list
is empty (length of 0), then accessing the first element will fail,
and that's why you get an error. | If you can't upload it, then at
least save it and inspect it in detail yourself. Or just try to make
some image that's safe, and causes same issue.

the image is black hence it does not contains any contour. Then printing the wrong element (I'm not fluent in Python) returned the error I was encountering. That's basically it.

Understanding contour hierarchies: How to distinguish filled circle/contour and unfilled circle/contour in OpenCV?

To distinguish between a filled contour and unfilled contour, you can use contour hierarchy when finding contours with cv2.findContours(). Specifically, you can select the contour retrieval mode to optionally return an output vector containing information about the image topology. There are the four possible modes:

  • cv2.RETR_EXTERNAL - retrieves only the extreme outer contours (no hierarchy)
  • cv2.RETR_LIST - retrieves all of the contours without establishing any hierarchical relationships
  • cv2.RETR_CCOMP - retrieves all of the contours and organizes them into a two-level hierarchy. At the top level, there are external boundaries of the components. At the second level, there are boundaries of the holes. If there is another contour inside a hole of a connected component, it is still put at the top level
  • cv2.RETR_TREE - retrieves all of the contours and reconstructs a full hierarchy of nested contours

Understanding contour hierarchies

So with this information, we can use cv2.RETR_CCOMP or cv2.RETR_TREE to return a hierarchy list. Take for example this image:

When we use the cv2.RETR_TREE parameter, the contours are arranged in a hierarchy, with the outermost contours for each object at the top. Moving down the hierarchy, each new level of contours represents the next innermost contour for each object. In the image above, the contours in the image are colored to represent the hierarchical structure of the returned contours data. The outermost contours are red, and they are at the top of the hierarchy. The next innermost contours -- the dice pips, in this case -- are green.

We can get that information about the contour hierarchies via the hierarchy array from the cv2.findContours function call. Suppose we call the function like this:

(_, contours, hierarchy) = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

The third return value, saved in the hierarchy variable in this code, is a three-dimensional NumPy array, with one row, X columns, and a "depth" of 4. The X columns correspond to the number of contours found by the function. The cv2.RETR_TREE parameter causes the function to find the internal contours as well as the outermost contours for each object. Column zero corresponds to the first contour, column one the second, and so on.

Each of the columns has a four-element array of integers, representing indices of other contours, according to this scheme:

[next, previous, first child, parent]

The next index refers to the next contour in this contour's hierarchy level, while the previous index refers to the previous contour in this contour's hierarchy level. The first child index refers to the first contour that is contained inside this contour. The parent index refers to the contour containing this contour. In all cases, an value of -1 indicates that there is no next, previous, first child, or parent contour, as appropriate. For a more concrete example, here are some example hierarchy values. The values are in square brackets, and the indices of the contours precede each entry.
If you printed out the hierarchy array you will get something like this

0:  [ 6 -1  1 -1]   18: [19 -1 -1 17]
1: [ 2 -1 -1 0] 19: [20 18 -1 17]
2: [ 3 1 -1 0] 20: [21 19 -1 17]
3: [ 4 2 -1 0] 21: [22 20 -1 17]
4: [ 5 3 -1 0] 22: [-1 21 -1 17]
5: [-1 4 -1 0] 23: [27 17 24 -1]
6: [11 0 7 -1] 24: [25 -1 -1 23]
7: [ 8 -1 -1 6] 25: [26 24 -1 23]
8: [ 9 7 -1 6] 26: [-1 25 -1 23]
9: [10 8 -1 6] 27: [32 23 28 -1]
10: [-1 9 -1 6] 28: [29 -1 -1 27]
11: [17 6 12 -1] 29: [30 28 -1 27]
12: [15 -1 13 11] 30: [31 29 -1 27]
13: [14 -1 -1 12] 31: [-1 30 -1 27]
14: [-1 13 -1 12] 32: [-1 27 33 -1]
15: [16 12 -1 11] 33: [34 -1 -1 32]
16: [-1 15 -1 11] 34: [35 33 -1 32]
17: [23 11 18 -1] 35: [-1 34 -1 32]

The entry for the first contour is [6, -1, 1, -1]. This represents the first of the outermost contours; note that there is no particular order for the contours, e.g., they are not stored left to right by default. The entry tells us that the next dice outline is the contour with index six, that there is no previous contour in the list, that the first contour inside this one has index one, and that there is no parent for this contour (no contour containing this one). We can visualize the information in the hierarchy array as seven trees, one for each of the dice in the image.

Sample Image

The seven outermost contours are all those that have no parent, i.e., those with an value of -1 in the fourth field of their hierarchy entry. Each of the child nodes beneath one of the "roots" represents a contour inside the outermost contour. Note how contours 13 and 14 are beneath contour 12 in the diagram. These two contours represent the innermost contours, perhaps noise or some lost paint in one of the pips. Once we understand how contours are arranged into a hierarchy, we can perform more sophisticated tasks, such as counting the number of contours within a shape in addition to the number of objects in an image.


Going back to your question, we can use hierarchy to distinguish between inner and outer contours to determine if a contour is filled or unfilled. We can define a filled contour as a contour with no child whereas a unfilled contour as at least one child. So with this screenshot of your input image (removed the box):

Sample Image

Result

Sample Image

Code

import cv2

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Filter using contour hierarchy
cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
hierarchy = hierarchy[0]
for component in zip(cnts, hierarchy):
currentContour = component[0]
currentHierarchy = component[1]
x,y,w,h = cv2.boundingRect(currentContour)
# Has inner contours which means it is unfilled
if currentHierarchy[3] > 0:
cv2.putText(image, 'Unfilled', (x,y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (36,255,12), 2)
# No child which means it is filled
elif currentHierarchy[2] == -1:
cv2.putText(image, 'Filled', (x,y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (36,255,12), 2)

cv2.imshow('image', image)
cv2.waitKey()


Related Topics



Leave a reply



Submit