【问题标题】:How to detect intersecting shapes with OpenCV findContours?如何使用 OpenCV findContours 检测相交形状?
【发布时间】:2021-03-31 03:56:36
【问题描述】:

我在黑白图像中有两个相交的椭圆。我正在尝试使用 OpenCV findContours 使用此代码(以及下面的附图)将单独的形状识别为单独的轮廓。

import numpy as np
import matplotlib.pyplot as plt

import cv2
import skimage.morphology

img_3d = cv2.imread("C:/temp/test_annotation_overlap.png")
img_grey = cv2.cvtColor(img_3d, cv2.COLOR_BGR2GRAY)
contours = cv2.findContours(img_grey, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]

fig, ax = plt.subplots(len(contours)+1,1, figsize=(5, 20))

thicker_img_grey = skimage.morphology.dilation(img_grey, skimage.morphology.disk(radius=3))
ax[0].set_title("ORIGINAL IMAGE")
ax[0].imshow(thicker_img_grey, cmap="Greys")

for i, contour in enumerate(contours):
    new_img = np.zeros_like(img_grey)
    cv2.drawContours(new_img, contour, -1,  (255,255,255), 10)
    ax[i+1].set_title(f"Contour {i}")
    ax[i+1].imshow(new_img, cmap="Greys")

plt.show()

但是找到了四个轮廓,都不是原来的轮廓:

如何配置 OpenCV.findContours 以识别两个单独的形状? (注意我已经玩过霍夫圈,发现它对于我正在分析的图像不可靠)

【问题讨论】:

  • 轮廓由从图像中提取的边缘组成,这意味着边缘以某种方式连接。由于您得到了所有白色圆圈,我不确定是否可以检测到圆圈。也许如果您可以使用不同的颜色,您可以更轻松地在每个通道上创建蒙版,只需使用按位和运算来计算交叉 ROI。
  • 另一方面,我想出了一个想法,您可以在找到的轮廓上使用角点检测。这可以提供一些信息,哪些轮廓可能是其他轮廓的一部分。
  • 轮廓层次结构在这里可能很有用。等高线 1 和 2 都是等高线 3 的子代。
  • 霍夫圆变换怎么样?你试过吗?

标签: python opencv contour shape-recognition


【解决方案1】:

作为参考,我将根据此处的一些想法和其他一些想法发布我提出的解决方案。该解决方案的有效率为 99.9%,并且可以从通常包括许多重叠、包含以及其他图像噪声(如线条、文本等)的图像中恢复椭圆。

代码太长,发到这里,但伪代码如下。

  1. 分割图像
    • 使用 RETR_EXTERNAL 运行 cv2 findContours 以获取图像中的单独区域
    • 对于每张图像,填充内部,应用蒙版,并独立于其他区域提取要处理的区域。
    • 为每个区域独立执行其余步骤
  2. 使用 RETR_LIST 运行 cv2 findContours 以获取所有内部和外部轮廓
  3. 对于找到的每个轮廓,应用多边形平滑以减少像素化的影响
  4. 对于每个平滑轮廓,确定该轮廓内具有相同曲率符号的连续段,即完全向右弯曲或向左弯曲的段(仅计算角度和符号变化)
  5. 在每个段内,使用最小二乘拟合椭圆模型 (scikit-learn EllipseModel)
  6. 对原始图像执行 Lee 算法,计算每个像素到白色像素的最小距离
  7. 对于每个模型,执行贪婪的局部邻域搜索以改进与原始模型的拟合 - 拟合是拟合椭圆到白色像素的最大距离,来自 lee 算法的输出

不简单也不优雅,但对我处理的内容高度准确,这是通过人工审查大量图像确认​​的。

【讨论】:

    【解决方案2】:

    也许我用这种方法矫枉过正,但它可以用作一种工作方法。您可以找到图像上的所有轮廓 - 您将获得两个类似“半圆”的轮廓、交叉点的轮廓和两个相接圆的外形轮廓。最小的三个轮廓应该是两个半圆和交点。如果您从这三个轮廓中绘制两个组合,您将得到三个掩码,其中两个将具有一个半圆和交点的组合。如果你在面具上执行关闭,你会得到你的圈子。然后你应该简单地制作一个算法来检测哪两个面具代表一个完整的圆圈,你就会得到你的结果。这是示例解决方案:

    import numpy as np
    import cv2
    
    
    # Function for returning solidity of contour - ratio of contour area to its 
    # convex hull area.
    def checkSolidity(cnt):
        area = cv2.contourArea(cnt)
        hull = cv2.convexHull(cnt)
        hull_area = cv2.contourArea(hull)
        solidity = float(area)/hull_area
        return solidity
    
    
    img_orig = cv2.imread("circles.png")
    # Had to dilate the image so the contour was completly connected.
    img = cv2.dilate(img_orig, np.ones((3, 3), np.uint8))
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # Grayscale transformation.
    # Otsu threshold.
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
    # Search for contours.
    contours = cv2.findContours(thresh, cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
    
    # Sorting contours from smallest to biggest.
    contours.sort(key=lambda cnt: cv2.contourArea(cnt))
    
    # Three contours - two semi circles and the intersection of the circles.
    cnt1 = contours[0]
    cnt2 = contours[1]
    cnt3 = contours[2]
    
    # Create three empty images
    h, w = img.shape[:2]
    mask1 = np.zeros((h, w), np.uint8)
    mask2 = np.zeros((h, w), np.uint8)
    mask3 = np.zeros((h, w), np.uint8)
    
    # Draw all combinations of two out of three contours on the masks.
    # The goal here is to draw one semicircle and the intersection together.
    
    cv2.drawContours(mask1, [cnt1], 0, (255, 255, 255), -1)
    cv2.drawContours(mask1, [cnt3], 0, (255, 255, 255), -1)
    
    cv2.drawContours(mask2, [cnt2], 0, (255, 255, 255), -1)
    cv2.drawContours(mask2, [cnt3], 0, (255, 255, 255), -1)
    
    cv2.drawContours(mask3, [cnt1], 0, (255, 255, 255), -1)
    cv2.drawContours(mask3, [cnt2], 0, (255, 255, 255), -1)
    
    
    # Perform closing operation on the masks so that you get uniform contours.
    kernel_size = 25
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    mask1 = cv2.morphologyEx(mask1, cv2.MORPH_CLOSE, kernel)
    mask2 = cv2.morphologyEx(mask2, cv2.MORPH_CLOSE, kernel)
    mask3 = cv2.morphologyEx(mask3, cv2.MORPH_CLOSE, kernel)
    
    masks = []  # List for storing all the masks.
    masks.append(mask1)
    masks.append(mask2)
    masks.append(mask3)
    
    # List where you will append solidity of the found biggest contour of every mask.
    solidity = []
    for mask in masks:
        cnts = cv2.findContours(mask, cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
        cnt = max(cnts, key=lambda c: cv2.contourArea(c))
        s = checkSolidity(cnt)
        solidity.append(s)
    
    
    # Index of the mask with smallest solidity.
    min_solidity = solidity.index(min(solidity))
    # The mask with the contour that has smallest solidity should be the one that
    # has two semicirles drawn instead of one semicircle and the intersection. 
    #You could build a better function to check which mask is the one with   
    # two semicircles... like maybe the contour with the largest 
    # height and width of the bounding box etc.
    # I chose solidity because it is enough for this example.
    
    # Selection of colors.
    colors = {
        0: (0, 0, 255),
        1: (0, 255, 0),
        2: (255, 0, 0),
    }
    
    # Draw contours of the mask other two masks - those two that have the        
    # semicircle and the intersection.
    for i, s in enumerate(solidity):
        if s != solidity[min_solidity]:
            cnts = cv2.findContours(
                masks[i], cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
            cnt = max(cnts, key=lambda c: cv2.contourArea(c))
            cv2.drawContours(img_orig, [cnt], 0, colors[i], 1)
    
    # Display result
    cv2.imshow("img", img_orig)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    结果:

    【讨论】:

      【解决方案3】:

      从哲学上讲,您希望找到 两个 ,因为您搜索它们时,您期望的是中心和半径。从图形上看,这些数字是相连的,我们可以看到它们是分开的,因为我们知道什么是“圆”并推断出与重叠部分匹配的坐标。

      那么如何为每个轮廓找到最小封闭圆(或者在某些情况下 fitEllipse 并使用它们的参数):https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html

      然后说在清晰的图像中绘制该圆并获取不为零的像素坐标 - 通过蒙版或通过逐步绘制圆来计算它们。

      然后以一定精度将这些坐标与其他轮廓中的坐标进行比较,并将匹配的坐标附加到当前轮廓。

      最后:在清晰的画布上绘制扩展轮廓,并将 HoughCircles 应用于单个非重叠圆。 (或计算圆心和半径、圆的坐标并与轮廓进行精确比较。)

      【讨论】:

        猜你喜欢
        • 2018-03-19
        • 1970-01-01
        • 2018-03-20
        • 2013-06-06
        • 1970-01-01
        • 1970-01-01
        • 2016-06-01
        • 1970-01-01
        相关资源
        最近更新 更多