图像分割之分水岭算法

理论

  任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰和丘陵,而低强度表示山谷。用不同颜色的水(标签)填充每个孤立的山谷(局部极小值)。当水上升时,根据附近的峰(梯度),不同山谷不同的颜色的水,显然会开始融合。为了避免这种情况,你在水就要融合的地方及时增加屏障(增高水坝)。你继续填满水,建造屏障,直到所有的山峰都被淹没。然后,您创建的屏障会给出分割结果。这就是分水岭背后的“哲学”。你可以访问分水岭的CMM网页,里面有动画帮助理解。

  但是这种方法会由于图像中的噪声或其他不规则性因素而导致过度分割的结果。OpenCV实现了一种基于标记的分水岭算法,你可以指定哪些是要合并的谷点,哪些不是。我们所做的是给我们所知道的对象赋予不同的标签(marker)。用一种颜色(或强度)标记我们确定的为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后用0标记我们不确定的区域。然后应用分水岭算法,其将使用我们给出的标签进行更新(填水),对象的边界值将为-1。

代码

  下面我们将看到一个关于如何使用距离变换(Distance Transform)和分水岭(watershed)分割相互接触的对象的例子。下面的图像,硬币互相接触。

  首先使用Otsu的二值化方法把图片变成二值图像。

import numpy as np
import cv2
img = cv2.imread(‘E:/pictures/coins.jpg‘)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

  结果为:

  现在我们需要去除图像中的一些小的白色噪声,我们可以使用形态学开运算。为了去除物体上的小洞(黑色噪声),我们可以使用形态闭运算。现在我们可以确定,靠近物体(硬币)中心的区域是前景,远离物体的区域是背景。还有我们不确定的区域就是是硬币的边界区域。

  所以我们需要提取的是硬币的区域。用侵蚀法磨损边界像素。剩下部分,我们都能确定是硬币。如果物体不互相接触,这个方法就很明显确定彼此之间的硬币。但是由于它们彼此接触,另一个好的方法是距离变换法并使用一个合适的阈值。接下来我们需要找到确定不是硬币的区域。用膨胀法增加了物体到背景的边界。这样,因为硬币边界区域被扩展,我们就可以确保此结果中的任何背景区域包含于源图像背景。请看下图。

  剩下的区域是我们不知道的,可能是硬币也可能是背景。用分水岭算法应该能找到它。这些区域通常在硬币的边界附近,也就是前景和背景相遇的地方(或者是两种不同硬币相遇的地方)。用sure_bg减去sure_fg可以得到(看最终结果second分窗图)。

# 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)

  看结果。我们得到了硬币的一些区域,我们确定这些区域是硬币,现在它们被分离了。(在某些情况下,您可能只对物体大概分割成几个感兴趣,而不是物体的相对更精确的分割情况。在这种情况下,你不需要使用距离变换,只要侵蚀就足够了。)

  现在我们确定了哪些是硬币的区域,哪些是背景等等。因此,我们创建了marker(它是一个与原始图像大小相同的矩阵,但是使用int32数据类型),并在其中标记区域。我们确定的区域(无论是前景还是背景)被标记为任意正整数,但是不同的整数,而我们不确定的区域则被保留为零。为此,我们使用了cv2.connectedComponents()。它用0来标记图像的背景,然后用从1开始的整数来标记其他对象。

但是我们知道,如果将background标记为0,watershed()方法将认为它是未知区域。所以我们要用不同的整数来标记它。相反,我们将标记未知区域为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

  查看colormap中显示的结果。深蓝色区域表示未知区域。硬币的颜色是不同的。与未知区域相比,背景确定的剩余区域以浅蓝色显示。

  现在是最后一步,应用分水岭,边界区域将标记为-1。

markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]
cv2.namedWindow(‘first‘,cv2.WINDOW_AUTOSIZE)
cv2.imshow(‘second‘,unknown)
cv2.imshow(‘first‘,img)
cv2.waitKey(0)
cv2.destroyAllWindows()

  最终结果:

  前面的图都是理论分析,最后两张才是上面程序段拼接起来运行的结果。在此特别感谢OpenCV官网提供的解释

原文地址:https://www.cnblogs.com/StillWater-is-me/p/11186158.html

时间: 2024-10-10 17:06:47

图像分割之分水岭算法的相关文章

新手学,java使用分水岭算法进行图像分割(一)

最近被图像分割整的天昏地暗的,在此感谢老朋友周洋给我关于分水岭算法的指点!本来打算等彩色图像分割有个完满的结果再写这篇文章,但是考虑到到了这一步也算是一个阶段,所以打算对图像分割做一个系列的博文,于是先写这篇. 啰嗦了这么多!先看效果: 效果一般,存在着很多过分割现象,但比没使用滤波之前的效果好很多,过分割是分水岭算法的通病.这个后续博文会继续解决. 本文用java实现的是基于自动种子区域的分水岭算法,注意本文是基于单色的分割,所以将输入图片首先进行灰度化处理,这个比较简单,不多提了:因此,对于

灰度图像--图像分割 区域分割之分水岭算法

学习DIP第60天 转载请标明本文出处:http://blog.csdn.net/tonyshengtan ,出于尊重文章作者的劳动,转载请标明出处!文章代码已托管,欢迎共同开发:https://github.com/Tony-Tan/DIPpro 开篇废话 今天已经是第60篇博客了,这六十篇每一篇平均要两天左右,所以,在过去的四个月学到了这么多知识,想想挺开心,但学的越多就会发现自己不会的越多.从小学到大学,这么多年一直以学习为主要工作但学习又有很多阶段,对于通用知识,比如小学的语文数学此观点

新手学,java使用分水岭算法进行图像分割(二)

最近要考试了,所以现在不写,怕这段时间都没空写了.(算法的效率暂时不考虑,有时间了再来解决彩色信息和效率问题) 继上一篇的算法:http://blog.csdn.net/abcd_d_/article/details/41218549,本文对分水岭算法进行了区域合并,合并准则采用hsv颜色空间的区域特征的直方图相似度进行合并.且看效果:图一是原图,图二是采用之前的文章算法的效果,图三为进行了区域合并后的效果.(大小被我调整过) (图一) (图二) (图三) 在第一篇的基础之上,增加了区域合并算法

python数字图像处理(19):骨架提取与分水岭算法

骨架提取与分水岭算法也属于形态学处理范畴,都放在morphology子模块内. 1.骨架提取 骨架提取,也叫二值图像细化.这种算法能将一个连通区域细化成一个像素的宽度,用于特征提取和目标拓扑表示. morphology子模块提供了两个函数用于骨架提取,分别是Skeletonize()函数和medial_axis()函数.我们先来看Skeletonize()函数. 格式为:skimage.morphology.skeletonize(image) 输入和输出都是一幅二值图像. 例1: from s

二十一 分水岭算法

一.原理 分水岭算法主要用于图像分段,通常是把一副彩色图像灰度化,然后再求梯度图,最后在梯度图的基础上进行分水岭算法,求得分段图像的边缘线. 任意的灰度图像可以被看做是地质学表面,高亮度的地方是山峰,低亮度的地方是山谷.给每个孤立的山谷(局部最小值)不同颜色的水(标签),当水涨起来,根据周围的山峰(梯度),不同的山谷也就是不同的颜色会开始合并,要避免这个,你可以在水要合并的地方建立障碍,直到所有山峰都被淹没.你所创建的障碍就是分割结果,这个就是分水岭的原理,但是这个方法会分割过度,因为有噪点,或

opencv分水岭算法对图像进行切割

先看效果 说明 使用分水岭算法对图像进行切割,设置一个标记图像能达到比較好的效果,还能防止过度切割. 1.这里首先对阈值化的二值图像进行腐蚀,去掉小的白色区域,得到图像的前景区域.并对前景区域用255白色标记 2.相同对阈值化后的图像进行膨胀,然后再阈值化并取反.得到背景区域. 并用128灰度表示 3.将前景和背景叠加在一起在同一幅图像中显示. 4.用标记图和原图,利用opencv的watershed对图像进行切割. 源代码 class WatershedSegment{ private: cv

OpenCV 源码中分水岭算法 watershed 函数源码注解

为了研究分水岭算法,阅读了OpenCV 2.4.9 中watershed函数的源码实现部分,代码位于 opencv\sources\modules\imgproc\src\segmentation.cpp 文件中.先贴出加了注解的代码,以后补充对分水岭算法的解释. #include "precomp.hpp" /******************************************************* Watershed **********************

opencv分水岭算法对图像进行分割

先看效果 说明 使用分水岭算法对图像进行分割,设置一个标记图像能达到比较好的效果,还能防止过度分割. 1.这里首先对阈值化的二值图像进行腐蚀,去掉小的白色区域,得到图像的前景区域.并对前景区域用255白色标记 2.同样对阈值化后的图像进行膨胀,然后再阈值化并取反.得到背景区域.并用128灰度表示 3.将前景和背景叠加在一起在同一幅图像中显示. 4.用标记图和原图,利用opencv的watershed对图像进行分割. 源码 class WatershedSegment{ private: cv::

新手学,java使用分水岭算法进行图像切割(一)

近期被图像切割整的天昏地暗的,在此感谢老朋友周洋给我关于分水岭算法的指点!本来打算等彩色图像切割有个完满的结果再写这篇文章,可是考虑到到了这一步也算是一个阶段,所以打算对图像切割做一个系列的博文,于是先写这篇. 啰嗦了这么多!先看效果: 效果一般,存在着非常多过切割现象,但比没使用滤波之前的效果好非常多,过切割是分水岭算法的通病.这个兴许博文会继续解决. 本文用java实现的是基于自己主动种子区域的分水岭算法,注意本文是基于单色的切割,所以将输入图片首先进行灰度化处理,这个比較简单,不多提了:因