查找彼此靠近的对象边界

Finding object boundaries which are close to each other

我正在研究一个计算机视觉问题,其中一个步骤是找到物体彼此靠近的位置。例如,在下图中,我很感兴趣地找到标记为灰色的区域。

输入:

输出:

我目前的做法是先反转图像,然后通过腐蚀进行形态学梯度跟随,然后去除一些不感兴趣的轮廓。脚本如下:

img = cv2.imread('mask.jpg', 0)
img = (255 - img)

kernel = np.ones((11,11), np.uint8) 
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

kernel = np.ones((5,5), np.uint8) 
img_erosion = cv2.erode(gradient, kernel, iterations=3) 

img_erosion[img_erosion > 200] = 255
img_erosion[img_erosion <= 200] = 0

def get_contours(mask):
    contours, hierarchy = cv2.findContours(mask,cv2.RETR_TREE,cv2.cv2.CHAIN_APPROX_NONE)
    return contours

cnts = get_contours(img_erosion)

img_new = np.zeros_like(img_erosion)
img_h, img_w = img_erosion.shape
for i in cnts:
    if cv2.contourArea(i) > 30:
        print(cv2.boundingRect(i), cv2.contourArea(i))
        x, y, h, w = cv2.boundingRect(i)
        if h/w > 5 or w/h > 5 or cv2.contourArea(i) > 100:  ## Should be elongated 
            if (x - 10 > 0) and (y - 10 > 0): ## Check if near top or left edge
                if (img_w - x > 10) and (img_h - y > 10): ## Check if near bottom or right edge

                    cv2.drawContours(img_new, [i], -1, (255,255,255), 2)
kernel = np.ones((3,3), np.uint8) 
img_new = cv2.dilate(img_new, kernel, iterations=2)
plt.figure(figsize=(6,6))
plt.imshow(img_new)

结果是:

但是,使用这种方法,我需要调整很多参数,并且在许多情况下,当方向不同或边缘稍微远一点,或者 "L" 形状的边缘等时,它会失败

我是图像处理的新手,有没有其他方法可以帮助我高效地解决这个问题?

编辑:附加更多图片

(主要是矩形多边形,但大小和相对位置变化很大)

最好的 方法可能是通过 Stroke Width Transform。这不在 OpenCV 中,尽管它在其他几个库中,您可以在互联网上找到一些实现。笔划宽度变换为图像中的每个像素找到最近边缘之间的最小宽度。见论文中的下图:

然后对该图像进行阈值处理会告诉您边缘之间的距离很小。例如,所有值 < 40 的像素都位于相距小于 40 像素的两条边之间。

所以,很可能很清楚,这非常接近您想要的答案。这里会有一些额外的噪音,就像你还会得到形状边缘的方形脊之间的值......你必须过滤掉或平滑掉(轮廓近似是一种简单的方法来例如,将它们清理为预处理步骤)。

然而,虽然我确实编写了一个 SWT 原型,但它不是一个很好的实现,而且我还没有真正测试过它(实际上几个月后就忘记了它......也许是年)所以我现在不打算把它放出来。但是,我确实有另一个更简单的想法,不需要阅读研究论文。


您的输入图像中有多个斑点。想象一下,如果您将每一个单独放在自己的图像中,并且您愿意将每个斑点之间的距离增加多少。如果您将每个斑点增加 10 个像素,并且它们重叠,那么它们之间的距离将在 20 个像素以内。然而,这并没有给我们完整的重叠区域,只是两个 expanded 斑点重叠的一部分。一种不同但相似的测量方法是,如果斑点增长 10 个像素,并且重叠,并且在扩展之前与原始斑点重叠,那么这两个斑点彼此之间的距离在 10 个像素以内。我们将使用第二个定义来查找附近的斑点。

def find_connection_paths(binimg, distance):

    h, w = binimg.shape[:2]
    overlap = np.zeros((h, w), dtype=np.int32)
    overlap_mask = np.zeros((h, w), dtype=np.uint8)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (distance, distance))

    # grows the blobs by `distance` and sums to get overlaps
    nlabels, labeled = cv2.connectedComponents(binimg, connectivity=8)
    for label in range(1, nlabels):
        mask = 255 * np.uint8(labeled == label)
        overlap += cv2.dilate(mask, kernel, iterations=1) // 255
    overlap = np.uint8(overlap > 1)

    # for each overlap, does the overlap touch the original blob?
    noverlaps, overlap_components = cv2.connectedComponents(overlap, connectivity=8)
    for label in range(1, noverlaps):
        mask = 255 * np.uint8(overlap_components == label)
        if np.any(cv2.bitwise_and(binimg, mask)):
            overlap_mask = cv2.bitwise_or(overlap_mask, mask)
    return overlap_mask

现在输出并不完美---当我扩展斑点时,我用一个圆圈(膨胀核)向外扩展它们,所以连接区域不是非常清晰。然而,这是确保它适用于任何方向的事物的最佳方式。您可以将此 out/clip 过滤掉。一个简单的方法是获取每个连接件(以蓝色显示),并重复将其向下腐蚀一个像素,直到它 与原始斑点重叠。其实好的,让我们补充一下:

def find_connection_paths(binimg, distance):

    h, w = binimg.shape[:2]
    overlap = np.zeros((h, w), dtype=np.int32)
    overlap_mask = np.zeros((h, w), dtype=np.uint8)
    overlap_min_mask = np.zeros((h, w), dtype=np.uint8)
    kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (distance, distance))

    # grows the blobs by `distance` and sums to get overlaps
    nlabels, labeled = cv2.connectedComponents(binimg)
    for label in range(1, nlabels):
        mask = 255 * np.uint8(labeled == label)
        overlap += cv2.dilate(mask, kernel_dilate, iterations=1) // 255
    overlap = np.uint8(overlap > 1)

    # for each overlap, does the overlap touch the original blob?
    noverlaps, overlap_components = cv2.connectedComponents(overlap)
    for label in range(1, noverlaps):
        mask = 255 * np.uint8(overlap_components == label)
        if np.any(cv2.bitwise_and(binimg, mask)):
            overlap_mask = cv2.bitwise_or(overlap_mask, mask)

    # for each overlap, shrink until it doesn't touch the original blob
    kernel_erode = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    noverlaps, overlap_components = cv2.connectedComponents(overlap_mask)
    for label in range(1, noverlaps):
        mask = 255 * np.uint8(overlap_components == label)
        while np.any(cv2.bitwise_and(binimg, mask)):
            mask = cv2.erode(mask, kernel_erode, iterations=1)
        overlap_min_mask = cv2.bitwise_or(overlap_min_mask, mask)

    return overlap_min_mask

当然,如果您仍然希望它们更大或更小,您可以随心所欲地处理它们,但这看起来非常接近您要求的输出,所以我将保留在那里。另外,如果你想知道,我不知道右上角的斑点去了哪里。稍后我可以在最后一块上再拍一遍。请注意,最后两个步骤可以合并;检查是否有重叠,如果有,很酷---将其缩小并存储在遮罩中。