【python】OpenCV—WaterShed Algorithm
文章目录
- 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 rngdef 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_bgrng.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
# 可视化markersprint("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 rngdef 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_bgrng.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
# 可视化markersprint("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
相关文章:
【python】OpenCV—WaterShed Algorithm
文章目录 1、功能描述2、代码实现3、完整代码4、效果展示5、涉及到的库函数5.1、cv2.pyrMeanShiftFiltering5.2、cv2.morphologyEx5.3、cv2.distanceTransform5.4、cv2.normalize5.5、cv2.watershed 6、更多例子7、参考 1、功能描述 基于分水岭算法对图片进行分割 分水岭分割…...
CSS flex布局- 最后一个元素占满剩余可用高度转载
效果图 技术要点 height父元素必须有一个设定的高度flex-grow: 1 flex 盒子模型内的该元素将会占据父容器中剩余的空间F12检查最后一行的元素,高度就已经改变了;...
Camp4-L1:XTuner 微调个人小助手认知
书生浦语大模型实战营第四期-XTuner 微调个人小助手认知 教程链接:https://github.com/InternLM/Tutorial/blob/camp4/docs/L1/XTuner/README.md任务链接:https://github.com/InternLM/Tutorial/blob/camp4/docs/L1/XTuner/task.md提交链接:…...
Qt:语言家视图
1.一不小心将qt语言家点成这样 2.点击查看->视图 3.效果...
【Paper Note】利用Boundary-aware Attention边界感知注意力机制增强部分伪造音频定位
利用Boundary-aware Attention边界感知注意力机制增强部分伪造音频定位 摘要核心模块什么是边界?什么是边界特征? 写作背景解决的问题 方法1. 特征提取使用预训练好的自监督学习模型进行前端特征提取Attentive poolingQ:为什么使用Attentive …...
海外共享奶牛牧场投资源码-理财金融源码-基金源码-共享经济源码
新版海外共享奶牛牧场投资源码/理财金融源码/基金源码/共享经济源码...
iOS静态库(.a)及资源文件的生成与使用详解(OC版本)
引言 iOS静态库(.a)及资源文件的生成与使用详解(Swift版本)_xcode 合并 .a文件-CSDN博客 在前面的博客中我们已经介绍了关于iOS静态库的生成步骤以及关于资源文件的处理,在本篇博客中我们将会以Objective-C为基础语言…...
Python自动化:关键词密度分析与搜索引擎优化
在数字营销领域,搜索引擎优化(SEO)是提升网站可见性和吸引有机流量的关键。关键词密度分析作为SEO的一个重要组成部分,可以帮助我们理解特定关键词在网页内容中的分布情况,从而优化网页内容以提高搜索引擎排名。本文将…...
苏州金龙新V系客车创新引领旅游出行未来
10月25日,为期三天的“2024第六届旅游出行大会”在风景秀丽的云南省丽江市落下帷幕。本次大会由中国旅游车船协会主办,全面展示了中国旅游出行行业最新发展动态和发展成就,为旅游行业带来全新发展动力。 在大会期间,备受瞩目的展车…...
linux:DNS服务
DNS简介: DNS系统使用的是网络的查询,那么自然需要有监听的port。DNS使用的是53端口, 在/etc/services(搜索domain)这个文件中能看到。通常DNS是以UDP这个较快速的数据传输协议来查 询的,但是没有查询到完…...
传奇架设好后创建不了行会,开区时点创建行会没反应的解决办法
传奇架设好后,测试了版本,发现行会创建不了,按道理说一般的版本在创建行会这里不会出错的,因为这是引擎自带的功能。 建立不了行会虽然说问题不大,但也不小,会严重影响玩家的游戏体验,玩游戏为的…...
【小白学机器学习28】 统计学脉络+ 总体+ 随机抽样方法
目录 参考书,学习书 0 统计学知识大致脉络 1 个体---抽样---整体 1.1 关于个体---抽样---整体,这个三段式关系 1.2 要明白,自然界的整体/母体是不可能被全部认识的 1.2.1 不要较真,如果是人为定义的一个整体,是可…...
安全研究 | 不同编程语言中 IP 地址分类的不一致性
作为一名安全研究人员,我分析了不同编程语言中 IP 地址分类 的行为。最近,我注意到一些有趣的不一致性,特别是在循环地址和私有 IP 地址的处理上。在这篇文章中,我将分享我对此问题的观察和见解。 设置 我检查了多种编程语言&am…...
小小的表盘还能玩出这么多花样?华为手表这次细节真的拉满
没想到小小的表盘还能玩出这么多花样?华为这次细节真的拉满!还有没有你不知道的神奇玩法? 情绪萌宠,心情状态抬腕可见 好心情就像生活馈赠的糖果,好的心情让我们遇到困难也不惧打击!HUAWEI WATCH GT 5情绪…...
trueNas 24.10 docker配置文件daemon.json无法修改(重启被覆盖)解决方案
前言 最近听说truenas的24.10版本开放docker容器解决方案放弃了原来难用的k3s,感觉非常巴适,就研究了一下,首先遇到无法迁移老系统应用问题比较好解决,使用sudo登录ssh临时修改daemon.json重启docker后进行docker start 容器即可…...
数字孪生,概念、应用与未来展望
随着科技的飞速发展,数字化已经成为各行各业的发展趋势,在这个过程中,数字孪生作为一种新兴的技术,逐渐引起了人们的关注,本文将对数字孪生的概念、应用以及未来展望进行详细介绍。 数字孪生的概念: 数字孪…...
Chromium HTML Input 类型Text 对应c++
一、文本域(Text Fields) 文本域通过 <input type"text"> 标签来设定,当用户要在表单中键入字母、数字等内容时,就会用到文本域。 <!DOCTYPE html> <html> <head> <meta charset"ut…...
SpringMvc参数传递
首先对于post请求汉字乱码需要进行过滤器配置 普通参数传递 直接传递 客户端传递的属性名与我的bean中的函数参数名相同 映射传递RequestParam("XXX") 在我们方法参数中定义一个与客户端属性名一致 并绑定参数 POJO实体类传递 嵌套POJO传递 数组likes参数传递…...
西安国际数字影像产业园:数字化建设赋能产业升级与拓展
西安国际数字影像产业园的数字化建设,在当前经济与科技迅猛发展的大背景下,已然成为提升园区管理效率、服务水平以及运营效果的关键趋势。随着信息技术日新月异的进步,数字化更是成为这座产业园转型升级的核心关键词。如今,西安国…...
linux线程池
线程池: * 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着 监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利 用࿰…...
PyTorch图像分类实战——基于ResNet18的RAF-DB情感识别(附完整代码和结果图)
PyTorch图像分类实战——基于ResNet18的RAF-DB情感识别(附完整代码和结果图) 关于作者 作者:小白熊 作者简介:精通python、matlab、c#语言,擅长机器学习,深度学习,机器视觉,目标检测…...
【OccNeRF: Advancing 3D Occupancy Prediction in LiDAR-Free Environments】阅读笔记
【OccNeRF: Advancing 3D Occupancy Prediction in LiDAR-Free Environments】阅读笔记 1. 论文概述Abstract1. Introduction2. Related work2.1 3D Occupancy Prediction2.2 Neural Radiance Fields2.3 Self-supervised Depth Estimation 3. Method3.1 Parameterized Occupanc…...
DDRPHY数字IC后端设计实现系列专题之后端设计导入,IO Ring设计
本章详细分析和论述了 LPDDR3 物理层接口模块的布图和布局规划的设计和实 现过程,包括设计环境的建立,布图规划包括模块尺寸的确定,IO 单元、宏单元以及 特殊单元的摆放。由于布图规划中的电源规划环节较为重要, 影响芯片的布线资…...
EDA --软件开发之路
之前一直在一家做数据处理的公司,从事c开发,公司业务稳定,项目有忙有闲,时而看下c,数据库,linux相关书籍,后面跳槽到了家eda公司,开始了一段eda开发之路。 eda 是 electric design …...
51c~目标检测~合集2
我自己的原文哦~ https://blog.51cto.com/whaosoft/12377509 一、总结 这里概述了基于深度学习的目标检测器的最新发展。同时,还提供了目标检测任务的基准数据集和评估指标的简要概述,以及在识别任务中使用的一些高性能基础架构,其还涵盖了…...
计算机低能儿从0刷leetcode | 33.搜索旋转排列数组
题目:33. 搜索旋转排序数组 思路:看到时间复杂度要求是O(log N)很容易想到二分查找,普通的二分查找我们已经掌握,本题中的数组可以看作由两个分别升序的数组拼成,在完全升序的部分中进行二分查找是容易的,…...
SpringBoot+VUE2完成WebSocket聊天(数据入库)
下载依赖 <!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- MybatisPlus --><dependency><groupId>com.ba…...
理解 CSS 中的绝对定位与 Flex 布局混用
理解 CSS 中的绝对定位与 Flex 布局混用 在现代网页设计中,CSS 布局技术如 flex 和绝对定位被广泛使用。然而,这两者结合使用时,可能会导致一些意想不到的布局问题。本文将探讨如何正确使用绝对定位元素,避免它们受到 flex 布局的…...
Redis 事务 问题
前言 相关系列 《Redis & 目录》《Redis & 事务 & 源码》《Redis & 事务 & 总结》《Redis & 事务 & 问题》 参考文献 《Redis事务详解》 Redis事务是什么? 标准的事务是指执行时具备原子性/一致性/隔离性/持久性的一系列操作。…...
Cpp学习手册-进阶学习
C标准库和C20新特性 C标准库概览: 核心库组件介绍: 容器: C 标准库提供了多种容器,它们各有特点,适用于不同的应用场景。 std::vector: vector:动态数组,支持快速随机访问。 #in…...
视频网站切片怎么做/体验式营销
毕业论文的系统的设计类图 ER模型 Student-Teacher-Paper-Topic之间的关系 学生查看课题信息 转载于:https://www.cnblogs.com/zll1028/p/8735175.html...
商城网站建设系统/深圳百度seo培训
转载于:https://www.cnblogs.com/ws410/p/10102440.html...
东坑镇仿做网站/搜索引擎关键词排名优化
为什么要页面静态化? 1.动态文件执行过程:语法分析-编译-运行 2.静态文件,不需要编译,减少了服务器脚本运行的时间,降低了服务器的响应时间,直接运行,响应速度快;如果页面中一些内容不经常改动&…...
个人电脑可以做网站服务器/万网注册域名查询官方网站
rest_framework频率组件能够限制一定时间内访问次数 rest_framework频率组件的使用 第一步,写一个类,继承SimpleRateThrottle,(根据ip限制) from rest_framework.throttling import SimpleRateThrottleclass VisitThro…...
网站开发有什么网站/app拉新推广平台
文章目录找到nki音源加载器右键打开 定位到音源文件夹即可,双击即可进入 如果提示文件缺失,找到音源所在文件夹会自动寻找 等待加载完毕,就可以调成琴键格式弹奏了...
建设银行企业网站/网络营销公司哪家好
目录 1.线程池基础知识 1.1线程池概念 1.2线程池使程序更高效的原理 2.标准库中的线程池 2.1线程池的继承关系 2.2线程池的构造方法 2.3线程池的使用 2.线程池的优点 3.线程池的模拟实现 1.线程池基础知识 1.1线程池概念 在多线程当中,并发程度会不断…...