Opencv not finding all contours

情到浓时终转凉″ 提交于 2019-12-11 03:47:28

问题


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?

image 1

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?

image 3

image 4


回答1:


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.

  1. Convert your image to gray

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

  1. Thresholding

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)

  1. Sobel magnitude

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)

  1. thinning (optional)

I use the thinning function which is implemented in 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)

  1. 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))    
    

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.

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)  

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.




回答2:


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)

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

UPDATE

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:

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)




回答3:


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.

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.

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




回答4:


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) 



来源:https://stackoverflow.com/questions/51749668/opencv-not-finding-all-contours

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!