原理
任何一副灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷。我们向每一个山谷中灌不同颜色的水。随着水的位的升高,不同山谷的水就会相遇汇合,为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝。不停的灌水,不停的构建堤坝知道所有的山峰都被水淹没。我们构建好的堤坝就是对图像的分割。这就是分水岭算法的背后哲理。
分水岭算法是一种图像分割算法,常用于分割具有重叠目标的图像。它基于数学形态学的理念,将图像看作地形图,水流在图像的低谷(目标边界)聚集,形成分割线。OpenCV 提供了 cv2.watershed()
函数来执行分水岭算法。
基本的语法如下:
cv2.watershed(image, markers)
参数说明:
image
: 输入的图像,通常是一个三通道彩色图像。markers
: 标记图像,用于指定分水岭算法的初始标记。标记图像应该是单通道灰度图像,其中不同的标记值表示不同的区域。
cv2.watershed
函数会修改输入图像,将标记图像中的区域分割开,并用不同的颜色标记不同的分割区域。分水岭算法通常用于从预先标记的图像中分割出目标区域。
以下是一个简单的示例,演示如何使用分水岭算法进行图像分割:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread(r"C:\Users\mzd\Desktop\opencv\4.jpg")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用阈值分割获取前景区域掩码
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
#cv2.threshold 是 OpenCV 中用于图像阈值化的函数。阈值化是一种将图像分割成两个区域的方法,通常用于目标检测、边缘检测等应用。
# 对前景区域进行形态学操作,消除小的噪点
kernel = np.ones((3, 3), np.uint8)
#np.ones 是 NumPy 中的一个函数,用于创建一个指定形状(shape)的数组,并将数组的所有元素初始化为 1
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 通过距离变换获取图像的距离变换结果
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
_, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
# 获取背景区域掩码
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# 标记不确定的区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
# 使用连通组件标记不确定区域
_, markers = cv2.connectedComponents(sure_fg)
# 将标记+1作为不确定区域的标记,以避免与已知区域的标记冲突
markers = markers + 1
markers[unknown == 255] = 0 # 不确定区域的标记设为0
# 应用分水岭算法
cv2.watershed(img, markers)
# 将分水岭算法的标记区域设为红色
img[markers == -1] = [255, 0, 0]
# 显示原始图像、阈值分割结果和分水岭算法结果
plt.figure(figsize=(12, 6))
plt.subplot(131), plt.imshow(img_rgb)
plt.title('Original Image'), plt.axis('off')
plt.subplot(132), plt.imshow(thresh, cmap='gray')
plt.title('Thresholded Image'), plt.axis('off')
plt.subplot(133), plt.imshow(img)
plt.title('Watershed Result'), plt.axis('off')
plt.show()
在这个示例中,我们首先读取了一幅图像,将其转换为灰度图像。然后,使用阈值分割获取前景区域的掩码,并通过形态学操作和距离变换对图像进行预处理。接着,通过分水岭算法将图像分割为不同的区域,并在结果图像上标记分割线。最后,通过 Matplotlib 显示原始图像、阈值分割结果和分水岭算法的分割结果。