【python】OpenCV—WaterShed Algorithm
文章目录
1、功能描述
基于分水岭算法对图片进行分割
分水岭分割算法(WaterShed Algorithm),是一种基于拓扑理论的数学形态学的分割方法,广泛应用于数学、图像学和电子信息学领域。
一、算法原理
分水岭分割算法的基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。
分水岭的概念和形成可以通过模拟浸入过程来说明:在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
二、算法步骤
分水岭算法的计算过程是一个迭代标注过程,主要包括排序和淹没两个步骤。
分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点即为分水岭。
三、应用场景
四、优缺点及改进方法
优点:
缺点:
改进方法:
五、示例
在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))
二、参数解释
三、使用示例
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]]]]])
二、参数解释
三、使用示例
下面是一个简单的使用示例,演示了如何使用 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 的灰度图像,然后定义了一个 5×5 的矩形核。接着,我们使用 cv2.morphologyEx
函数分别进行了腐蚀和膨胀操作,并将结果显示在窗口中。
四、注意事项
5.3、cv2.distanceTransform
cv2.distanceTransform 是 OpenCV 库中的一个函数,用于计算图像中每个非零像素点到其最近的零像素点的距离。这个函数在处理二值图像时特别有用,尤其是在图像分割、形态学操作以及目标检测等任务中。
一、函数原型
cv2.distanceTransform(src, distanceType=cv2.DIST_L2, 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.]]
在这个示例中,我们首先创建了一个10×10的二值图像,其中中心有一个4×4的白色方块。然后,我们对这个图像应用了距离变换,并打印出结果。结果图像中的每个像素值代表了该像素点到最近的零像素(即背景)的距离。
四、应用场景
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)
二、参数解释
cv2.NORM_MINMAX
(将数组缩放到指定范围)、cv2.NORM_L2
(L2范数归一化)等。三、使用示例
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
二、使用步骤
1、读取和预处理图像:
2、确定前景和背景标记:
3、应用分水岭算法:
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、参考
作者:bryant_meng