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.
Related
I use this code to find some blobs, and pick the biggest one.
contours, hierarchy = cv2.findContours(th1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if len(contours) != 0:
c = max(contours, key=cv2.contourArea)
Now, I would need to change this code in a way so it returns the contour that is in the middle of the frame. (its bounding box covers the center pixel of the image)
I am not able to figure out how to do this except getting the bounding box of all contours with
xbox, ybox, wbox, hbox = cv2.boundingRect(cont)
and then checking that x and y are smaller than the centere, and x+w and y+h aare bigger than the centre. It does not look like a efficient way tough, since there can be up to 500 small controus..
There is a function in OpenCV that will check if a given point is inside a contour (returns a 1), on the boundary (returns a 0) or outside (returns a -1).
cv2.pointPolygonTest()
I'd like to suggest this approach, maybe there is a more straightforward one. I'm writing code here, but instead giving a possible algorithm:
Iterate over contours and draw each single contour using a mask in a binary mat (b/w and filled)
Check if the center pixel (image width/2, image height/2) is equal to 1
That should work.
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
I have a binary image, and I am trying to represent a graph, such that the white parts of the image are the vertices and edges, where the big area white spots are the vertices, and the edges are the white parts that connect between the big white parts that I detected as vertices.
I managed to find the center of the big white parts by using OpenCV functions such as erosion, findContours and moments, using moments centroids.
So I have the vertices of the graph.
My next goal is to get the edges, meaning finding lines that are IN WHITE AREAS only, represented by 2 points, (x1,y1) and (x2,y2).
I tried using all kinds of function such as:
cv2.Canny()
cv2.findLine
cv2.findContour with different parameters on the binary image
For understanding my goal one can think about it as a maze, where the beginning of it is the biggest white spot in the image, and the end of the maze is the second biggest white spot, and the places you can walk through are all white areas of the image.
Some code segments I used in my project:
First finds the edges, given a binary image (finalImage) and return the centroids
def findCentroids(finalImage):
_, contours0, hierarchy = cv2.findContours(finalImage.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
moments = [cv2.moments(cnt) for cnt in contours0]
centroids = []
for M in moments:
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
centroids.append((cX, cY))
return centroids
So like I found centroids, I want to find more centroids (to make the image less erosed) and then perhaps find all edges that connect between these centroids. This doesn't seem like a good method so I hope to get better approaches in answers.
EDIT
So I thought about another idea, which is to use the connected components method. I try to use the connected components supplied by cv2, likewise:
output = cv2.connectedComponentsWithStats((imageForEdges), 8, cv2.CV_32S)
But the outcome is that only black spots are recognized as components, which is the opposite as what I need. I tried to use the inverted image and it gave the same results, as I assume the algorithm prefers the spots that are completely bounded, and not the background (which is the white color in my case, and the whole purpose of me using it, that it finds areas that are not bounded
)
Did you check out Iwanowski's algorithm ?
https://pdfs.semanticscholar.org/cd14/22f1e33022b0bede3f4a03844bc7dcc979ed.pdf
"The paper describes a method for the analysis of the content of a binary image in order to find its structure. The class of images it deals with consists of images showing at its foreground groups of objects connected one to another forming a graph-like structure. Described method extract automatically this structure from image bitmap and produces a matrix containing connections between all the objects shown on the input image"
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)
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.