
文章目录
1、功能描述
基于分水岭算法对图片进行分割
分水岭分割算法(WaterShed Algorithm),是一种基于拓扑理论的数学形态学的分割方法,广泛应用于数学、图像学和电子信息学领域。
一、算法原理
分水岭分割算法的基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。
分水岭的概念和形成可以通过模拟浸入过程来说明:在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
二、算法步骤
分水岭算法的计算过程是一个迭代标注过程,主要包括排序和淹没两个步骤。
- 排序:对每个像素的灰度级进行从低到高排序。
- 淹没:在从低到高实现淹没过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。
分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点即为分水岭。
三、应用场景
- 医学图像分析:用于分割MRI或CT图像中的不同结构,如肿瘤、器官等。
- 纹理分割:将图像分割成纹理块,从而识别材质。
- 物体检测:分割图像中的物体,从而实现目标检测。
四、优缺点及改进方法
-
优点:
- 分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证。
- 分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。
-
缺点:
- 常规的分水岭算法由于图像上噪声和图局部不连续原因常常表现出过度分割。
-
改进方法:
- 利用先验知识去除无关边缘信息。
- 修改梯度函数使得集水盆只响应想要探测的目标。
- 对梯度图像进行阈值处理,以消除灰度的微小变化产生的过度分割。
五、示例
在OpenCV中,分水岭算法通过 watershed() 函数实现。该函数基于图像中的灰度级和边缘来构建一组标记,将图像分割成不同的区域或物体。虽然需要手动标记辅助,但其效果显著。
综上所述,分水岭分割算法是一种有效的图像分割方法,但需要注意其过度分割的问题,并采取相应的改进方法以提高分割效果。
2、代码实现
图像前处理
import cv2 as cv
import numpy as np
import random as rng
def process_img2(img):
# 转成灰度图
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cv.imwrite("img_gray.jpg", img_gray)
# 高斯模糊
img_gray = cv.GaussianBlur(img_gray, (5, 5), 0.1)
cv.imwrite("GaussianBlur.jpg", img_gray)
# 中值滤波
img_gray = cv.medianBlur(img_gray, 5)
cv.imwrite("medianBlur.jpg", img_gray)
# 二值化
_, image_binary = cv.threshold(img_gray, 0, 255, cv.THRESH_OTSU + cv.THRESH_BINARY)
cv.imwrite("image_binary.jpg", image_binary)
# 形态学膨胀
kernel = np.ones((7, 7), np.uint8)
# sure_bg = cv.morphologyEx(image_binary, cv.MORPH_CLOSE, kernel, iterations=3)
sure_bg = cv.dilate(image_binary, kernel, iterations=2)
cv.imwrite("sure_bg.jpg", sure_bg)
# 二进制非
sure_bg = cv.bitwise_not(sure_bg)
cv.imwrite("bitwise_not_sure_bg.jpg", sure_bg)
# 形态学变化,开运算
element = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3, 3))
image_binary = cv.morphologyEx(image_binary, cv.MORPH_OPEN, element)
cv.imwrite("morphologyEx_image_binary.jpg", image_binary)
# 计算前景到背景的距离
imageSC = cv.distanceTransform(image_binary, cv.DIST_L2, 5)
imageSC = imageSC.astype(np.uint8)
cv.imwrite("imageSC.jpg", imageSC)
# 归一化
imageSC = cv.normalize(imageSC, 0, 255, cv.NORM_MINMAX)
cv.imwrite("imageSC_normalize.jpg", imageSC * 255)
# 二值化
_, imageSC = cv.threshold(imageSC, 0, 255, cv.THRESH_OTSU + cv.THRESH_BINARY)
cv.imwrite("imageSC_threshold.jpg", imageSC)
return imageSC, sure_bg
rng.seed(12345)
imgPath = "./images/6.jpeg"
src = cv.imread(imgPath)
shifted = cv.pyrMeanShiftFiltering(src, 7, 15)
cv.imwrite("shift.jpg", shifted)
if src is None:
print('Could not open or find the image:')
# print('Could not open or find the image:', args.input)
exit(0)
# Show source image
cv.imshow('Source Image', src)
opening, sure_bg = process_img2(shifted)
# Show output image
cv.imshow('Background Image', sure_bg) # 背景
原始图片

mean shift 后的结果

转换为灰度图 img_gray.jpg

高斯模糊 GaussianBlur.jpg

中值滤波 medianBlur.jpg

二值化 image_binary.jpg

形态学膨胀 sure_bg.jpg

明显看出来前景变大了许多
二进制非 bitwise_not_sure_bg.jpg,前景变成了背景,作为 process_img2 函数的第二个返回值 return

基于二值化的 image_binary.jpg 进行开运算 morphologyEx_image_binary.jpg

基于二值化的 image_binary.jpg 计算前景到背景的距离,imageSC.jpg,便于计算分水岭
不乘以 255 的效果

乘上 255 后的效果

最大最小值归一化,得到 imageSC_normalize.jpg

乘以 255 后可视化的结果

二值化归一化后的结果,imageSC_threshold.jpg,作为 process_img2 函数的第一个返回值 return

# Dilate a bit the dist image
kernel1 = np.ones((3, 3), dtype=np.uint8)
dist = cv.dilate(imageSC, kernel1)
cv.imwrite("dist-dilate.jpg", dist*255)
cv.imshow('Peaks', dist)
膨胀 imageSC_threshold.jpg,得到 dist-dilate.jpg

# 构建初始markers
dist_8u = dist.astype('uint8')
# Find total markers
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
找轮廓
# 创建即将应用分水岭算法的标记图像
# markers = np.zeros(dist.shape, dtype=np.int32)
markers = sure_bg.copy().astype(np.int32)
# 标记前景
for i in range(len(contours)):
cv.drawContours(markers, contours, i, (i + 1), -1) # 轮廓标记从1开始
# 标记背景
# cv.circle(markers, (5, 5), 3, 255, -1) # 此处背景标记为255
# 可视化markers
print("before watershed: ", np.unique(markers)) # 0表示不确定标记区域
markers_8u = (markers * 10).astype('uint8')
cv.imwrite('markers_8u.jpg', markers_8u)
cv.imshow('Markers', markers_8u)
output
before watershed: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 255]
绘制轮廓 markers_8u.jpg

# 应用分水岭分割算法
markers = cv.watershed(src, markers)
print("after watershed: ", np.unique(markers)) # -1表示边界
# mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
cv.imwrite('mark.jpg', mark)
output
after watershed: [ -1 1 2 3 4 5 6 7 8 9 10 11 12 255]
分水岭算法 mark.jpg

mark = cv.bitwise_not(mark)
cv.imwrite('mark-bitwise_not.jpg', mark)
cv.imshow('Markers_v2', mark)
取反 mark-bitwise_not.jpg

# Generate random colors
colors = []
for contour in contours:
colors.append((rng.randint(0, 256), rng.randint(0, 256), rng.randint(0, 256)))
# Create the result image
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np


1159

被折叠的 条评论
为什么被折叠?



