分水岭算法基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。
1、通过形态学开运算对原始图像O去噪
2、通过腐蚀操作获取“确定背景B”
3、利用距离变换函数distanceTransform对原始图像O进行运算,并对其进行阈值处理
4、计算未知区域UN(UN=O-B-F)
5、利用函数connectedComponents对原始图像O进行标注
6、利用函数connectedComponents对原始图像O进行修正
7、使用分水岭函数完成对图像的分割
# 分水岭算法流程:输入图像、灰度、二值、距离变换、寻找种子、生成Marker、分水岭变换、输出图像
def watershed_demo():
print(src.shape)
# 一、去除噪声remove noise if any
# 1、边缘检测
blurred = cv.pyrMeanShiftFiltering(src, 10, 100)
# 2、灰度图像gray image
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
# 3、二值图像binary image
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow('binary_image', binary)
# 二、morphology operation:对图像进行开操作,能够去除图像内的噪声
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
# 1、膨胀操作可获取确定背景B
mb = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel, iterations=2)
# 2、获取图像边界(图像O-确定背景B)=原始图像-dilate图像
sure_bg = cv.dilate(mb, kernel, iterations=3)
cv.imshow('mor_opt', sure_bg)
# 三、距离变换函数distanceTransform:可用于计算对象的中心、细化轮廓和获取图像前景F等。
"""
distanceTransform(src, distanceType, maskSize, dst=None, dstType=None)
src:8位单通道的二值图像
distanceType:距离类型参数
maskSize:掩膜的尺寸,当distanceType=DIST_L1或DIST_C时,maskSize强制为3
dst:计算得到的目标图像,可以是8位或32位浮点数
dstType:目标图像的类型,默认CV_32F
"""
# 1、计算二值图像内任意点到最近背景点的距离
dist = cv.distanceTransform(mb, cv.DIST_L2, 3)
# 2、归一化函数normalize:把需要处理的数据通过某种算法处理后,限制在需要的一定范围内
dis_output = cv.normalize(dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('distance_t', dis_output * 50)
# 四、确定未知区域
# 1、生成Marker
ret, surface = cv.threshold(dist, dist.max() * 0.6, 255, cv.THRESH_BINARY)
cv.imshow('surface_bin', surface)
# 2、获取图像前景F
surface_fg = np.uint8(surface)
# 3、确定未知区域UN=图像O-确定背景B-确定前景F
unknown = cv.subtract(sure_bg, surface_fg)
# 五、connectedComponents:对前景图像进行标注
"""
connectedComponents(image, labels=None, connectivity=None, ltype=None)
image:8位单通道的待标注图像
labels:输出的标记图像,背景index=0
connectivity:连通域,默认是8连通
ltype:输出的labels类型,默认是CV_32S
ret:返回的标注的数量
markers:标注的结果图像
"""
ret, markers = cv.connectedComponents(surface_fg)
print(ret)
# 六、watershed transform:对预处理图像进行分割
# 1、将标注的结果都加上1,数值1表示背景区域,从数值2开始的值,代表不同的前景区域
markers = markers + 1
#2、 将已经计算出来的未知区域标注为0
markers[unknown == 255] = 0
"""
watershed(image, markers)
image:输入图像,必须是8位三通道的图像
markers:32位单通道的标注结果。在markers中,一个像素要么被设置为初期的“种子值”,要么被设置为“-1”表示边界
"""
# 3、图像分割
markers = cv.watershed(src, markers=markers)
# 4、给边界涂色
src[markers == -1] = [0, 0, 255]
cv.imshow('watershed_demo', src)