在OpenCV里实现分水岭算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/caimouse/article/details/102619371

目标:学习基于标记的图像分割算法,又叫做分水岭算法,以及使用cv.watershed()函数。

 

理论:

任何灰度图像都可以看作是一个地形表面,高强度表示山峰和丘陵,低强度表示山谷。你开始用不同颜色的水(标签)填满每个孤立的山谷(局部最小值)。随着水位的上升,根据附近的山峰(坡度),来自不同山谷的水,显然是不同颜色的水会开始融合。为了避免这种情况,你要在水融合的地方设置屏障。当你继续补充水和建造更高的障碍物时,直到所有的山峰都在水下。然后,你所创建的屏障将为你提供了分割结果。这就是分水岭背后的“原理”。

 

但是这种方法会由于图像中的噪声或任何其他不规则性而导致过分割的后果,所以OpenCV的实现是基于标记的分水岭算法,你需要提前指定哪些是分割的山谷位置,哪些不是。它是一个交互式的图像分割算法。我们所做的就是给我们所知道的物体贴上不同的标签。用一种颜色(或强度)标记我们确定是前景或对象的区域,用另一种颜色标记我们确定是背景或非对象的区域,最后用0标记我们不确定的区域。这就是我们的标记marker。然后根据marker来应用分水岭算法。最后我们的标记marker将用我们给出的分割标签进行更新,对象的边界值将置为-1。

 

编码实现:

接着下来,将用一个例子来演示分水岭算法的使用。如下图所示,这是一个分割对象相互接触的情况,需要使用距离变换和分水岭算法。

从上图可以看到所有硬币平放,并且靠得非常近,连接在一起,如果使用阈值分割,会发现它们轮廓相互连接成一片。为了可以把它们分离开,需构造一个分水岭算法里的标记图,第一步采用OTSU算法进行二值分割:

import numpy as np

import cv2

from matplotlib import pyplot as plt

 

img = cv2.imread('coin1.png')

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

 

cv2.imshow('out', thresh)

输出结果如下:

接着下来需要把二值图像中白噪声去除,可以使用前面学习过的形态学的开运算,使用闭运算来去除对象中间的小洞。所以,现在我们可以确定靠近物体中心的区域是前景,而远离物体的区域是背景。唯一我们不确定的区域是硬币的边界区域。

接着下来通过膨胀运算就可以把边缘去掉,让硬币的面积更大,这样就可以认为硬币的最大面积就是目前这种情况了,这就是我们得到可确信的硬币最大面积,如上图的左图。接着下来我们要找硬币的最小可能面积,也就是说,硬币小到多少时,一定是这个硬币的面积。那么就可以采用距离变换的算法函数cv2.distanceTransform来实现,它实现了计算白点与最接近黑点的距离,通过这样计算,就可以得到一个灰度图像,如上图的右图所示。有了上面两步计算,就可以得到可靠的背景和可靠的前景范围,那么只要把可靠的背景减去可靠的前景,就可以得到不确定区域的面积了,如下图:

在这图里白色区域就是不能确认是否哪一个硬币的区域。实际中应用Watershed变换的有效途径是首先确定图像中目标的标记或种子,然后再进行生长,并且生长的过程中仅对具有不同标记的标记点建筑防止溢流汇合的堤坝,产生分水线。

接着下来是使用cv2.connectedComponents函数来进行标记markers,它会创建一个标记图像markers出来,与图形的大小相同,但每个像素是使用int32类型来定义。cv2.connectedComponents函数会把背景元素标记为0,而非背景元素标记为从1开始的整数,因此返回图像是[0,N-1]这样分类。但是分水岭算法认为不知道区域为0,所以要对前面创建出来markers进行增加1的步骤,这样就变成背景元素全是1,非背景元素为2到N。最后一步,就是把前面找到不确认区域的元素全部标记为0,这样markers就形这样一个图像:0表示不能确认区域,1表示背景,2到N表示前景。

到这里就把标记做好了,最后一步就是进行分水岭算法运算,如下:

markers = cv2.watershed(img,markers)#进行分水岭算法

img[markers == -1] = [0,0,255] #把原图分离的边界改为红色

当分水岭算法运算完成之后,markers里等于-1的元素就是原图像里的边缘。在这里把边缘元素全部显示为红色,如下图:

例子全部代码:

#python 3.7.4,opencv4.1
#蔡军生 https://blog.csdn.net/caimouse/article/details/51749579
#
import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('coin1.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

cv2.imshow('thresh', thresh)

#去除噪声
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
cv2.imshow('opening', opening)

#一定是背景区域
sure_bg = cv2.dilate(opening,kernel,iterations=3)
cv2.imshow('sure_bg', sure_bg)

#一定是前景区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
#归一化显示
distN = cv2.normalize(dist_transform,0,255, cv2.NORM_MINMAX)
cv2.imshow('dist_transform', distN)

ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
cv2.imshow('sure_fg', sure_fg)

#找到不能分别区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)

cv2.imshow('unknown', unknown)

#进行标记
ret, markers = cv2.connectedComponents(sure_fg)

# 每个像素增加1,以便所有背景非0,等于1
markers = markers+1

#现在把所有不确定区域标志为0
markers[unknown==255] = 0

markers = cv2.watershed(img,markers)#进行分水岭算法
img[markers == -1] = [0,0,255] #把原图分离的边界改为红色
cv2.imshow('markers', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

到这里就完成了整个算法应用过程,如果还想了解更多关于分水岭算法,可以查看这个网站:

http://www.cmm.mines-paristech.fr/~beucher/wtshed.html

https://blog.csdn.net/caimouse/article/details/51749579

猜你喜欢

转载自blog.csdn.net/caimouse/article/details/102619371