【python】OpenCV—WaterShed Algorithm

news2024/11/24 20:23:28

在这里插入图片描述

文章目录

  • 1、功能描述
  • 2、代码实现
  • 3、完整代码
  • 4、效果展示
  • 5、涉及到的库函数
    • 5.1、cv2.pyrMeanShiftFiltering
    • 5.2、cv2.morphologyEx
    • 5.3、cv2.distanceTransform
    • 5.4、cv2.normalize
    • 5.5、cv2.watershed
  • 6、更多例子
  • 7、参考

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.uint8)
# Fill labeled objects with random colors
for i in range(markers.shape[0]):
    for j in range(markers.shape[1]):
        index = markers[i, j]
        if index > 0 and index <= len(contours):  # -1表示边界, 255表示背景
            dst[i, j, :] = colors[index - 1]
# Visualize the final image
cv.imshow('Final Result', dst)
cv.imwrite('Final-Result.jpg', dst)
cv.waitKey(0)
cv.destroyAllWindows()

绘制 Final-Result.jpg

在这里插入图片描述

3、完整代码

输入图片

在这里插入图片描述

实现一,也即前面章节所描述的方法

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)
    cv.imwrite("imageSC255.jpg", imageSC*255)

    # 归一化
    cv.normalize(imageSC, imageSC, 0, 255, cv.NORM_MINMAX)
    cv.imwrite("imageSC_normalize.jpg", imageSC)
    cv.imwrite("imageSC_normalize255.jpg", imageSC*255)

    # 二值化
    # _, imageSC = cv.threshold(imageSC, 0, 255, cv.THRESH_OTSU + cv.THRESH_BINARY)
    _, imageSC = cv.threshold(imageSC, 0.3, 1.0, cv.THRESH_BINARY)
    cv.imwrite("imageSC_threshold.jpg", imageSC*255)
    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)

imageSC, sure_bg = process_img2(shifted)
# Show output image
cv.imshow('Background Image', sure_bg)  # 背景

# 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)

# 构建初始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)

# 应用分水岭分割算法
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)

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

# 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.uint8)
# Fill labeled objects with random colors
for i in range(markers.shape[0]):
    for j in range(markers.shape[1]):
        index = markers[i, j]
        if index > 0 and index <= len(contours):  # -1表示边界, 255表示背景
            dst[i, j, :] = colors[index - 1]
# Visualize the final image
cv.imshow('Final Result', dst)
cv.imwrite('Final-Result.jpg', dst)
cv.waitKey(0)
cv.destroyAllWindows()

在这里插入图片描述


实现二,感觉这套前处理少一些

import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\
    Sample code showing how to segment overlapping objects using Laplacian filtering, \
    in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='./images/6.jpeg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
    print('Could not open or find the image:', args.input)
    exit(0)
# Show source image
cv.imshow('Source Image', src)

# 转灰度
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# noise removal,开运算
kernel = np.ones((5, 5), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)

# 获取背景图
sure_bg = opening.copy()  # 背景
# Show output image
cv.imshow('Black Background Image', sure_bg)  # 黑色是背景

# 获取前景图
dist = cv.distanceTransform(opening, cv.DIST_L2, 3)
# Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)
_, dist = cv.threshold(dist, 0.2, 1.0, cv.THRESH_BINARY)
# Dilate a bit the dist image
kernel1 = np.ones((3, 3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)
cv.imshow('Peaks', dist)

# 构建初始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)
# 标记前景
for i in range(len(contours)):
    cv.drawContours(markers, contours, i, (i + 1), -1)  # 轮廓标记从1开始
# 标记背景
cv.circle(markers, (5, 5), 3, 255, -1)  # 此处背景标记为255
print("before watershed: ", np.unique(markers))  # 0表示不确定标记区域
# 可视化markers
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)

# 应用分水岭分割算法
markers = cv.watershed(src, markers)
print("after watershed: ", np.unique(markers))  # -1表示边界

# mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
mark = cv.bitwise_not(mark)
# uncomment this if you want to see how the mark
# image looks like at that point
# cv.imshow('Markers_v2', mark)
# 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.uint8)
# Fill labeled objects with random colors
for i in range(markers.shape[0]):
    for j in range(markers.shape[1]):
        index = markers[i, j]
        if index > 0 and index <= len(contours):  # -1表示边界, 255表示背景
            dst[i, j, :] = colors[index - 1]
# Visualize the final image
cv.imshow('Final Result', dst)
cv.waitKey()

在这里插入图片描述

4、效果展示

输入

在这里插入图片描述

法二

在这里插入图片描述

输入

在这里插入图片描述

法二

在这里插入图片描述

输入

在这里插入图片描述
法二
在这里插入图片描述

5、涉及到的库函数

5.1、cv2.pyrMeanShiftFiltering

cv2.pyrMeanShiftFiltering 是 OpenCV 中用于图像平滑处理的一个函数,它基于均值漂移(Mean Shift)算法,并通过图像金字塔的方式来实现。这种滤波方法对于去除图像中的噪声和细节纹理非常有效,同时能够保留图像的边缘信息。

一、函数原型

cv2.pyrMeanShiftFiltering(src, dst, sp, sr, maxLevel=1, termcrit=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_MAX_ITER, 5, 1))

二、参数解释

  • src: 输入图像,应该是一个 8 位或 16 位的单通道或三通道图像。
  • dst: 输出图像,与输入图像具有相同的类型和大小。
  • sp: 空间窗口的半径,它决定了在进行均值漂移计算时考虑的邻域大小。
  • sr: 颜色窗口的半径,它决定了在颜色空间中考虑的邻域大小。
  • maxLevel: 金字塔的最大层数。默认值为 1,表示只处理原始图像,不进行金字塔分解。增加层数可以在更粗的尺度上进行滤波,但计算量也会增加。
  • termcrit: 迭代过程的终止条件。它是一个元组,包含三个元素:终止条件的类型、最大迭代次数和所需满足的精度。默认值是 (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_MAX_ITER, 5, 1),意味着迭代将在达到最大迭代次数 5 或满足精度 1 时停止。

三、使用示例

import cv2  
import numpy as np  
  
# 读取图像  
image = cv2.imread('example.jpg')  
  
# 使用 pyrMeanShiftFiltering 进行滤波  
filtered_image = cv2.pyrMeanShiftFiltering(image, None, 21, 31)  
  
# 显示结果  
cv2.imshow('Original Image', image)  
cv2.imshow('Filtered Image', filtered_image)  
cv2.waitKey(0)  
cv2.destroyAllWindows()

在这个例子中,我们读取了一张名为 example.jpg 的图像,然后使用 cv2.pyrMeanShiftFiltering 函数对其进行滤波处理。其中,空间窗口的半径设置为 21,颜色窗口的半径设置为 31。处理后的图像将显示在窗口中。

四、注意事项

  • cv2.pyrMeanShiftFiltering 函数在计算上可能比较耗时,特别是对于大图像和较大的窗口半径。
  • 正确地选择空间窗口和颜色窗口的半径对于获得良好的滤波效果至关重要。
  • 滤波后的图像可能会看起来更加平滑,但一些细节信息可能会丢失。

5.2、cv2.morphologyEx

cv2.morphologyEx 是 OpenCV 中用于执行形态学变换的函数。形态学变换是一种基于图像形状的图像处理技术,可以用于提取图像中的特定结构或特征,如边界、骨架、凸包等。这些变换基于图像的集合表示,通过定义一些基本的操作(如腐蚀、膨胀、开运算、闭运算等)来实现对图像的处理。

一、函数原型

cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])

二、参数解释

  • src: 输入图像,必须是单通道的灰度图像或二值图像。
  • op: 形态学变换的类型,可以是以下几种之一:
    • cv2.MORPH_ERODE: 腐蚀操作,使图像中的白色区域缩小,黑色区域扩大。
    • cv2.MORPH_DILATE: 膨胀操作,使图像中的白色区域扩大,黑色区域缩小。
    • cv2.MORPH_OPEN: 开运算,先进行腐蚀再进行膨胀,可以去除图像中的小物体或噪声。
    • cv2.MORPH_CLOSE: 闭运算,先进行膨胀再进行腐蚀,可以填充图像中的小孔或连接邻近的物体。
    • cv2.MORPH_GRADIENT: 形态学梯度,表示膨胀图像与腐蚀图像之差,用于突出图像中的边缘。
    • cv2.MORPH_TOPHAT: 顶帽变换,原图像减去膨胀后的图像,用于分离比邻近点亮一些的斑点。
    • cv2.MORPH_BLACKHAT: 黑帽变换,膨胀后的图像减去原图像,用于分离比邻近点暗一些的斑点。
    • cv2.MORPH_HITMISS: 结构元素对应的点集比较,用于检测图像中的特定模式。
  • kernel: 形态学变换的核,通常是一个矩形、椭圆或十字形的小矩阵。核的大小和形状会影响变换的效果。
  • dst: 输出图像,如果未指定,则函数会创建一个新的输出图像。
  • anchor: 核的锚点,默认是核的中心。锚点决定了核在图像上移动时的参考点。
  • iterations: 变换的次数,默认值为 1。增加迭代次数可以增强变换的效果。
  • borderType: 边界像素的外推方法,默认值为 cv2.BORDER_CONSTANT。
  • borderValue: 使用 cv2.BORDER_CONSTANT 时边界的像素值,默认值为 0。

三、使用示例

下面是一个简单的使用示例,演示了如何使用 cv2.morphologyEx 函数进行腐蚀和膨胀操作:

import cv2  
import numpy as np  
  
# 读取图像  
image = cv2.imread('example.png', 0)  # 读取为灰度图像  
  
# 定义核  
kernel = np.ones((5, 5), np.uint8)  
  
# 腐蚀操作  
eroded_image = cv2.morphologyEx(image, cv2.MORPH_ERODE, kernel)  
  
# 膨胀操作  
dilated_image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel)  
  
# 显示结果  
cv2.imshow('Original Image', image)  
cv2.imshow('Eroded Image', eroded_image)  
cv2.imshow('Dilated Image', dilated_image)  
cv2.waitKey(0)  
cv2.destroyAllWindows()

在这个例子中,我们读取了一张名为 example.png 的灰度图像,然后定义了一个 5x5 的矩形核。接着,我们使用 cv2.morphologyEx 函数分别进行了腐蚀和膨胀操作,并将结果显示在窗口中。

四、注意事项

  • 形态学变换的效果取决于核的大小和形状,以及变换的类型。
  • 腐蚀操作会使图像中的白色区域缩小,而膨胀操作会使白色区域扩大。
  • 开运算和闭运算是腐蚀和膨胀的组合操作,可以用于去除小物体、填充小孔或连接邻近物体。
  • 在使用形态学变换时,需要注意选择合适的核大小和形状,以及变换的次数,以获得最佳的处理效果。

5.3、cv2.distanceTransform

cv2.distanceTransform 是 OpenCV 库中的一个函数,用于计算图像中每个非零像素点到其最近的零像素点的距离。这个函数在处理二值图像时特别有用,尤其是在图像分割、形态学操作以及目标检测等任务中。

一、函数原型

cv2.distanceTransform(src, distanceType=cv2.DIST_L2, maskSize=5)
  • src: 输入的8位二值图像,通常为单通道图像。非零像素被视为前景(对象),而零像素被视为背景。
  • distanceType: 距离类型,它决定了如何计算距离。常用的选项有:
    • cv2.DIST_L1: 使用L1范数(城市街区距离)。
    • cv2.DIST_L2: 使用L2范数(欧几里得距离),这是默认值。
    • cv2.DIST_C: 使用Chebyshev距离。
  • maskSize: 距离变换掩码的大小,必须是正奇数。默认值为5。掩码越大,计算出的距离越精确,但计算成本也越高。

二、返回值

该函数返回一个与输入图像大小相同的图像,但数据类型为32位浮点数图像中的每个像素值代表了该像素点到最近的零像素点的距离

三、使用示例

import cv2  
import numpy as np  
  
# 创建一个简单的二值图像  
image = np.zeros((10, 10), dtype=np.uint8)  
image[3:7, 3:7] = 1  # 在图像中心创建一个4x4的白色方块  
  
# 应用距离变换  
dist_transform = cv2.distanceTransform(image, cv2.DIST_L2, 5)  
  
# 打印结果  
print(image)
print(dist_transform)

output

[[0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 1 1 1 1 0 0 0]
 [0 0 0 1 1 1 1 0 0 0]
 [0 0 0 1 1 1 1 0 0 0]
 [0 0 0 1 1 1 1 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]]
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 1. 1. 1. 0. 0. 0.]
 [0. 0. 0. 1. 2. 2. 1. 0. 0. 0.]
 [0. 0. 0. 1. 2. 2. 1. 0. 0. 0.]
 [0. 0. 0. 1. 1. 1. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

在这个示例中,我们首先创建了一个10x10的二值图像,其中中心有一个4x4的白色方块。然后,我们对这个图像应用了距离变换,并打印出结果。结果图像中的每个像素值代表了该像素点到最近的零像素(即背景)的距离。

四、应用场景

  • 图像分割:在图像分割任务中,可以通过距离变换来确定前景和背景之间的边界。
  • 形态学操作:距离变换可以用于形态学梯度、膨胀和腐蚀等高级形态学操作的基础。
  • 目标检测:在目标检测中,距离变换可以帮助识别目标物体的轮廓和形状。

5.4、cv2.normalize

cv2.normalize 是 OpenCV 库中的一个函数,用于对数组(通常是图像)进行归一化处理。归一化是指将数据按比例缩放,使之落入一个小的特定区间,通常是[0, 1]或[-1, 1]。这种处理对于图像预处理、特征提取和比较等任务非常重要,因为它可以帮助改善算法的收敛速度和性能,或者满足某些特定算法对数据范围的要求。

一、基本语法

cv2.normalize(src, dst=None, alpha=None, beta=None, norm_type=cv2.NORM_MINMAX, dtype=-1, mask=None)

二、参数解释

  • src: 输入数组(图像),可以是任意深度的,但通常是8位或32位浮点数。
  • dst: 输出数组,与输入数组具有相同的形状和深度。如果为None,则函数会创建一个具有适当大小和类型的数组。
  • alpha: 归一化后的范围下限(通常用于NORM_MINMAX和NORM_INF类型)。对于NORM_MINMAX,这个值表示归一化后的最小值。
  • beta: 归一化后的范围上限(同样用于NORM_MINMAX和NORM_INF类型)。对于NORM_MINMAX,这个值表示归一化后的最大值。
  • norm_type: 归一化类型。OpenCV提供了几种不同的归一化类型,如 cv2.NORM_MINMAX(将数组缩放到指定范围)、cv2.NORM_L2(L2范数归一化)等。
  • dtype: 输出数组的可选深度。当参数为负值时(如-1),输出数组与输入数组具有相同的深度。
  • mask: 可选的操作掩码,用于指定哪些元素需要被归一化。掩码应该是与输入数组形状相同的单通道数组,其中非零元素表示对应的输入元素需要被处理。

三、使用示例

import cv2  
import numpy as np  
  
# 创建一个简单的图像(二维数组)  
image = np.array([[10, 20, 30], [40, 50, 60]], dtype=np.float32)  
  
# 使用cv2.normalize进行归一化处理  
normalized_image = cv2.normalize(image, None, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)  
  
print("归一化后的图像:")  
print(normalized_image)

output

归一化后的图像:
[[0.         0.19999999 0.39999998]
 [0.59999996 0.8        0.99999994]]

在这个例子中,我们将一个二维数组(模拟一个简单的图像)进行了归一化处理,将其值缩放到[0, 1]范围内。这种处理对于图像处理中的许多任务都是非常有用的。

5.5、cv2.watershed

cv2.watershed 是OpenCV库中用于图像分割的一个函数,它实现了基于标记的分水岭算法。分水岭算法是一种图像分割技术,特别适用于从图像中分离出触摸或重叠的对象。

一、函数原型

cv2.watershed(image, markers) -> int, output markers
  • image:输入图像,应该是8位或浮点类型的三通道图像。
  • markers:输入/输出标记数组,应该是32位单通道图像。在输入时,标记数组应该包含已知的前景和背景标记。在输出时,函数将修改这个数组,为每个分割的区域分配不同的标签,并将边界区域标记为-1

二、使用步骤

1、读取和预处理图像:

  • 使用 cv2.imread 读取图像。
  • 如果图像是彩色的,可以转换为灰度图像(使用 cv2.cvtColor)。
  • 应用阈值处理(使用 cv2.threshold)或边缘检测(如Canny边缘检测)来生成二值图像。

2、确定前景和背景标记:

  • 使用形态学操作(如膨胀和腐蚀)来增强或修正边缘。
  • 查找二值图像中的连通组件(使用 cv2.findContours),并为每个组件分配一个唯一的标记。
  • 将背景标记为0,前景标记为正整数。

3、应用分水岭算法:

  • 调用 cv2.watershed 函数,传入预处理后的图像和标记数组。
  • 函数将修改标记数组,为每个分割的区域分配不同的标签。

4、分析结果:

  • 查看修改后的标记数组,了解哪些像素被分配到了哪些区域。
  • 使用这些信息在原图上绘制分割边界或进行其他分析。

三、注意事项

  • 分水岭算法的效果很大程度上依赖于预处理步骤和标记的正确性。
  • 过度分割是一个常见问题,可以通过调整预处理步骤的参数或结合其他分割技术来减轻。
  • 在使用分水岭算法之前,通常需要确保图像中的对象之间有清晰的边界或分隔。

四、示例代码

以下是一个简单的示例代码,演示了如何使用 cv2.watershed 函数进行图像分割:

import cv2  
import numpy as np  
  
# 读取图像  
image = cv2.imread('your_image.png')  
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  
  
# 应用阈值处理  
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)  
  
# 查找轮廓并创建标记数组  
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  
markers = np.zeros(gray.shape, dtype=np.int32)  
  
# 为每个轮廓分配一个唯一的标记(从1开始)  
for i, contour in enumerate(contours):  
    cv2.drawContours(markers, [contour], -1, (i + 1), -1)  
  
# 应用分水岭算法  
markers = cv2.watershed(image, markers)  
  
# 绘制分割边界  
image[markers == -1] = [0, 0, 255]  # 将边界设置为红色  
  
# 显示结果  
cv2.imshow('Segmented Image', image)  
cv2.waitKey(0)  
cv2.destroyAllWindows()

请注意,上述示例代码是一个简单的演示,实际应用中可能需要根据具体情况进行调整和优化。

6、更多例子

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\
    Sample code showing how to segment overlapping objects using Laplacian filtering, \
    in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='7.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
    print('Could not open or find the image:', args.input)
    exit(0)

# 显示原图像
cv.imshow('Source Image', src)
src[np.all(src == 255, axis=2)] = 0

# 显示输出图像
cv.imshow('Black Background Image', src)

kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
# // 二阶导数的近似,一个相当强的核可以很好地进行拉普拉斯滤波
# //  我们需要float类型来转换所有的东西,因为核有一些负值,我们期望得到一个负的拉普拉斯像
# // 但是一个8位无符号整数(我们正在处理的)可以包含0到255的值, 所以可能的负数会被截断
imgLaplacian = cv.filter2D(src, cv.CV_32F, kernel)
sharp = np.float32(src)
imgResult = sharp - imgLaplacian

# 转换回8位灰度
imgResult = np.clip(imgResult, 0, 255)
imgResult = imgResult.astype('uint8')
imgLaplacian = np.clip(imgLaplacian, 0, 255)
imgLaplacian = np.uint8(imgLaplacian)
#cv.imshow('Laplace Filtered Image', imgLaplacian)
cv.imshow('New Sharped Image', imgResult)

# 现在我们将锐化图像分别转换为灰度和二值图像:
bw = cv.cvtColor(imgResult, cv.COLOR_BGR2GRAY)
_, bw = cv.threshold(bw, 40, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow('Binary Image', bw)

# 现在我们准备对二值图像应用距离变换。此外,我们对输出图像进行了归一化,以便能够对结果进行可视化和阈值操作:
dist = cv.distanceTransform(bw, cv.DIST_L2, 3)

# 将距离归一化到{0.0, 1.0},这样我们就可以可视化并设定阈值
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)

_, dist = cv.threshold(dist, 0.4, 1.0, cv.THRESH_BINARY)
# 对dist图像进行膨胀操作
kernel1 = np.ones((3,3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)
cv.imshow('Peaks', dist)

dist_8u = dist.astype('uint8')

# 发现所有标记
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

# 为分水岭算法创建标记图像
markers = np.zeros(dist.shape, dtype=np.int32)

# 绘制前景标记
for i in range(len(contours)):
    cv.drawContours(markers, contours, i, (i+1), -1)
    
# 绘制背景标记
cv.circle(markers, (5,5), 3, (255,255,255), -1)
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)
cv.watershed(imgResult, markers)

#mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
mark = cv.bitwise_not(mark)
# // 如果您想查看标记图像此时的样子,请取消注释
#cv.imshow('Markers_v2', mark)
# 生成随机颜色
colors = []
for contour in contours:
    colors.append((rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)))
# 创建结果图像
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# 用随机的颜色填充已标记的物体
for i in range(markers.shape[0]):
    for j in range(markers.shape[1]):
        index = markers[i,j]
        if index > 0 and index <= len(contours):
            dst[i,j,:] = colors[index-1]
# 可视化最终结果
cv.imshow('Final Result', dst)
cv.waitKey()

输入

在这里插入图片描述

输出

在这里插入图片描述

7、参考

  • 基于标记的分水岭分割算法
  • https://anothertechs.com/programming/cpp/opencv/opencv-watershed/
  • 基于距离变换和分水岭算法的图像分割
  • https://docs.opencv.org/4.x/d2/dbd/tutorial_distance_transform.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2228878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务设计模式 - 特性标志(Feature Flags)

微服务设计模式 - 特性标志&#xff08;Feature Flags&#xff09; 定义 特性标志&#xff08;Feature Flags&#xff09;&#xff0c;又称特性开关&#xff08;Feature Toggles&#xff09;&#xff0c;是一种常见的云计算设计模式&#xff0c;允许开发人员通过配置动态地打开…

WebStorm EsLint报红色波浪线

如图左侧。 这个错误是由于 ESLint 和 Prettier 的配置不一致导致的。它建议你移除多余的空格。以下是一些解决方法&#xff1a; 安装 Prettier 插件&#xff1a; 确保你在 WebStorm 中安装了 Prettier 插件&#xff0c;并确保它配置正确。 调整 ESLint 配置&#xff1a; 检查…

四、k8s快速入门之Kubernetes资源清单

kubernetes中的资源 ⭐️ k8s中所有的内容都抽象为资源&#xff0c;资源实列化之后&#xff0c;叫做对象 1️⃣名称空间级别 ⭐️ kubeadm在执行k8s的pod的时候会在kube-system这个名称空间下执行&#xff0c;所以说当你kubectl get pod 的时候是查看不到的查看的是默认的po…

数据库->数据库约束

目录 一、数据库约束 1.定义 2.约束类型 3.NOT NULL 非空约束 4. UNIQUE 唯一约束 5.PRIMARY KEY 主键约束 1.主键的使用 2.把表中的主键交给数据库自己维护 2.1主键列设置为null 则使用自增 2.2插入除了主键以外的所有非空列&#xff08;推荐方法&#xff09; 2.3自…

Kafka相关API开发

(一)引入依赖 用API直接去操作kafka(读写数据)在实际开发中用的并不多&#xff0c;学习它主要还是为了加深对Kafka功能的理解。kafka的读写操作&#xff0c;实际开发中&#xff0c;是通过各类更上层的组件去实现。而这些组件在读写kafka数据时&#xff0c;用的当然是kafka的jav…

【K8S系列】Kubernetes 中 NodePort 类型的 Service 无法访问的问题【已解决】

在 Kubernetes 中&#xff0c;NodePort 类型的 Service 允许用户通过每个节点的 IP 地址和指定的端口访问应用程序。如果 NodePort 类型的 Service 无法通过节点的 IP 地址和指定端口进行访问&#xff0c;可能会导致用户无法访问应用。本文将详细分析该问题的常见原因及其解决方…

如何使用AdsPower指纹浏览器克服爬虫技术限制,安全高效进行爬虫!

随着中国开发者日益成熟&#xff0c;应用质量明显提升&#xff0c;越来越多的开发者选择出海寻找机会扩大市场。但“应用出海”说起来容易&#xff0c;做起来难。其中&#xff0c;最大的困恼就是对海外市场缺乏了解。 很多开发者会选择使用网络爬虫&#xff08;Web Crawling&a…

centos7之LVS-DR模式传统部署

介绍 优缺点以及适用场景 优点&#xff1a;能负载更多的Realserver减轻LB的压力,性能高于tun模式。 缺点&#xff1a;不支持端口转发(VIP:80必须代理RIP:80),Realserver和LVS需要在同一网段下。 适用&#xff1a;适用于大多数公司&#xff0c;也是大多数公司用的最多的模式。…

爬虫+数据保存2

爬取数据保存到MySQL数据库 这篇文章, 我们来讲解如何将我们爬虫爬取到的数据, 进行保存, 而且是把数据保存到MySQL数据库的方式去保存。 目录 1.使用pymysql连接数据库并执行插入数据sql代码(insert) 2.优化pymysql数据库连接以及插入功能代码 3.爬取双色球网站的数据并保…

什么样的工程项目管理软件适合中小施工企业?

工程行业是典型的传统行业&#xff0c;劳动密集&#xff0c;协作频繁&#xff0c;依赖经验传承。在工程项目施工过程中&#xff0c;常见的难题纷繁复杂&#xff0c;其中包括效率低下、材料浪费、数据不实、原材料成本上涨、工期延误、质量缺陷和安全风险等。这些问题不仅阻碍了…

机器学习中的嵌入是什么?

一、说明 嵌入是真实世界对象的数字表示&#xff0c;机器学习&#xff08;ML&#xff09;和人工智能&#xff08;AI&#xff09;系统利用它来像人类一样理解复杂的知识领域。例如&#xff0c;计算算法了解 2 和 3 之间的差为 1&#xff0c;这表明与 2 和 100 相比&#xff0c;2…

NVR设备ONVIF接入平台EasyCVR视频融合平台智慧小区视频监控系统建设方案

一、方案背景 智慧小区构成了“平安城市”建设的基石。随着社会的进步&#xff0c;社区安全问题逐渐成为公众关注的热点。诸如高空抛物、乱丢垃圾、破坏车辆、入室盗窃等不文明行为和违法行为频繁出现。目前&#xff0c;许多小区的物业管理和安全防护系统仍然较为简单和陈旧&a…

Typora一款极简Markdown文档编辑器和阅读器,实时预览,序列号生成!免费!最新可用!

文章目录 一、Typora下载和安装二、Typora序列号生成 Typora是一款Markdown编辑器和阅读器&#xff0c;风格极简&#xff0c;实时预览&#xff0c;所见即所得&#xff0c;支持MacOS、Windows、Linux操作系统&#xff0c;有图片和文字、代码块、数学公式、图表、目录大纲、文件管…

uniapp的video视频属性打包app后层级过高

问题&#xff1a;在使用uniapp开发APP时&#xff0c;使用video标签显示视频发现H5可以正常展示&#xff0c;但是打包到APP后&#xff0c;它的层级过高&#xff0c;把底部导航都盖住了。 官网说明&#xff1a;uni-app官网 官网给了cover-view组件或plus.nativeObj.view、subNVue…

人工智能原理实验一:知识的表示与推理实验

一、实验目的 本实验课程是计算机、智能、物联网等专业学生的一门专业课程&#xff0c;通过实验&#xff0c;帮助学生更好地掌握人工智能相关概念、技术、原理、应用等&#xff1b;通过实验提高学生编写实验报告、总结实验结果的能力&#xff1b;使学生对智能程序、智能算法等有…

混凝土裂缝图像分割系统:快速图像识别

混凝土裂缝图像分割系统源码&#xff06;数据集分享 [yolov8-seg-C2f-RFAConv&#xff06;yolov8-seg-C2f-SCConv等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Glo…

不再输入单号查快递,批量查快递单号信息的新方法,智能排序快递时效并查找时效相同的单号,一站式物流查询解决方案

厌倦了逐个输入快递单号查询物流信息的繁琐过程&#xff1f;想要一键就能批量查询快递单号&#xff0c;并且智能排序快递时效&#xff0c;轻松查找时效相同的单号&#xff1f;那么&#xff0c;恭喜你&#xff0c;你即将解锁快递查询的新境界&#xff01;快递批量查询高手软件&a…

国标GB28181设备管理软件EasyGBS国标GB28181公网平台应用到“雪亮工程”

随着信息技术的飞速发展&#xff0c;视频监控领域正经历从传统安防向智能化、网络化安防的深刻转变。在这一变革中&#xff0c;国标GB28181设备管理软件EasyGBS凭借其强大的功能和广泛的应用场景&#xff0c;成为推动这一转变的重要力量。特别是在“雪亮工程”这一重要的群众性…

Redis 哨兵 总结

前言 相关系列 《Redis & 目录》《Redis & 哨兵 & 源码》《Redis & 哨兵 & 总结》《Redis & 哨兵 & 问题》 参考文献 《Redis的主从复制和哨兵机制详解》《Redis中的哨兵&#xff08;Sentinel&#xff09;》《【Redis实现系列】Sentinel自动故…

springboot使用配置类从 application.yml 或 application.properties 文件中读取静态属性

springboot使用配置类从 application.yml 或 application.properties 文件中读取静态属性 1. 配置类定义 通过 ConfigurationProperties(prefix “data-base-check”)&#xff0c;Spring Boot 将带有 data-base-check 前缀的属性从 application.yml 或 application.propertie…