0


图像分割 - 分水岭算法

1. 介绍

图像是由x,y表示的,如果将灰度值也考虑进去的话,那么一幅图像需要一个三维的空间去表示。

这样就可以把x,y轴比作大地,将灰度值的z轴比作地面上的坡度。

因为图像的灰度值是不均匀的,那么也意味着这个地面也是坑坑洼洼的。那么试想一下,下雨的时候,由于地面是不平坦的,雨水会顺着高的地面流向地处。必然会导致有的地方堆满了水,有的地方由于地势较陡,没有雨水

分水岭算法就是利用这种“地形学”,或者说灰度值的不均匀对图像进行分割。

在这种将图像类比成地形的方法里,主要考虑三种点:

  • 属于区域极小值的点
  • 水滴所在位置的点,如果把水滴放在任意位置,水滴必然流向某个极小值
  • 水等概率的流向不止一个极小值的点

在这里,如果某个区域是极小值,且满足第二个条件的点集称为汇水盆地或者分水岭

满足第三个条件的点形成地形表面的线,称为分界线

分水岭算法实现的思路是:

找到一些初始点,然后对这些点集进行灌水。随着水位的上升,不同区域的水会开始融合。为了阻止这种现象,在这些融合处建立拦水坝,这样拦水坝就把图像进行了分割

初始点的选择需要用到距离变换

2. 分水岭算法的实现

首先尝试对下面的算法进行分水岭算法的分割


这里读取的是彩色图像,因为最后的 watershed 函数需要传入彩色图像

这里对图像进行阈值处理的结果为


阈值处理发现,这里硬币内部出现了孔洞,在背景上也出现了白色的噪声点

因为开运算可以消除白色的噪声,而闭运算可以消除内部的孔洞,所以这里采用形态学的方法进行处理。具体的可以参考:灰度级形态学 - 灰度开运算和灰度闭运算


接下来,通过膨胀,将前景扩大,这样就可以确定哪些是背景,哪些是前景

这一步主要为了确定真正的前景区域


距离变换

接下来,通过距离变换可以找到这些硬币近似的中心点。先展示下效果,在做讲解

因为数据类型的原因,这种用 matplotlib 展示。这里距离变换会计算前景像素点到周围最近背景像素点的距离,所以这里像素点越亮,距离背景越远。并且输入图像必须是个二值图像


然后,获取距离变换中距离背景远处的点,这些点就是我们分水岭算法的初始点

因为现在的前景区域一定是硬币的位置


unknown 是不确定区域,或者说,这里是有分线线的地方


连接连通分量

它会把将背景标记为 0,其他的对象使用从 1 开始的正整数标记

这样,分水岭算法会从每个标记的位置开始注水,除了0未知区域。这样水位升高的时候,不同区域的水就会在未知区域相遇,这样分界线就被找出来了


分水岭算法会将不同区域之间的边界设置为 -1

watershed的结果中只有-1,0,1,2这样的数

分水岭算法的结果:

分割的结果:

3. 代码

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

src = cv2.imread('./img.png')                       # 彩色图像

img = src.copy()
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)          # 转为灰度图像

ret, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)   # 阈值处理

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))                            # 获取结构元
img_close = cv2.morphologyEx(img_bin, cv2.MORPH_CLOSE, kernel, iterations=3)        # 闭运算填充内部的孔洞
img_open = cv2.morphologyEx(img_close, cv2.MORPH_OPEN, kernel, iterations=2)        # 开运算消除白色噪声

img_bin = cv2.dilate(img_open, kernel, iterations=2)                                # 膨胀确定前景和背景
dist_transform = cv2.distanceTransform(img_bin, cv2.DIST_L2, 5)                     # 距离变换

ret, img_seed = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, cv2.THRESH_BINARY)   # 获取距离背景最远的点
img_seed = np.uint8(img_seed)       # 转为图像的形式

unknown = cv2.subtract(img_bin,img_seed)                # 可能是背景,可能是前景

ret, markers = cv2.connectedComponents(img_seed)       # 连接连通分量
markers = markers + 1                                  # 确保背景是1不是0
markers[unknown == 255] = 0                            # 未知区域标记为0

markers = cv2.watershed(src, markers)                 # 分水岭算法
src[markers == -1] = [255,0,0]                        # 将边界找出

plt.imshow(np.abs(markers),cmap = 'jet')                # 分水岭算法结果
plt.show()

cv2.imshow('img',src)                                   # 分割结果
cv2.waitKey()
cv2.destroyAllWindows()

本文转载自: https://blog.csdn.net/qq_44886601/article/details/128012108
版权归原作者 Henry_zhangs 所有, 如有侵权,请联系我们删除。

“图像分割 - 分水岭算法”的评论:

还没有评论