函数cv2.watershed()实现分水岭算法。在具体实现过程中,还需要借助其他形态学函数来辅助。

1、形态学函数回顾

OpenCV For Python入门25 形态学概述及腐蚀操作

OpenCV For Python入门26 形态学的膨胀操作

OpenCV For Python入门27 开运算、闭运算及形态学梯度运算

OpenCV For Python入门28 礼帽运算、黑帽运算和核函数

(1)开运算

开运算是先腐蚀后膨胀的操作,开运算能够去除图像内的噪声。

开运算效果

对图像进行开运算,能够去除图像内的噪声,在用分水岭算法处理图像前,要先使用开运算去除图像内的噪声,避免噪声对图像分割可能造成的干扰。

(2)获取图像边界

通过形态学操作和减法运算能够获取图像的边界。

形态学操作

2、距离变换函数distance Transform

当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了,此时借助于距离变换函数cv2.distanceTransform()可以方便地将前景对象提取出来。

距离变换函数cv2.distanceTransform()计算二值图像内任意点到最近背景点的距离。一般情况下,该函数计算的是图像内非零值像素点的最近的零值像素点的距离,即计算二值图像中所有像素点距离其最近的值为0的像素点的距离。当然,如果像素点本身的值为0,则这个距离也为0。

距离变换函数cv2.distanceTransform()的计算结果反应了各个像素和背景(值为0的像素点)的距离关系。通常情况下:

  • 如果前景对象的中心(质心)距离值为0的像素点距离较远,会得到较大的值
  • 如果前景对象的边缘距离值为0的像素点较近,会得到较小的值

对上述结果进行阈值化,就可以得到图像子图的中心、骨架等信息。

语法:

dst = cv2.distanceTransform(src,distanceType,maskSize[,dstType])
  • src是8位单通道的二值图像。
  • distanceType是距离参数类型,值及含义如下表所示:
参数值 含义
cv2.DIST_USER 用户自定义距离
cv2.DIST_L1 distance = |x1-x2|+|y1-y2|
cv2.DIST_L2 简单欧式距离
cv2.DIST_C distance = max(|x1-x2|,|y1-y2|)
cv2.DIST_L12 L1-L2 metric:distance = 2(sqrt(1+x*x/2)-1)
cv2.DIST_FAIR distance = c^2(|x|/c - log(1+|x|/c)) , c = 1.3998
cv2.DIST_WELSCH diatance = c^2/2(1-exp(-(x/c)^2)) , c=2.9846
cv2.DIST_HUBER distance = |x|<c?x^2/2:c(|x|-c/2) , c=1.345
  • maskSize为掩模的尺寸。需要注意的是,当distanceType = cv2.DIST_L1或cv2.DIST_C时,maskSize强制为3
参数值 对应整数
cv2.DIST_MASK_3 3
cv2.DIST_MASK_5 5
cv2.DIST_MASK_PRECISE
  • dstType为目标图像的类型,默认值为CV_32F
  • dst表示计算的目标图像,可以是8位或32位浮点数,尺寸和src相同

例:使用距离变换函数cv2.distanceTransform(),计算一幅图像的确定前景,并观察结果。

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

#读取
img = cv2.imread('water_coins.png')
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow = img.copy()
#二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)

dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret,fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)

plt.subplot(131)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(132)
plt.imshow(dist_transform)
plt.axis('off')
plt.subplot(133)
plt.imshow(fore)
plt.axis('off')

plt.show()

运行结果

  • 左图是原始图像
  • 中间的是距离变换函数计算得到的图像
  • 右图是对距离变换的图像进行阈值处理后的结果图像

右图比较准确的显示出左图内的确定前景,这里的确定前景通常是指前景对象的中心,之所以认为这些点是确定前景,是因为他们距背景点的距离足够远。距离大于足够大的固定阈值(0.7*dist_transform.max())的点。

3、确定未知区域

使用形态学的膨胀操作能够将图像内的前景膨胀放大。当图像内的前景被放大后,背景就会被压缩,所以此时得到的背景信息一定小于实际背景的,不包含前景的确定背景。现将确定背景称之为B

距离变换函数能够获取图像的中心,得到”确定前景“,为了方便,现将确定前景称之为F

针对一幅图像O,通过一下关系能够得到未知区域UN:

未知区域UN = 图像O - 确定背景B - 确定前景F

可整理得:

未知区域UN = ( 图像O - 确定背景B ) - 确定前景F

上式中,图像O - 确定背景B, 可以通过对图像进行形态学的膨胀操作得到。

例:标注一幅图像的确定前景、确定背景以及未知区域。

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

#读取
img = cv2.imread('water_coins.png')
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow = img.copy()
#二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)

bg = cv2.dilate(opening,kernel,iterations=3)

dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)

ret,fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)

fore = np.uint8(fore)
un = cv2.subtract(bg,fore)

plt.subplot(221)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(222)
plt.imshow(bg)
plt.axis('off')
plt.subplot(223)
plt.imshow(fore)
plt.axis('off')
plt.subplot(224)
plt.imshow(un)
plt.axis('off')

plt.show()

效果展示

  • 左上角是原始图像
  • 右上角是对图像ishow进行膨胀后得到的图像bg,其背景图像时确定背景,前景图像是原始图像减去确定背景
  • 左下角是确定前景图像fore
  • 右下角即位置图像

需要注意,右上角的图中,一个个小圆是”原始图像 - 确定背景“部分,而不是”确定背景“,背景图才是确定背景

4、函数connectedComponents

明确了确定前景后,就可以对确定前景图像进行标注了。在OpenCV中,可以使用函数cv2.connectComponents()进行标注。该函数会将背景标注为0,而将其他的对象使用从1开始的正整数标注。

函数cv2.connectedComponents()函数的语法:

retval, labels = cv2.connectedComponents(image)
  • image为8位单通道的待标注的图像
  • retval为返回的标注的数量
  • labels为标注的结果图像

例:使用函数cv2.connectedComponents( )

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

#读取
img = cv2.imread('water_coins.png')
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
ishow = img.copy()
#二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)

sure_bg = cv2.dilate(opening,kernel,iterations=2)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret,fore = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)

fore = np.uint8(fore)
ret, markers = cv2.connectedComponents(fore)

plt.subplot(131)
plt.imshow(ishow)
plt.axis('off')
plt.subplot(132)
plt.imshow(fore)
plt.axis('off')
plt.subplot(133)
plt.imshow(markers)
plt.axis('off')

print(ret)

plt.show()

标注结果

  • 左图是原始图像
  • 中间的图,是距离变换后的前景图像的中心点图像fore
  • 右图是对前景图像的中心点图像进行标注后的结果图像markers

5、修正markers

函数cv2.connectedComponents()在标注图像时,会将背景标注为0,将其他的对象用从1开始的正整数标注,具体的对应关系为:

  • 数值0代表背景区域
  • 从数值1开始的值,代表不同的前景区域

在分水岭算法中,标注0代表位置区域。所以,我们要对函数cv2.connectedComponents()的标注结果进行调整:将标注的结果都加上数值1。经过上述处理后,标注结果:

  • 数值1代表背景区域
  • 从数值2开始的值,代表不同的前景区域。

关键代码为:

ret, markers = cv2.connectedComponents(fore)
markers = markers + 1
markers[未知区域] = 0 

6、函数cv2.watershed()

完成上述处理后,就可以使用分水岭算法对预处理结果图像进行分割了,在OpenCV中,实现分水岭算法的函数是cv2.watershed()

语法格式为:

markers = cv2.watershed(image, markers)
  • image是输入图像,必须是8位三通道的图像
  • markers是32位单通道的标注结果。

博主个人公众号
版权声明 ▶ 本网站名称:陶小桃Blog
▶ 本文链接:https://www.52txr.cn/2022/OpenCV54.html
▶ 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行核实删除。
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

最后修改:2022 年 07 月 12 日
如果觉得我的文章对你有用,请随意赞赏