任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水位的上升,根据附近的山峰(坡度),来自不同山谷的水明显会开始合并,颜色也不同。为了避免这种情况,你要在水融合的地方建造屏障。你继续填满水,建造障碍,直到所有的山峰都在水下。然后你创建的屏障将返回你的分割结果。
但是这种方法会由于图像中的噪声或其他不规则性而产生过度分割的结果。因此OpenCV实现了一个基于标记的分水岭算法,你可以指定哪些是要合并的山谷点,哪些不是。这是一个交互式的图像分割。我们所做的是给我们知道的对象赋予不同的标签。用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后用0标记我们不确定的区域。这是我们的标记。然后应用分水岭算法。然后我们的标记将使用我们给出的标签进行更新,对象的边界值将为-1。
使用分水岭算法实现基于标记的图像分割
watershed(image, markers) -> markers
def watershed_image(image):
"""分水岭算法"""
# 图像二值化
blurred = cv.pyrMeanShiftFiltering(image, 10, 50) # 均值迁移滤波
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY) # 转换成灰度图
# cv.imshow("gray", gray)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU) # 图像二值化
# cv.imshow("binary", binary)
# 去除噪声
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3)) # 构造25×25的方形结构元素
opening = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel=kernel, iterations=2) # 开操作(需要去除图像中的任何白点噪声),迭代次数2
# cv.imshow("noise removal", opening)
# 确定背景区域sure_bg
sure_bg = cv.dilate(opening, kernel, iterations=3) # 腐蚀,迭代次数3,会去除边界像素
cv.imshow("sure_bg", sure_bg)
# 寻找前景区域sure_fg
""" 距离变换的基本含义是计算一个图像中非零像素点到最近的零像素点的距离,也就是到零像素点的最短距离
一个最常见的距离变换算法就是通过连续的腐蚀操作来实现,腐蚀操作的停止条件是所有前景像素都被完全腐蚀。
这样根据腐蚀的先后顺序,我们就得到各个前景像素点到前景中心像素点的距离。根据各个像素点的距离值,设置
为不同的灰度值。这样就完成了二值图像的距离变换。
cv2.distanceTransform(src, distanceType, maskSize)
distanceType为距离类型CV_DIST_L1, CV_DIST_L2 , CV_DIST_C;maskSize为距离转换掩码的大小
"""
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5) # 距离变换
dist_output = cv.normalize(dist_transform, 0, 1.0, cv.NORM_MINMAX) # 矩阵归一化,主要是为了显示出dist_output
cv.imshow("dist_transform", dist_output*50) # dist_output不乘50看不出来
ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0) # 图像二值化
cv.imshow("sure_fg", sure_fg)
# 找到未知的区域unknown
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg) # 从sure_bg区域中减去sure_fg区域来获得unknown
cv.imshow("unknown", unknown)
# 类别标记
ret, markers1 = cv.connectedComponents(sure_fg)
print(ret) # 计算数量,但此时会把图像边框也算进去,因此ret会多1
# print(markers1)
# 为所有的标记加1,保证背景是0而不是1
markers = markers1 + 1
# print(markers)
# 现在让所有的未知区域为0
markers[unknown == 255] = 0
# 使用分水岭算法
markers3 = cv.watershed(image, markers=markers) # 边界区域将被修改标记为-1
image[markers3 == -1] = [0, 0, 255] # 边界区域画红色
# print(markers3)
cv.imshow("result", image)
结果:
参考链接: