extract only one contour after FindContours operation - python

I am currently using FindContours and DrawContours function in order to segment an image.
I only extract external contours, and want to save only the contour which contains a given point.
I use h_next to move through the cv_seq structure and test if the point is contained using PointPolygonTest
I actually can find the contour that interests me, but my problem is to extract it.
Here is the python code :
def contour_from_point(contours, point, in_img):
"""
Extract the contour from a sequence of contours which contains the point.
For this to work in the eagle road case, the sequence has to be performed using the
FindContours function with CV_RETR_EXTERNAL
"""
if contours:
# We got at least one contour. Search for the one which contains point
contour = contours # first contour of the list
distance = cv.PointPolygonTest(contour, point, 0)
while distance < 0: # 0 means on eadge of contour
contour = contour.h_next()
if contour: # avoid end of contours
distance = cv.PointPolygonTest(contour, point, 0)
else :
contour = None
else:#
contour = None
return contour
At the end, I got contour. But this structure still contains all the contours that have not been tested yet.
How can I do to keep only the first contour of my output sequence?
Thanks by advance !

There is finally a way to get only one contour. Juste use another function that needs a cvseq in input, as ConvexHull for example. The output will be only the first contour of the sequence.

Related

findContours returns contour with duplicate points

OpenCV's findContours sometimes returns bad results
The code snippet attempts to find the largest contour in the edge images.
In the "bad" example, it seems as though most vertex's of the contour are needlessly duplicated. This causes a subsequent wrong contourArea and pointPolygonTest behavior.
import cv2
import imutils
from scipy import misc
edges = misc.imread('edges3.png')
cnts = cv2.findContours(edges.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
sorted_cnts = sorted(cnts, key = lambda c:cv2.arcLength(c,True), reverse = True)
largest_cnt = sorted_cnts[0]
print("Largest contour area",cv2.contourArea(largest_cnt))
print("Largest contour arc length",cv2.arcLength(largest_cnt,True))
print("Largest contour num of vertx",len(largest_cnt))
Bad code output:
Largest contour area 14.0
Largest contour arc length 2639.200133085251
Largest contour num of vertx 667
Good code output:
Largest contour area 95534.0
Largest contour arc length 1321.8721450567245
Largest contour num of vertx 340
The two attached photos are almost identical and should return similar results. However, the first returns a contour with a very small area, and double the arc length and vertex number compared to second one.
I cant upload picture in a comment. Is it possible that your edge detection has some fault and there is some minor opening? the resulting is top is counting the area next to the edge and break by opening. And bottom is counting the whole image? The blue area denotes the actual counted area. Because there is a break in the edge detection, then the area is actually pretty small. It is common for edge to partially fail on a few points.
if the assumption is there is some pixel break (you line is not continuous), the result form this assumption fits your description of
A. a very small area,
B. double the arc length and vertex number
C. Some point are duplicated as they are on the same line.
To deal with this opening is using morphological dilate or convex hull to close the gap.

finding largest connected component using opencv 2.4 in python 2.7

I am writing code in python 2.7.12 using opencv '2.4.9.1'.
I have a 2d numpy array containing values in range [0,255].
My aim is to find largest region containing value in range[x,y]
I found
How to use python OpenCV to find largest connected component in a single channel image that matches a specific value? as pretty well-explained .
Only, the catch is - it is meant for opencv 3 .
I can try to write a function of this type
[pseudo code]
def get_component(x,y,list):
append x,y to list
visited[x][y]=1
if(x+1<m && visited[x+1][y]==0)
get_component(x+1,y,list)
if(y+1<n && visited[x][y+1]==0)
get_component(x,y+1,list)
if(x+1<m)&&(y+1<n)&&visited[x+1][y+1]==0
get_component(x+1,y+1,list)
return
MAIN
biggest_component = NULL
biggest_component_size = 0
low = lowest_value_in_user_input_range
high = highest_value_in_user_input_range
matrix a = gray image of size mxn
matrix visited = all value '0' of size mxn
for x in range(m):
for y in range(n):
list=NULL
if(a[x][y]>=low) && (a[x][y]<=high) && visited[x][y]==1:
get_component(x,y,list)
if (list.size>biggest_component_size)
biggest_component = list
Get maximum x , maximum y , min x and min y from above list containing coordinates of every point of largest component to make rectangle R .
Mission accomplished !
[/pseudo code]
Such an approach will not be efficient, I think.
Can you suggest functions for doing the same with my setup ?
Thanks.
Happy to see my answer linked! Indeed, connectedComponentsWithStats() and even connectedComponents() are OpenCV 3+ functions, so you can't use them. Instead, the easy thing to do is just use findContours().
You can calculate moments() of each contour, and included in the moments is the area of the contour.
Important note: The OpenCV function findContours() uses 8-way connectivity, not 4-way (i.e. it also checks diagonal connectivity, not just up, down, left, right). If you need 4-way, you'd need to use a different approach. Let me know if that's the case and I can update..
In the spirit of the other post, here's the general approach:
Binarize your image with the thresholds you're interested in.
Run cv2.findContours() to get the contour of each distinct component in the image.
For each contour, calculate the cv2.moments() of the contour and keep the maximum area contour (m00 in the dict returned from moments() is the area of the contour).
Either keep the contour as a list of points if that's what you need, otherwise draw them on a new blank image if you want it as a mask.
I lack creativity today, so you get the cameraman as our example image as you didn't provide one.
import cv2
import numpy as np
img = cv2.imread('cameraman.png', cv2.IMREAD_GRAYSCALE)
Now, let's binarize to get some separated blobs:
bin_img = cv2.inRange(img, 50, 80)
Now let's find the contours.
contours = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
# For OpenCV 3+ use:
# contours = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[1]
Now for the main bit; looping through the contours and finding the largest one:
max_area = 0
max_contour_index = 0
for i, contour in enumerate(contours):
contour_area = cv2.moments(contour)['m00']
if contour_area > max_area:
max_area = contour_area
max_contour_index = i
So now we have an index max_contour_index of the largest contour by area, so you can access the largest contour directly just by doing contours[max_contour_index]. You could of course just sort the contours list by the contour area and grab the first (or last, depending on sort order). If you want to make a mask of the one component, you can use
cv2.drawContours(new_blank_image, contours, max_contour_index, color=255, thickness=-1)
Note the -1 will fill the contour as opposed to outlining it. Here's an example drawing the contour over the original image:
Looks about right.
All in one function:
def largest_component_mask(bin_img):
"""Finds the largest component in a binary image and returns the component as a mask."""
contours = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
# should be [1] if OpenCV 3+
max_area = 0
max_contour_index = 0
for i, contour in enumerate(contours):
contour_area = cv2.moments(contour)['m00']
if contour_area > max_area:
max_area = contour_area
max_contour_index = i
labeled_img = np.zeros(bin_img.shape, dtype=np.uint8)
cv2.drawContours(labeled_img, contours, max_contour_index, color=255, thickness=-1)
return labeled_img

How can I extract internal contours (holes) with python opencv?

is there an easy and direct way to extract the internal contours (holes) from an image using opencv 3.1 python ?
I know that I can use "area" as a condition. However, if I change the image resolution, the "areas" are not the same.
For instance, with this image:
How can I extract the internal holes?
_, contours, hier_ = cv2.findContours(img,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
areas = [cv2.contourArea(c) for c in millCnts]
max_area = np.max(areas)
Mask = np.ones(img.shape[:2], dtype="uint8") * 255
# I can do something like this (currently not working, just to show an example)
for c in contours:
if(( cv2.contourArea(c) > 8) and (cv2.contourArea(c)< 100000)):
cv2.drawContours(Mask ,[c],-1,0,1)
As I explained in my comment, you have to check the hierarchy return variable. After find contours you will get the contours (List of List of Points) and hierarchy (List of List).
The documentation is very clear in this:
hierarchy – Optional output vector, containing information about the
image topology. It has as many elements as the number of contours. For
each i-th contour contours[i] , the elements hierarchy[i][0] ,
hiearchy[i][1] , hiearchy[i][2] , and hiearchy[i][3] are set to
0-based indices in contours of the next and previous contours at the
same hierarchical level, the first child contour and the parent
contour, respectively. If for the contour i there are no next,
previous, parent, or nested contours, the corresponding elements of
hierarchy[i] will be negative.
So, this means that for each countour[i] you should get a hierarchy[i] that contains a List with 4 variables:
hierarchy[i][0]: the index of the next contour of the same level
hierarchy[i][1]: the index of the previous contour of the same level
hierarchy[i][2]: the index of the first child
hierarchy[i][3]: the index of the parent
So, saying that, in your case, there should be one without a parent, and you can check which one by checking the hierarchy[i][3] if it is negative.
It should be something like (untested code):
holes = [contours[i] for i in range(len(contours)) if hierarchy[i][3] >= 0]
* UPDATE:*
To summarize what we discussed in the chat,
The image was too big, and the contours had small holes: solved with dilate and erode with a kernel of size 75
The image needed to be inverted since OpenCV expects for dilate a black background
The algorithm was giving 2 big contours, one outside (as expected) and one inside (almost the same as the outside one), this is probably due to the image having some external (and closed) bumps. This was solved by removing the contour without a parent and its first child.

What does len(contour) means?

I have been working under the image of bacteria and a wish to take the number of bacteria from the image, and also need to classify the bacteria with specific shape and size.
I am using opencv python. Now i use the contour method.
contours,hierarchy = cv2.findContours(dst,1,2)
cnt = contours[0]
l = len(contours)
print l
li = list(contours)
print li
This give an output of l= 115 and li= some array values .
What does this means??
please help me in finding out the answer..e.coli image below:
Contours connects continuous points and puts all of them in an array. So each element in this array probably corresponds to a different bacteria (or a false detection, due to a connected color group that is a shadow etc).
When you say len(contours), you get the number of elements in this array. Therefore, you get a rough estimation of the number of bacterias.
In your case, there are 115 bacterias, or colors that are different than their surroundings which may or may not be bacterias. When you define a list for them and print the list, you get the properties of each element in this list, therefore you get the properties for each "connected point group" or each "object that is possibly a bacteria". Its all pretty straightforward really.
If you realize that you have many false detections here is what you do:
A group of bacterias appearing as one:
You threshold the image (convert it to black&white) and use the erode function first. Then use dilate function to remove their connections. Then go with findContours once more.
Stains detected as bacterias:
Make your thresholding only cover the bacterias color range, so everything else will be ignored.
See sources below, they might help:
http://docs.opencv.org/2.4/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.html
http://docs.opencv.org/trunk/d4/d73/tutorial_py_contours_begin.html#gsc.tab=0
cv2.findCountours returns a list of contours where each contour is a numpy array of points (2 columns for x, y coordinates). len(foo) is the length of list foo. So in your case it found 115 contours and your li is just a copy of the contours list.
You can easily examine the contours by using the drawContours function.
# draws contours in white color, outlines only (not filled)
cv2.drawContours(dst, contours, -1, (255,))
cv2.imshow("result", dst)
cv2.waitKey(-1)

OpenCV: findContours(): how contours are ordered?

When we want to find the contours of a given image according to a certain threshold, we use the function cv2.findContours() which returns among other things, the list of contours (a Pythonic list of arrays representing contours of the picture).
Here is how to use of the function:
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
My question:
What is the order that OpenCV uses toorder the countours ?
I mean on which criterion it says this contour is in position 0, the other in position 1 and so on ?
I need this information because I want to know which contour I am dealing with in my program and why it is given this or that position.
Hope you won't close my question, I am just a beginner.
I do not fully understand the question but from my understanding the Contour positions are based on the x and y pixel coordinates of the image which is what the returned vector list contain. So surely the criterion is they're position on the image. (Please consider this as a comment)

Categories

Resources