目录
1. 介绍
2. 分水岭算法的实现
距离变换
连接连通分量
3. 代码
1. 介绍
图像是由x,y表示的,如果将灰度值也考虑进去的话,那么一幅图像需要一个三维的空间去表示。
这样就可以把x,y轴比作大地,将灰度值的z轴比作地面上的坡度。
因为图像的灰度值是不均匀的,那么也意味着这个地面也是坑坑洼洼的。那么试想一下,下雨的时候,由于地面是不平坦的,雨水会顺着高的地面流向地处。必然会导致有的地方堆满了水,有的地方由于地势较陡,没有雨水
分水岭算法就是利用这种“地形学”,或者说灰度值的不均匀对图像进行分割。
在这种将图像类比成地形的方法里,主要考虑三种点:
属于区域极小值的点水滴所在位置的点,如果把水滴放在任意位置,水滴必然流向某个极小值水等概率的流向不止一个极小值的点在这里,如果某个区域是极小值,且满足第二个条件的点集称为汇水盆地或者分水岭
满足第三个条件的点形成地形表面的线,称为分界线
分水岭算法实现的思路是:
找到一些初始点,然后对这些点集进行灌水。随着水位的上升,不同区域的水会开始融合。为了阻止这种现象,在这些融合处建立拦水坝,这样拦水坝就把图像进行了分割
初始点的选择需要用到距离变换
2. 分水岭算法的实现
首先尝试对下面的算法进行分水岭算法的分割
这里读取的是彩色图像,因为最后的 watershed 函数需要传入彩色图像
这里对图像进行阈值处理的结果为
阈值处理发现,这里硬币内部出现了孔洞,在背景上也出现了白色的噪声点
因为开运算可以消除白色的噪声,而闭运算可以消除内部的孔洞,所以这里采用形态学的方法进行处理。具体的可以参考:灰度级形态学 - 灰度开运算和灰度闭运算
接下来,通过膨胀,将前景扩大,这样就可以确定哪些是背景,哪些是前景
这一步主要为了确定真正的前景区域
距离变换
接下来,通过距离变换可以找到这些硬币近似的中心点。先展示下效果,在做讲解
因为数据类型的原因,这种用 matplotlib 展示。这里距离变换会计算前景像素点到周围最近背景像素点的距离,所以这里像素点越亮,距离背景越远。并且输入图像必须是个二值图像
然后,获取距离变换中距离背景远处的点,这些点就是我们分水岭算法的初始点
因为现在的前景区域一定是硬币的位置
unknown 是不确定区域,或者说,这里是有分线线的地方
连接连通分量
它会把将背景标记为 0,其他的对象使用从 1 开始的正整数标记
这样,分水岭算法会从每个标记的位置开始注水,除了0未知区域。这样水位升高的时候,不同区域的水就会在未知区域相遇,这样分界线就被找出来了
分水岭算法会将不同区域之间的边界设置为 -1
watershed的结果中只有-1,0,1,2这样的数
分水岭算法的结果:
分割的结果:
3. 代码
import cv2import numpy as npimport matplotlib.pyplot as pltsrc = 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不是0markers[unknown == 255] = 0 # 未知区域标记为0markers = 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()