使用OpenCV对距离图像进行分水岭变换

3 投票
1 回答
4959 浏览
提问于 2025-04-18 14:08

在Matlab中,我们可以对距离变换进行分水岭变换,以分开两个相互接触的物体:

这里输入图片描述

这里输入图片描述

上面的第一张图片是我们想要分开的接触物体的图像。第二张图片是它的距离变换结果。

所以,如果我们把黑白图像叫做 img,在Matlab中我们可以这样做:

D = -bwdist(~img); 
L = watershed(D);

现在我们来看看如何用OpenCV做同样的事情:OpenCV有一个基于标记的分水岭分割功能。要用OpenCV完成分开两个接触物体的任务,我们需要为两个物体和背景提供标记。

img = np.zeros((400, 400), np.uint8)
cv2.circle(img, (150, 150), 100, 255, -1)
cv2.circle(img, (250, 250), 100, 255, -1)

dist = cv2.distanceTransform(img, cv2.cv.CV_DIST_L2, cv2.cv.CV_DIST_MASK_PRECISE)
dist3 = np.zeros((dist.shape[0], dist.shape[1], 3), dtype = np.uint8)
dist3[:, :, 0] = dist
dist3[:, :, 1] = dist
dist3[:, :, 2] = dist

markers = np.zeros(img.shape, np.int32)
markers[150,150] = 1 # seed for circle one
markers[250, 250] = 2 # seed for circle two
markers[50,50] =  3 # seeds for background

cv2.watershed(dist3, markers)

在下面的图片中,你可以看到经过分水岭处理后的 markers 图像。原始的黑白 img 被红色叠加在上面。问题是,结果中的 markers 图像的物体边界和原始图像并不相同。我该如何确保物体边界保持一致呢?

这里输入图片描述

1 个回答

3

你最好了解一下“分水岭”这个函数到底是怎么工作的。它会开始用种子进行“洪水填充”,并把它们的坐标和邻居的梯度放进一个优先队列里。

你知道,当你对图像应用距离变换时,圆的梯度会变成0或1,但背景的梯度总是0。

现在你有三个种子,洪水填充开始工作:背景(种子3)、邻居(种子1)、邻居(种子2),它们会轮流工作,直到种子1或种子2的梯度达到1;然后只有种子3可以继续工作。

当种子3碰到圆的边界时,它的梯度变成1,这样它们又可以轮流工作了。

所以如果你想确保物体的边界保持不变,最好在种子3碰到圆的边界时增加梯度。

就像这样:

dist = cv2.distanceTransform(img, cv2.cv.CV_DIST_L2, cv2.cv.CV_DIST_MASK_PRECISE)
dist[dist > 0] += 2.0

这里是一个结果

...这里面有一些问题(当队列中的所有梯度都是1时,哪个会先弹出,哪个会第二个弹出)

撰写回答