Opencv not finding all contours

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Opencv not finding all contours



I'm trying to find the contours of this image, but the method findContours only returns 1 contour, the contour is highlighted in image 2. I'm trying to find all external contours like these circles where the numbers are inside. What am i doing wrong? What can i do to accomplish it?



enter image description here
image 1



enter image description here
image 2



Below is the relevant portion of my code.


thresh = cv2.threshold(image, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)



When i change cv2.RETR_EXTERNAL to cv2.RETR_LIST it seems to detect the same contour twice or something like this. Image 3 shows when the border of circle is first detected and then it is detected again as shows image 4. I'm trying to find only outer borders of these circles. How can i accomplish that?


cv2.RETR_EXTERNAL


cv2.RETR_LIST



enter image description here image 3



enter image description here image 4




4 Answers
4



Instead of finding contours, I would suggest applying the Hough circle transform using the appropriate parameters.



Finding contours poses a challenge. Once you invert the binary image the circles are in white. OpenCV finds contours both along the outside and the inside of the circle. Moreover since there are letters such as 'A' and 'B', contours will again be found along the outside of the letters and within the holes. You can find contours using the appropriate hierarchy criterion but it is still tedious.



Here is what I tried by finding contours and using hierarchy:



Code:


#--- read the image, convert to gray and obtain inverse binary image ---
img = cv2.imread('keypad.png', 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

#--- find contours ---
_, contours, hierarchy = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

#--- copy of original image ---
img2 = img.copy()

#--- select contours having a parent contour and append them to a list ---
l =
for h in hierarchy[0]:
if h[0] > -1 and h[2] > -1:
l.append(h[2])

#--- draw those contours ---
for cnt in l:
if cnt > 0:
cv2.drawContours(img2, [contours[cnt]], 0, (0,255,0), 2)

cv2.imshow('img2', img2)



enter image description here



For more info on contours and their hierarchical relationship please refer this



I have a rather crude way to ignore unwanted contours. Find the average area of all the contours in list l and draw those that are above the average:


l



Code:


img3 = img.copy()
a = 0
for j, i in enumerate(l):
a = a + cv2.contourArea(contours[i])
mean_area = int(a/len(l))

for cnt in l:
if (cnt > 0) & (cv2.contourArea(contours[cnt]) > mean_area):
cv2.drawContours(img3, [contours[cnt]], 0, (0,255,0), 2)

cv2.imshow('img3', img3)



enter image description here



The problem is the flag cv2.RETR_EXTERNAL that you used in the function call. As described in the OpenCV documentation, this only returns the external contour.


cv2.RETR_EXTERNAL



Using the flag cv2.RETR_LIST you get all contours in the image. Since you try to detect rings, this list will contain the inner and the outer contour of these rings.


cv2.RETR_LIST



To filter the outer boundary of the circles, you could use cv2.contourArea() to find the larger of two overlapping contours.


cv2.contourArea()





when i try cv2.RETR_LIST it works, but it seems to return same contour twice. I am trying to find only the outer border of these circles.
– Bruno Brito
Aug 8 at 15:54



I am not sure this is really what you expect nevertheless in case like this there is many way to help findContours to do its job.
Here is a way I use frequently.



Convert your image to gray



Ig = cv2.cvtColor(I,cv2.COLOR_BGR2GRAY)


Ig = cv2.cvtColor(I,cv2.COLOR_BGR2GRAY)



gray image



The background and foreground values looklike quite uniform in term of colours but locally they are not so I apply an thresholding based on Otsu's method in order to binarise the intensities.


_,It = cv2.threshold(Ig,0,255,cv2.THRESH_OTSU)



image thresh



In order to extract only the contours I process the magnitude of the Sobel edges detector.


sx = cv2.Sobel(It,cv2.CV_32F,1,0)

sy = cv2.Sobel(It,cv2.CV_32F,0,1)

m = cv2.magnitude(sx,sy)

m = cv2.normalize(m,None,0.,255.,cv2.NORM_MINMAX,cv2.CV_8U)



sobel



I use the thinning function which is implemented in ximgproc.


ximgproc



The interest of the thining is to reduce the contours thickness to as less pixels as possible.


m = cv2.ximgproc.thinning(m,None,cv2.ximgproc.THINNING_GUOHALL)



thinned_image



Final Step findContours


_,contours,hierarchy = cv2.findContours(m,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
disp = cv2.merge((m,m,m)
disp = cv2.drawContours(disp,contours,-1,hierarchy=hierarchy,color=(255,0,0))



ctrs



Hope it help.



I think an approach based on SVM or a CNN might be more robust.
You can find an example here.
This one may also be interesting.



-EDIT-



I found a let say easier way to reach your goal.



Like previously after loading the image applying a threshold ensure that the image is binary.
By reversing the image using a bitwise not operation the contours become white over a black background.
Applying cv2.connectedComponentsWithStats return (among others) a label matrix in which each connected white region in the source has been assign a unique label.
Then applying findContours based on the labels it is possible give the external contours for every areas.


cv2.connectedComponentsWithStats


findContours


import numpy as np
import cv2
from matplotlib import pyplot as plt




I = cv2.imread('/home/smile/Downloads/ext_contours.png',cv2.IMREAD_GRAYSCALE)

_,I = cv2.threshold(I,0.,255.,cv2.THRESH_OTSU)
I = cv2.bitwise_not(I)

_,labels,stats,centroid = cv2.connectedComponentsWithStats(I)

result = np.zeros((I.shape[0],I.shape[1],3),np.uint8)

for i in range(0,labels.max()+1):
mask = cv2.compare(labels,i,cv2.CMP_EQ)

_,ctrs,_ = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

result = cv2.drawContours(result,ctrs,-1,(0xFF,0,0))

plt.figure()
plt.imshow(result)



external_boundaries



P.S. Among the outputs return by the function findContours there is a hierachy matrix.
It is possible to reach the same result by analyzing that matrix however it is a little bit more complex as explain here.


findContours





Thanks for the interesting links and for sharing the cv2.ximgproc module. Never knew about it until today !!! +1
– Jeru Luke
Aug 8 at 19:06



cv2.ximgproc



Another approach would be to find circles using Hough Circle transform after finding the appropriate parameters.



Code:


img = cv2.imread('keypad.png', 0)


img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)

circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=10, maxRadius=50)
circles = np.uint16(np.around(circles))

for i in circles[0,:]:

#--- draw the circle ---
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
centre = (i[0],i[1])

cv2.imshow('detected circles',cimg)



enter image description here






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard