The best做到这一点的方法可能是通过笔画宽度变换 https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/1509.pdf。这不在 OpenCV 中,尽管它在其他一些库中,并且您可以在互联网上找到一些实现。笔划宽度变换查找图像中每个像素的最近边缘之间的最小宽度。请看论文中的下图:
对该图像进行阈值处理可以告诉您哪里有相隔一小段距离的边缘。例如,所有值
因此,正如可能清楚的那样,这非常接近您想要的答案。这里会有一些额外的噪音,就像你还会得到形状边缘的方形脊之间的值一样......你必须过滤掉或平滑掉这些值(轮廓近似是一种简单的方法)例如,将它们作为预处理步骤进行清理)。
然而,虽然我确实编写了一个 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
现在输出并不完美——当我扩展斑点时,我用一个圆圈(膨胀内核)将它们向外扩展,因此连接区域并不是非常清晰。然而,这是确保它适用于任何方向的事物的最佳方法。您可以将其过滤掉/剪辑下来。一个简单的方法是获取每个连接块(以蓝色显示),并重复将其侵蚀一个像素,直到它doesn't与原始斑点重叠。其实没问题,我们补充一下:
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
当然,如果您仍然希望它们更大或更小一点,您可以对它们做任何您喜欢的事情,但这看起来非常接近您要求的输出,所以我将其留在那里。另外,如果你想知道,我不知道右上角的斑点去了哪里。稍后我可以再看一遍这最后一篇文章。请注意,最后两个步骤可以合并;检查是否有重叠,如果有,酷——将其缩小并存放在掩模中。