【问题标题】:How to find the centre of these sometimes-overlapping circles如何找到这些有时重叠的圆圈的中心
【发布时间】:2017-08-14 23:44:09
【问题描述】:

作为我正在进行的项目的一部分,我需要使用 OpenCV 和 Python 在图像中找到一些“斑点”的中心点。 我遇到了一些麻烦,非常感谢任何帮助或见解:)

我目前的方法是:获取图像的轮廓,在其上覆盖椭圆,使用斑点检测器找到每个图像的中心。 这工作得相当好,但有时我需要忽略多余的斑点,有时这些斑点会相互接触。

以下是运行良好的示例: 良好的源图片: 提取轮廓后: 检测到 blob:

当它运行不佳时(您可以看到它错误地将一个椭圆覆盖在三个斑点上,并检测到一个我不想要的): 错误的源图片: 提取轮廓后: 检测到 blob:

这是我目前使用的代码。我不确定还有其他选择。

def process_and_detect(img_path):
    img = cv2.imread(path)
    imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    ret, thresh = cv2.threshold(imgray, 50, 150, 0)
    im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    drawn_img = np.zeros(img.shape, np.uint8)
    min_area = 50
    min_ellipses = []
    for cnt in contours:
        if cv2.contourArea(cnt) >= min_area:
            ellipse = cv2.fitEllipse(cnt)
            cv2.ellipse(drawn_img,ellipse,(0,255,0),-1)
    plot_img(drawn_img, size=12)

    # Change thresholds
    params = cv2.SimpleBlobDetector_Params()
    params.filterByColor = True
    params.blobColor = 255
    params.filterByCircularity = True
    params.minCircularity = 0.75
    params.filterByArea = True
    params.minArea = 150
    # Set up the detector
    detector = cv2.SimpleBlobDetector_create(params)

    # Detect blobs.
    keypoints = detector.detect(drawn_img)
    for k in keypoints:
        x = round(k.pt[0])
        y = round(k.pt[1])
        line_length = 20
        cv2.line(img, (x-line_length, y), (x+line_length, y), (255, 0, 0), 2)
        cv2.line(img, (x, y-line_length), (x, y+line_length), (255, 0, 0), 2)
    plot_img(img, size=12)

非常感谢您阅读本文,我真诚地希望有人可以帮助我,或者指出我正确的方向。谢谢!

【问题讨论】:

  • 你事先知道会有多少个blob吗?所有 blob 的大小是否应该相似,或者您可能有一个与这 3 个组合 blob 大小相同的 blob?这条管道非常多余,因为SimpleBlobDetector 有自己的一组阈值和轮廓检测操作。在申请 SimpleBlobDetector 之前,您已经完成了大部分工作。
  • 我正在使用的 blob 是神经网络的输出,因此我知道训练期间的 blob 数量,但不知道在测试时 - 这是关键时刻。组合大小为 3 的 blob 不太可能出现,但它们的大小确实会有些波动。

标签: python opencv image-processing


【解决方案1】:

斑点检测器

目前,您的实现是多余的。来自SimpleBlobDetector() docs

该类实现了一个从图像中提取斑点的简单算法:

  1. 通过使用从 minThreshold(包括)到 maxThreshold(不包括)的多个阈值以及相邻阈值之间的距离 thresholdStep 应用阈值处理,将源图像转换为二值图像。
  2. 通过 findContours() 从每个二值图像中提取连通分量并计算它们的中心。
  3. 按坐标对多个二值图像的中心进行分组。闭合中心形成一组,对应一个 blob,由 minDistBetweenBlobs 参数控制。
  4. 从这些组中,估计 blob 的最终中心及其半径,并作为关键点的位置和大小返回。

因此,您已经在实施部分步骤,这可能会产生一些意外行为。您可以尝试使用这些参数,看看是否能找出一些适合您的方法(尝试创建trackbars 来使用这些参数并使用不同的斑点检测器参数获取算法的实时结果)。

修改您的管道

但是,您已经编写了大部分自己的管道,因此您可以轻松移除 blob 检测器并实现自己的算法。如果你简单地把你的阈值降低一点,你可以很容易地得到清晰标记的圆圈,然后斑点检测就像轮廓检测一样简单。如果每个斑点都有单独的轮廓,则可以使用moments() 计算轮廓的质心。例如:

def process_and_detect(img_path):

    img = cv2.imread(img_path)
    imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    ret, thresh = cv2.threshold(imgray, 100, 255, cv2.THRESH_BINARY)

    contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[1]
    line_length = 20
    for c in contours:
        if cv2.contourArea(c) >= min_area:
            M = cv2.moments(c)
            x = int(M['m10']/M['m00'])
            y = int(M['m01']/M['m00']) 
            cv2.line(img, (x-line_length, y), (x+line_length, y), (255, 0, 0), 2)
            cv2.line(img, (x, y-line_length), (x, y+line_length), (255, 0, 0), 2)

更多参与

同样的管道可用于自动循环遍历阈值,因此您不必猜测和硬编码这些值。由于所有斑点看起来都大致相同,因此您可以循环遍历,直到所有轮廓具有大致相同的面积。你可以这样做,例如通过找到中值轮廓大小,定义该中值大小的某个百分比高于和低于您允许的范围,并检查检测到的所有轮廓是否都符合这些边界。

这是我的意思的动画gif。请注意,一旦轮廓分离,gif 就会停止:

然后您可以简单地找到这些分离轮廓的质心。代码如下:

def process_and_detect(img_path):

    img = cv2.imread(img_path)
    imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    for thresh_val in range(0, 255):

        # threshold and detect contours
        thresh = cv2.threshold(imgray, thresh_val, 255, cv2.THRESH_BINARY)[1]
        contours = cv2.findContours(thresh,
                                    cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[1]

        # filter contours by area
        min_area = 50
        filtered_contours = [c for c in contours
                             if cv2.contourArea(c) >= min_area]
        area_contours = [cv2.contourArea(c) for c in filtered_contours]

        # acceptable deviation from median contour area
        median_area = np.median(area_contours)
        dev = 0.3
        lowerb = median_area - dev*median_area
        upperb = median_area + dev*median_area

        # break when all contours are within deviation from median area
        if ((area_contours > lowerb) & (area_contours < upperb)).all():
            break

    # draw center location of blobs
    line_length = 8
    cross_color = (255, 0, 0)
    for c in filtered_contours:
        M = cv2.moments(c)
        x = int(M['m10']/M['m00'])
        y = int(M['m01']/M['m00'])
        cv2.line(img, (x-line_length, y), (x+line_length, y), cross_color, 2)
        cv2.line(img, (x, y-line_length), (x, y+line_length), cross_color, 2)

请注意,这里我用range(0, 255) 循环遍历所有可能的阈值以给出0, 1, ..., 254,但实际上您可以从更高的起点开始并一次跳过几个值,例如range(50, 200, 5) 以得到50, 55, ..., 195当然会快得多。

【讨论】:

  • 使用多个阈值来分离blob确实是一个好方法。对中心进行分组并不是唯一的选择。您可以考虑在分离良好时获得的中心,以便斑点的内部更加对称。我还猜想半径的精确测量是遥不可及的,因为接触的斑点实际上不再是圆形的。 (也许在强度图上有一个稳健的球面拟合。)
  • @YvesDaoust 我也同意您的分水岭方法,如果 OP 还没有几乎可以工作的管道,我会建议这样做。看起来在 OP 的图片中,blob 的大小都相似,因此只要大于min_area 的所有轮廓都接近中等大小的轮廓,您就可以轻松地循环通过多个阈值和break
  • @YvesDaoust 很好,你启发了我用同样的方法遍历阈值。看起来效果不错!
  • 呵呵,循环遍历所有阈值是……分水岭!
  • @YvesDaoust 确实!我只是认为使用 OP 提供的代码手动处理它会很有趣。
【解决方案2】:

这种斑点分裂问题的“标准”方法是通过分水岭变换。它可以应用于二值图像,使用变换距离,或直接应用于灰度图像。

过度细分问题可能会使其变得棘手,但您的情况似乎不会因此受到影响。

为了找到中心,我通常会推荐像素坐标的加权平均值以获得降噪效果,但在这种情况下,我可能会选择最大强度的位置,它不会受到形状变形。

这是您使用灰度分水岭得到的结果(区域强度是平均值)。与我最初的想法相反,由于 blob 中的不规则性,存在一些碎片

您可以在分割前通过一些低通滤波来改进。

【讨论】:

    猜你喜欢
    • 2016-07-23
    • 2012-10-05
    • 1970-01-01
    • 2021-03-21
    • 2021-03-08
    • 1970-01-01
    • 2013-10-29
    • 2022-01-09
    • 2016-07-16
    相关资源
    最近更新 更多