如何使用Python OpenCV优化圆形检测?

8

图片描述在此输入

我已经查看了多个关于使用Python中的OpenCV优化圆形检测的页面,但所有这些页面都是针对给定图片的特定环境而编写的。有哪些关于cv2.HoughCircles参数的起点参考值?由于我不确定推荐值是什么,我尝试循环范围,但这并没有产生任何有前途的结果。为什么我不能检测到此图像中的任何一个圆?

import cv2
import numpy as np
image = cv2.imread('IMG_stack.png')
output = image.copy()
height, width = image.shape[:2]
maxWidth = int(width/10)
minWidth = int(width/20)

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 20,param1=50,param2=50,minRadius=minWidth,maxRadius=maxWidth)


if circles is not None:
    # convert the (x, y) coordinates and radius of the circles to integers
    circlesRound = np.round(circles[0, :]).astype("int")
    # loop over the (x, y) coordinates and radius of the circles
    for (x, y, r) in circlesRound:
        cv2.circle(output, (x, y), r, (0, 255, 0), 4)
    cv2.imwrite(filename = 'test.circleDraw.png', img = output)
    cv2.imwrite(filename = 'test.circleDrawGray.png', img = gray)
else:
    print ('No circles found')

这应该是一个简单明了的圆形检测,但是检测到的所有圆形都远远不够接近。


你能分享一下你想检测圆圈的原始图像吗?并指出应该在哪里检测这些圆圈。 - Jop Knoppers
将所有暗/黑区域阈值化;使用findContours选择内部轮廓;使用椭圆拟合或minEnclosingCircle获取圆形轮廓。请参考此问题/答案以获取其他想法:https://dev59.com/V5Lea4cB1Zd3GeqP6cSA#34653138 - Micka
2个回答

5
你应该关注的主要参数是minDistminRadiusmaxRadius
首先分析半径:假设有一张图像,它有12个圆环和8个垂直圆环,这给出了每个圆环大约为width/12的直径,或者半径为(width/12)/2。你所使用的约束条件允许算法检测到比必要的大或小得多的圆,因此你应该使用更适合你的图像的参数化。在这种情况下,我使用了一个区间[0.9 * radius, 1.1 * radius]
由于没有重叠,因此可以说两个圆之间的距离至少是直径,因此minDist可以设置为类似2*minRadius的东西。
这个实现基本上与你的相同,只是更新了那3个参数。
%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread('data/balls.jpg')
output = image.copy()
height, width = image.shape[:2]
maxRadius = int(1.1*(width/12)/2)
minRadius = int(0.9*(width/12)/2)

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(image=gray, 
                           method=cv2.HOUGH_GRADIENT, 
                           dp=1.2, 
                           minDist=2*minRadius,
                           param1=50,
                           param2=50,
                           minRadius=minRadius,
                           maxRadius=maxRadius                           
                          )

if circles is not None:
    # convert the (x, y) coordinates and radius of the circles to integers
    circlesRound = np.round(circles[0, :]).astype("int")
    # loop over the (x, y) coordinates and radius of the circles
    for (x, y, r) in circlesRound:
        cv2.circle(output, (x, y), r, (0, 255, 0), 4)

    plt.imshow(output)
else:
    print ('No circles found')

结果如下:

结果是:

输入图像描述


1
谢谢你,Ricardo!我在各种文件大小上尝试了一下代码,通常会导致不同的结果。但是你的代码基本上可以产生相同的效果。在最终求助于stackoverflow之前,我也以各种方式调整了半径,但结果很奇怪,我猜你的逻辑更有意义。最终,我使用了'minRadius'为'int(0.8*(width/12)/2)',因为在较大的图像中,较大的'minRadius'会导致更多的重叠,并且某些圆可能无法被检测到。这似乎有所帮助。干杯! - Rob

5

通常,可以使用传统图像处理方法(例如阈值化+轮廓检测hough circles或轮廓拟合)来进行圆形检测,但由于您的圆形重叠/接触watershed分割可能更好。 这是一个不错的资源

enter image description here

import cv2
import numpy as np
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage

# Load in image, convert to gray scale, and Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Remove small noise by filtering using contour area
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

for c in cnts:
    if cv2.contourArea(c) < 1000:
        cv2.drawContours(thresh,[c], 0, (0,0,0), -1)

cv2.imshow('thresh', thresh)
# Compute Euclidean distance from every binary pixel
# to the nearest zero pixel then find peaks
distance_map = ndimage.distance_transform_edt(thresh)
local_max = peak_local_max(distance_map, indices=False, min_distance=20, labels=thresh)

# Perform connected component analysis then apply Watershed
markers = ndimage.label(local_max, structure=np.ones((3, 3)))[0]
labels = watershed(-distance_map, markers, mask=thresh)

# Iterate through unique labels
for label in np.unique(labels):
    if label == 0:
        continue

    # Create a mask
    mask = np.zeros(gray.shape, dtype="uint8")
    mask[labels == label] = 255

    # Find contours and determine contour area
    cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    c = max(cnts, key=cv2.contourArea)
    cv2.drawContours(image, [c], -1, (36,255,12), -1)

cv2.imshow('image', image)
cv2.waitKey()

不错的解决方案,nathancy! - Cris Luengo
啊,我本来也想发类似的东西的。 :) - Cris Luengo
1
实际上,我有些困惑。虽然非常详细和受人赞赏,但这段代码实际上只是突出显示图像中的黑色部分,而不仅仅是圆圈。例如,此代码还可以有效地检测白色页面上的黑色字母,甚至是图像中的矩形。重要的是,如果我反转这张图片的颜色,它会突出显示除圆圈以外的整个图像,显然不是预期的结果。 - Rob
1
这是一段非常有趣的代码,我从中学到了很多东西。然而,它并不一定能够检测圆形,只能检测黑线。就像我上面所说的,如果图像中有文本或任何其他形状,只要它是黑线,这段代码就无法区分它们和实际圆形之间的差别。我希望学习如何优化检测圆形的能力,以排除假阳性。我不关心圆周围的线条是否着色,只关心检测圆形。 - Rob
不要使用 from skimage.morphology import watershed,而是使用 from skimage.segmentation import watershed。 Watershed 已经被移动到 Segmentation 中。 - shreyans jain
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接