【python】OpenCV—WaterShed Algorithm

在这里插入图片描述

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值