OpenCV-Python -- Image Segmentation with Watershed Algorithm

学习目标

  • 学习分水岭算法(watershed algorithm).
  • 函数:cv2.watershed().

理论

任何灰度图像可以被看作是地形面(topographic surface),较大的灰度值表示峰和山,较小的灰度值区域表示山谷。我们首先使用不同的颜色(标签)填充孤立的山谷(局部最小值)。当填充水位上升至山峰附近时,来自不同山谷的水开始融合。为了避免融合,需要在合并的地方建立障碍。指导所有的山峰在水之下。那么这些建立的障碍就是分割结果。这就是分水岭的基本思想。

由于噪声或者不规则区域的影响,这种方法会出现过度分割。所以OpenCV中采用基于标志点的分水岭算法,标志点指明了是否是山谷点。这是一种交互的图像分割算法。我们需要做的是为目标指定不同的标记。标记方法如下:目标区域给定一种颜色A,背景或者非目标给定颜色B,不确定的区域给定零。这就是marker。然后应用分水岭算法。最终,标志点会被不断更新,边界区域值为-1。

代码

下面的例子将使用距离变换和分水岭算法来分割相互接触的目标。考虑下面的硬币,它们相互接触。尽管使用阈值,它们也会相互粘连。
在这里插入图片描述
我们首先进行近似估计硬币区域。使用Otsu’s binarization算法,代码如下:

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('water_coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
cv2.imshow('thresh', thresh)
cv2.waitKey(0)

运行结果如下:
在这里插入图片描述
现在我们需要移除小的白色噪声。形态学开操作,使用闭操作移除小的洞。这样操作后,接近物体中心的是前景目标,远离中心的是背景。唯一不确定的是硬币的边界。

所以我们需要提取硬币的区域。侵蚀边界像素,那么留下的像素更倾向于硬币区域。这种情况对于没有连接的硬币有很好的效果。但是图中的硬币显然大部分是相互连接的,另外可以使用距离变换,并设定一个阈值。接下来,我们需要找到确定不是硬币的区域。为此,我们对上述结果进行膨胀。那么前景与背景的边界减少,也就更容易确定背景区域,如下图所示:

在这里插入图片描述
经过上述的一系列操作,剩下的区域是比较难处理的,难以区分硬币和背景。但是,分水岭算法可以找到。这些区域通常位于硬币的边界附近,即前景和背景的交汇处,称之为border。它们存在于确定的背景区域和确定的前景区域之间。代码如下:

# noise removal
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# sure background area
sure_bg = cv2.dilate(opening, kernel, iterations=3)

# Finding sure foreground area
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)

# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
cv2.imshow('unknown', unknown)
cv2.waitKey(0)

从结果中,我们可以看出,在阈值图像中,我们得到硬币的区域,并且被分离。(在一些情况下,可能更感兴趣前景分割,并不需要分离黏连区域)。在这种情况下,不需要使用距离变换,仅仅是侵蚀就可以。

在这里插入图片描述

现在,我们知道了硬币的区域,以及背景区域。所以我们创建marker(是与原图同样大小的数组,数据类型为int32),并且进行标记。对于确定的背景或者前景,标记为不同的正值,不确定的区域标记为零。使用函数:cv2.connectedComponents(),该函数标记背景为零,其它目标为正整数。

但是,一旦背景标记为零,那么分水岭算法认为该区域是不确定区域。所以,我们标记为不同的整数。相反,不确定的区域标记为0。

# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)

# Add one to all labels so that sure background is not 0, but 1
markers = markers+1

# Now, mark the region of unknown with zero
markers[unknown==255] = 0

查看JET颜色图。深蓝色区域表示不确定区域。确定的区域标记为不同的颜色。剩下的是背景,标记为浅蓝色。

在这里插入图片描述
现在,marker制作完成,最后使用分水岭算法。然后,marker image被修改,边界区域标注为-1.

markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]

看最后的结果,分割效果不错:
在这里插入图片描述
分水岭算法:http://www.cmm.mines-paristech.fr/~beucher/wtshed.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值