文章目录
- 1 原理
- 2 算法改进
- 3 API
- 4 实例
1 原理 ??分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。这种方法也称作泛洪法,对应的还有降雨法。
文章图片
??分水岭的计算过程是一个迭代标注过程。分水岭比较经典的计算方法是L. Vincent提出的。在该算法中,分水岭计算分两个步骤,一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高排序,然后在从低到高实现淹没过程中,对每一个局部极小值在h h h 阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。具体流程如下:
- 把梯度图像中的像素按照灰度值进行分类,设定一个测地距离阈值(测地线距离(Geodesic Distance):地球表面两点之间的最短路径的距离)。
- 找到灰度值最小的像素点,让t h r e s h o l d threshold threshold 从最小值开始增长,这些点为起始点。
- 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类。
- 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。
文章图片
文章图片
在O p e n C v OpenCv OpenCv 中算法不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标(mark)的分水岭算法。其中标记的每个点就相当于分水岭中的注水点,从这些点开始注水使得水平面上升。手动标记太麻烦,我们可是使用距离转换(
cv2.distanceTransform
函数)的方法进行标记。cv2.distanceTransform
计算的是图像内非零值像素点到最近的零值像素点的距离,即计算二值图像中所有像素点距离其最近的值为 0 的像素点的距离。当然,如果像素点本身的值为 0,则这个距离也为 0。 文章图片
3 API
cv2.watershed( InputArray image, InputOutputArray markers )
- 第一个参数i m a g e image image,必须是一个8bit 3通道彩色图像矩阵序列。
- 关键是第二个参数 markers:在执行分水岭函数watershed之前,必须对第二个参数m a r k e r s markers markers 进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过O p e n c v Opencv Opencv 中
connectedComponents
方法实现,这个是执行分水岭之前的要求。
4 实例 目标是将下图中的硬币和背景分离:
文章图片
在编程之前为了更好理解过程,要先介绍几个概念。
- 背景:不感兴趣的区域,越远离目标图像中心的区域就越是背景
- 前景:感兴趣的区域,越靠近目标图像中心就越是前景
- 未知区域:即不确定区域,边界所在的区域
文章图片
代码:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as pltdef test_watershed() :
image = cv.imread('images/coins.jpg')
image_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
#基于直方图的二值化处理
_, thresh = cv.threshold(image_gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)#做开操作,是为了除去白噪声
kernel = np.ones((3, 3), dtype = np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations = 2)#做膨胀操作,是为了让前景漫延到背景,让确定的背景出现
sure_bg = cv.dilate(opening, kernel, iterations = 2)#为了求得确定的前景,也就是注水处使用距离的方法转化
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
#归一化所求的距离转换,转化范围是[0, 1]
cv.normalize(dist_transform, dist_transform, 0, 1.0, cv.NORM_MINMAX)
#再次做二值化,得到确定的前景
_, sure_fg = cv.threshold(dist_transform, 0.5 * dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)#得到不确定区域也就是边界所在区域,用确定的背景图减去确定的前景图
unknow = cv.subtract(sure_bg, sure_fg)#给确定的注水位置进行标上标签,背景图标为0,其他的区域由1开始按顺序进行标
_, markers = cv.connectedComponents(sure_fg)# cv.imshow('markers', markers.astype(np.uint8))
# cv.waitKey(0)#让标签加1,这是因为在分水岭算法中,会将标签为0的区域当作边界区域(不确定区域)
markers += 1#是上面所求的不确定区域标上0
markers[unknow == 255] = 0
# print(markers.dtype)int32
markers_copy = markers.copy()# 使用分水岭算法执行基于标记的图像分割,将图像中的对象与背景分离
markers = cv.watershed(image, markers)#分水岭算法得到的边界点的像素值为-1
image[markers == -1] = [0, 0, 255]images = [thresh, opening, sure_bg, dist_transform, sure_fg, unknow, markers_copy, image]titles = ['thresh', 'opening', 'sure_bg', 'dist_tranform', 'sure_fg', 'unknow', 'markers', 'image']
plt.figure(figsize = (8, 6.1))for i in range(len(images)) :
if i == 7 :
plt.subplot(2, 4, i + 1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.axis('off')
else :
plt.subplot(2, 4, i + 1)
plt.imshow(images[i], cmap = 'gray')
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.savefig('figure.png')
plt.show()if __name__ == '__main__':
test_watershed()
【图像处理|【OpenCv】图像分割——分水岭算法】效果:
文章图片
从上面看来效果还是蛮好的。
推荐阅读
- Python|【Turtle系列】端正心态正确面对疫情,守护安全防线不放松,共抗疫情,只待春来~
- python|Python 学生信息管理系统------文章中源码100%真实有效-----如何将类、初始化属性、模块、循环判断、静态方法等一系列知识点结合起来做一个项目
- mysql|python数据库存 和 取 数据 ~~~~别磨叽了,拿来直接用吧
- 自然语言处理|自然语言处理(中文分句)——————中文逆向最大匹配,文章中含有验证源码
- 数据分析基础|python数据分析基础之Numpy库详解(二)
- python|python数据分析基础003 -numpy的使用(详解)
- Python|【Opencv实战】这是我见过的最强大“美颜滤镜”,代码美颜傻瓜式一键操作~(附源码)
- 图像处理|【OpenCv】图像分割——聚类算法
- 图像算法|非局部均值滤波算法(NL-means)