1192 字
6 分钟
OpenCV:图像分割
1. 分水岭算法
在图像处理的过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来。
分水岭算法将图像形象地比喻为地理学上的地形表面,实现图像分割。任何一幅灰度图像,都可以被看作是地理学上的地形表面:灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷。
2. 使用方法
使用函数cv2.watershed()实现分水岭算法,期间需要借助形态学函数、距离变换函数cv2.distanceTransform()、cv2.connectedComponents()完成图像分割。
2.1 形态学操作
开运算
开运算是先腐蚀、后膨胀的操作,开运算能够去除图像内的噪声,如毛刺、噪点等。
import cv2import numpy as np
J = cv2.imread("J.png")kernel = np.ones((5, 5), np.uint8)J_open = cv2.morphologyEx(src=J, op=cv2.MORPH_OPEN, kernel=kernel)
cv2.imwrite("J_open.png", J_open)| 毛刺图 | 开运算:去毛刺 |
|---|---|
![]() | ![]() |
获取图像边界
通过膨胀图像减去腐蚀图像,能够获取图像的边界。
import cv2import numpy as np
J = cv2.imread("J_open.png")kernel = np.ones((3, 3), np.uint8)J_gradient = cv2.morphologyEx(src=J, op=cv2.MORPH_GRADIENT, kernel=kernel)
cv2.imwrite("J_gradient.png", J_gradient)| 原图 | 形态学梯度运算 |
|---|---|
![]() | ![]() |
2.2 距离变换、前景提取
距离变换函数cv2.distanceTransform()计算二值图像内任意点到最近背景点的距离,其计算结果反映了各个像素与背景的距离关系。通常情况下:
-
如果前景对象的中心距离值为0的像素点距离较远,会得到一个较大的值。
-
如果前景对象的边缘距离值为0的像素点距离较近,会得到一个较小的值。
-
函数原型:
dst = cv2.distanceTransform(src, distanceType, maskSize, dstType ) -
参数说明:
- src:8位、单通道的二值图像。
- distanceType:距离类型参数。
- cv2.DIST_USER:用户自定义距离
- cv2.DIST_L1: L-1范数,街区距离,
- cv2.DIST_L2:L-2范数,欧式距离,
- cv2.DIST_C: L-∞范数,棋盘距离,
- maskSize:掩模的尺寸,当distanceType为cv2.DIST_L1或cv2.DIST_C时,maskSize强制为3。
- dstType :目标图像的类型,默认为CV_32F。
- dst :目标图像,可以是8位或32位的浮点数据,尺寸和src相同。
import cv2import numpy as npimport matplotlib.pyplot as plt
# 灰度处理img = cv2.imread("coins.jpg")img_gray = img[:,:,2]
cv2.imwrite("coins_gray.png", img_gray)
# 二值化处理、形态学处理ret, img_bw = cv2.threshold(img_gray, 139, 255, cv2.THRESH_BINARY_INV)kernel = np.ones((5, 5), np.uint8)img_morph = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, kernel, iterations=1)img_morph = cv2.morphologyEx(img_morph, cv2.MORPH_OPEN, kernel, iterations=1)
cv2.imwrite("coins_morph.png", img_morph)
# 距离变换dist_transform = cv2.distanceTransform(img_morph, cv2.DIST_L2, 0)
plt.figure(figsize=(10,10))plt.imshow(dist_transform)plt.axis('off')plt.savefig("coins_dist_transform.png")
# 前景提取ret, foreground = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
plt.figure(figsize=(10,10))plt.imshow(foreground)plt.axis('off')plt.savefig("coins_foreground.png")| 原图 | 灰度图 | 二值图 |
|---|---|---|
![]() | ![]() | ![]() |
| 距离变换 | 前景提取 |
|---|---|
![]() | ![]() |
2.3 确定未知区域
- 未知区域 = 图像 - 确定背景 - 确定前景
import cv2import numpy as npimport matplotlib.pyplot as plt
img = cv2.imread("coins_morph.png", 0)
ret, background = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
dist_transform = cv2.distanceTransform(background, cv2.DIST_L2, 0)ret, foreground = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
unknown = cv2.subtract(background, np.uint8(foreground))
plt.figure(figsize=(10,10))plt.imshow(unknown)plt.axis('off')plt.savefig("coins_unknown.png")| 前景图 | 未知区域 |
|---|---|
![]() | ![]() |
2.4 标注前景区域
-
使用函数
cv2.connectedComponents()进行标注,函数会将背景标注为0,将其他对象使用从1开始的正整数进行标注。 -
函数原型:
ret, labels = cv2.connectedComponents( image ) -
参数说明:
- image :8位、单通道的待标注图像。
- ret:返回的标注的数量。
- labels:标注的结果图像。
import cv2import numpy as npimport matplotlib.pyplot as plt
img = cv2.imread("coins_morph.png", 0)
ret, background = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
dist_transform = cv2.distanceTransform(background, cv2.DIST_L2, 0)ret, foreground = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
ret, markers = cv2.connectedComponents(np.uint8(foreground))
plt.figure(figsize=(10,10))plt.imshow(markers)plt.axis('off')plt.savefig("coins_markers.png")| 前景图 | 前景区域标注 |
|---|---|
![]() | ![]() |
2.5 图像分割
-
使用函数
cv2.watershed()实现基于分水岭算法的图像分割。 -
函数原型:
markers = cv2.watershed( image, markers ) -
参数说明:
- image :8位、三通道的图像。
- markers:32位、单通道的标注结果。每一个像素要么被设置为初始种子值,要么被设置为-1,表示边界。
-
基本步骤
- 通过形态学开运算对原始图像O去噪。
- 通过腐蚀操作获取背景B。
- 利用距离变换函数对原始图像进行运算,并进行阈值处理,得到前景F。
- 计算未知区域UN。其中,UN = O - B - F。
- 对前景区域进行标注。
- 对标注结果进行修正。
- 使用分水岭算法完成图像分割。
import cv2import numpy as npimport matplotlib.pyplot as plt
# 灰度处理img = cv2.imread("coins.jpg")img_gray = img[:,:,2]
# 二值化处理、形态学处理ret, img_bw = cv2.threshold(img_gray, 139, 255, cv2.THRESH_BINARY_INV)kernel = np.ones((5, 5), np.uint8)img_morph = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, kernel, iterations=1)img_morph = cv2.morphologyEx(img_morph, cv2.MORPH_OPEN, kernel, iterations=1)
# 距离变换dist_transform = cv2.distanceTransform(img_morph, cv2.DIST_L2, 0)
# 前景提取ret, foreground = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
# 获取未知区域unknown = cv2.subtract(img_morph, np.uint8(foreground))
# 前景标注ret, markers = cv2.connectedComponents(np.uint8(foreground))
# 标注结果修正markers = markers + 1markers[unknown==255] = 0
# 图像分割:分水岭算法markers = cv2.watershed(img,markers)
# 边缘标记img[markers == -1] = [0, 255, 0]
cv2.imwrite("coins_watershed.png", img)| 原图 | 图像分割 |
|---|---|
![]() | ![]() |










