分水岭算法watershed
- 进行图像分割
- 基本的步骤
- 通过形态学开运算对原图像O去噪
- 通过膨胀操作获取“确定背景B”
- 利用距离变换函数对图像进行运算,并对其进行阈值处理,得到“确定前景F”
- 计算未知区域UN(UN = O – B – F )
- 利用函数connecedComponents对原图像O进行标注
- 对函数connecedComponents的标注结果进行修正
- 使用分水岭函数watershed完成对图像的分割
操作小记
代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('coins.jpg')
if img is None:
print('Could not open or find the image ')
exit(0)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# cv.imshow("threshold", thresh) #阈值处理后会有紧挨着(粘连)的情况
# 去噪处理
kernel = np.ones((3, 3), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2) # 开运算
# sure background area
sure_bg = cv.dilate(opening, kernel, iterations=3) # 膨胀操作
# Finding sure foreground area
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0) # 距离背景点足够远的点认为是确定前景
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg) # 确定未知区域:减法运算
# Marker labelling
ret, markers = cv.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
markers = cv.watershed(img, markers)
img[markers == -1] = [255, 0, 0]
plt.imshow(img)
plt.show()
效果
代码分析
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
- 函数的作用是进行阈值处理
- 返回参数:
ret
为阈值,thresh
为处理结果 - 输入参数中
-
gray
为输入图像的灰度图像,上一行代码cv.cvtColor(img, cv.COLOR_BGR2GRAY)
进行了色彩空间的转换 -
0
为设定的阈值 -
255
代表图像最大值 cv.THRESH_BINARY_INV + cv.THRESH_OTSU
二值化的方式,THRESH_BINARY_INV
表示采取反二进制阈值化,而`THRESH_OTSU``代表采用自适应图像的二值化Otsu
- 说明:反二进制阈值化:小于阈值取255,大于阈值取0
- 测试中,
ret=162.0
,thresh
的效果如下:
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
- 函数的作用是去除噪声
- 输入参数中
-
thresh
是进行阈值处理的图像 -
cv.MORPH_OPEN
表示进行开运算,开运算就是对图像先腐蚀再膨胀 -
kernel
为形态学运算的内核,上一句kernel = np.ones((3, 3), np.uint8)
设置了参考点位于中心3x3的核 -
iterations
用于设置迭代次数,这里设置为2
opening
为处理结果,效果如下:- 相关资料:More Morphology Transformations
sure_bg = cv.dilate(opening, kernel, iterations=3)
- 作用:用膨胀的方式获取背景
sure_bg
为得到的结果,效果如下:
# Finding sure foreground area
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0) # 距离背景点足够远的点认为是确定前景
通过函数cv.distanceTransform(...)
进行距离变换,cv.DIST_L2
代表采用欧几里得的距离计算公式,5
代表掩膜尺寸,用来确定前景,然后通过阈值处理得到核心的区域,超过最大值的70%才留下来
dst = cv2.distanceTransform( src, distanceType, maskSize[, dst[, dstType]] )
src
:8位单通道的二值图像distanceType
:距离类型参数maskSize
:掩膜尺寸dstType
:目标图像的类型,默认值为cv_32Fdst
:计算得到的目标图像distanceType
取值
maskSize
说明
得到效果如下:
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg) # 确定未知区域:减法运算
通过做减法来获取到未知区域,确定的背景-确定的前景
确定未知区域
对确定的前景F进行膨胀放大——得到——确定的背景B未知区域UN = 原图像O—确定的背景B — 确定前景F减法运算
unknown
的效果如下:
# # Marker labelling
ret, markers = cv.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
用来设置水坝来分割背景和前景,将未知区域标记为0
标注确定的前景图像函数
connectedComponents
cv2. connectedComponents中:背景标注为0,其他对象使用从1开始的正整数标注
- 数值0代表背景区域
- 从数值1开始的值,代表不同的前景区域
在分水岭算法中,标注0代表未知区域,所以需要对上面的标注结果进行调整
- 数值1代表背景区域
- 从数值2开始的值,代表不同的前景区域
- 对未知区域进行标注,将计算出的未知区域标注为0
retval, labels = cv2. connectedComponents(image )
- images:8位单通道的待标注图像
- retval :返回标注的数量
- labels :标注的结果图像
markers = cv.watershed(img, markers)
进行分水岭算法
Markers = cv2.watershed(image, markers )
Image: 输入图像,必须是一个8bit 3通道的图像
- 在处理之前必须用正数大致勾画出图像中的期望分割区域。
- 每个区域被标注为1、2、3等。对尚未确定的区域,需要将它们标注为0.
- 标注区域可以理解为进行分水岭算法分割的“种子”区域
markers: 32位单通道的标注结果,和image大小相同
- 算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定
- 每个像素要么要么被设置为初期的“种子值”,要么为“-1”
- 区域与区域之间的分界处的值被置为“-1”
附录
官方文档:The Watershed Transformation
距离变换函数distanceTransform
提取前景对象
如果前景对象的中心(质心)距离值为0的像素点距离较远,会得到一个较大的值
如果前景对象的边缘距离值为0的像素点较近,会得到一个较小的值