理论
任何灰度图像都可以看作是地形表面,其中高强度表示山峰和丘陵,而低强度表示山谷.用不同颜色的水(标签)填充每个孤立的山谷(局部最小值),随着水的上升,明显具有不同的颜色的水将开始融合.为避免这种情况,需要在水合并的位置建立障碍,在所有的山峰都被水淹没之前,要继续填满水和建造栅栏的工作然后你创建的障碍会给你分割的结果,这就是分水岭背后的“哲学”.
可以访问了解更多相关内容.
这种方法会导致由于噪声或图像中任何其他不正常的情况而导致的结果过于分散, 因此,OpenCV实现了一个基于标记的分水岭算法,可以在其中指定要合并的和不合并的谷点.这是一个交互式的图像分割,我们所做的就是给我们所知道的对象提供不同的标签,用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后标记我们不确定的区域为0,然后应用分水岭算法,我们的标记将会随着我们所给出的标签进行更新,对象的边界将值为-1.
应用
我们将看到一个关于如何使用距离变换和分水岭来分割相互触摸的物体的例子.
考虑下面的硬币图像,硬币相互接触.即使把它阈值,它也会互相接触.
我们使用Otsu的二值化找到硬币的近似估计值.
import cv2import numpy as npfrom matplotlib import pyplot as pltimg = cv2.imread('img4.jpg')gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)cv2.imshow('show',thresh)cv2.waitKey(0)cv2.destroyAllWindows()
现在我们需要去除图像中的任何小的白噪声,因此我们要使用形态学开运算,为了去除物体上的小洞,我们要使用形态学闭运算,所以,现在我们可以确定,靠近物体中心的区域是前景,远离物体的区域是背景,只有硬币的边界区域是我们不确定的区域.
我们需要提取出我们确信它们是硬币的区域,腐蚀边界像素,不管剩下的是什么,我们都可以确定它是硬币.如果它们不相互接触还可以继续,如果它们相互接触,另一个好的选择是找到距离变换并应用一个合适的阈值.
为此,我们对结果进行了扩张,扩张将对象边界增加为背景,通过这种方法,我们可以确保背景中的任何区域都是真正的背景,因为边界区域被移除.
剩下的区域是我们不知道的区域,无论是硬币还是背景.分水岭算法应该找到它, 这些区域通常围绕着前景和背景相遇的硬币边界(甚至两个不同的硬币相遇),它可以从sure_bg区域中减去sure_fg区域获得.
# noise removalkernel = np.ones((3,3),np.uint8)opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)# sure background areasure_bg = cv2.dilate(opening,kernel,iterations=3)# Finding sure foreground areadist_transform = cv2.distanceTransform(opening,cv.DIST_L2,5)ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(),255,0)# Finding unknown regionsure_fg = np.uint8(sure_fg)unknown = cv2.subtract(sure_bg,sure_fg)
现在我们可以确定哪些是硬币的区域,哪些是背景,哪些是背景.因此,我们创建标记(它是一个与原始图像相同大小的数组,但使用int32数据类型)并对其内部的区域进行标记.
cv2.connectedComponents()
我们知道,如果背景是0,那么分水岭将会被认为是未知的区域, 所以我们用不同的整数来标记它,用0表示由未知定义的未知区域.
# Marker labellingret, markers = cv2.connectedComponents(sure_fg)# Add one to all labels so that sure background is not 0, but 1markers = markers+1# Now, mark the region of unknown with zeromarkers[unknown==255] = 0
深蓝色区域显示未知区域,硬币的颜色是不同的,与未知区域相比,确定背景的剩余区域以较浅的蓝色显示.
标记已经准备好了,现在是最后一步的时候了,应用分水岭.
markers = cv2.watershed(img,markers)img[markers == -1] = [255,0,0]
代码:
import numpy as npimport cv2from matplotlib import pyplot as pltimg = cv2.imread('img4.jpg')gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)# noise removalkernel = np.ones((3,3),np.uint8)opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2) # 形态开运算# sure background areasure_bg = cv2.dilate(opening,kernel,iterations=3)# Finding sure foreground areadist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)# Finding unknown regionsure_fg = np.uint8(sure_fg)unknown = cv2.subtract(sure_bg,sure_fg)# Marker labellingret, markers = cv2.connectedComponents(sure_fg)# Add one to all labels so that sure background is not 0, but 1markers = markers+1# Now, mark the region of unknown with zeromarkers[unknown==255] = 0markers = cv2.watershed(img,markers)img[markers == -1] = [255,0,0]cv2.imshow('img',img)cv2.waitKey(0)cv2.destroyAllWindows()