我正在尝试查找此图像的轮廓,但是方法 findContours 仅返回 1 轮廓,轮廓在图像2中突出显示。我正在尝试找到所有外部轮廓,例如这些数字在其中的圆圈。我究竟做错了什么?我该怎么做?
下面是我代码的相关部分。
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)
当我将cv2.RETR_EXTERNAL
更改为cv2.RETR_LIST
时,似乎两次检测到相同的轮廓或类似的东西。图像3显示了何时首先检测到圆的边界,然后如图4所示再次被检测到。我试图仅查找这些圆的外边界。我该怎么办?
答案 0 :(得分:2)
问题是您在函数调用中使用的标志cv2.RETR_EXTERNAL
。如the OpenCV documentation中所述,这仅返回外部轮廓。
使用标志cv2.RETR_LIST
,您将在图像中得到所有轮廓。由于您尝试检测环,因此该列表将包含这些环的内部和外部轮廓。
要过滤圆的外边界,可以使用cv2.contourArea()
查找两个重叠轮廓中较大的一个。
答案 1 :(得分:2)
我建议不要使用轮廓线,而是使用适当的参数来应用霍夫圆变换。
寻找轮廓带来了挑战。反转二进制图像后,圆圈为白色。 OpenCV沿圆的外部和查找轮廓。此外,由于存在诸如“ A”和“ B”之类的字母,沿字母的外侧和孔内将再次发现轮廓。您可以使用适当的层次结构准则来找到轮廓,但这仍然很繁琐。
这是我尝试通过找到轮廓并使用层次结构进行的尝试:
代码:
#--- 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)
有关轮廓及其层次关系please refer this
的更多信息我有一种相当粗略的方法来忽略不需要的轮廓。在列表l
中找到所有轮廓的平均面积,并绘制出高于平均值的轮廓:
代码:
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)
答案 2 :(得分:2)
尽管如此,我不确定这是否真的是您期望的,有很多方法可以帮助findContours完成工作。 这是我经常使用的方法。
将图像转换为灰色
Ig = cv2.cvtColor(I,cv2.COLOR_BGR2GRAY)
背景和前景值在颜色方面看起来很均匀,但是局部上却不一样,因此我基于Otsu方法对阈值进行了二值化处理。
_,It = cv2.threshold(Ig,0,255,cv2.THRESH_OTSU)
为了只提取轮廓,我处理了Sobel边缘检测器的大小。
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)
我使用在ximgproc
中实现的细化功能。
细化的目的是将轮廓厚度减小到尽可能少的像素。
m = cv2.ximgproc.thinning(m,None,cv2.ximgproc.THINNING_GUOHALL)
最终步骤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))
希望有帮助。
我认为基于SVM或CNN的方法可能更健壮。 您可以找到示例here。 This one也可能很有趣。
-EDIT-
我找到了一种可以说更容易实现目标的方法。
与之前在应用阈值加载图像之后,请确保图像是二进制的。
通过使用按位非操作反转图像,轮廓在黑色背景上变为白色。
应用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)
P.S。在函数findContours
返回的输出中,有一个层次矩阵。
通过分析该矩阵可以达到相同的结果,但是要比解释here复杂一些。
答案 3 :(得分:0)
另一种方法是在找到合适的参数后使用Hough Circle变换来找到圆。
代码:
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)