1192 字
6 分钟
OpenCV:图像分割

1. 分水岭算法#

​ 在图像处理的过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来

分水岭算法将图像形象地比喻为地理学上的地形表面,实现图像分割。任何一幅灰度图像,都可以被看作是地理学上的地形表面灰度值高的区域可以被看成是山峰灰度值低的区域可以被看成是山谷

2. 使用方法#

​ 使用函数cv2.watershed()实现分水岭算法,期间需要借助形态学函数距离变换函数cv2.distanceTransform()cv2.connectedComponents()完成图像分割。

2.1 形态学操作#

开运算#

​ 开运算是先腐蚀、后膨胀的操作,开运算能够去除图像内的噪声,如毛刺、噪点等

import cv2
import 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)
毛刺图开运算:去毛刺
JJ_open

获取图像边界#

​ 通过膨胀图像减去腐蚀图像,能够获取图像的边界

import cv2
import 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)
原图形态学梯度运算
J_openJ_gradient

2.2 距离变换、前景提取#

​ 距离变换函数cv2.distanceTransform()计算二值图像内任意点到最近背景点的距离,其计算结果反映了各个像素与背景的距离关系。通常情况下:

  • 如果前景对象中心距离值为0的像素点距离较远,会得到一个较大的值。

  • 如果前景对象边缘距离值为0的像素点距离较近,会得到一个较小的值。

  • 函数原型dst = cv2.distanceTransform(src, distanceType, maskSize, dstType )

  • 参数说明

    • src8位单通道二值图像
    • distanceType距离类型参数
      • cv2.DIST_USER:用户自定义距离
      • cv2.DIST_L1: L-1范数,街区距离,dist=x1x2+y1y2dist=|x_1-x_2|+|y_1-y_2|
      • cv2.DIST_L2:L-2范数,欧式距离,dist=(x1x2)2+(y1y2)2dist=\sqrt{(x_1-x_2)^2+(|y_1-y_2)^2}
      • cv2.DIST_C: L-∞范数,棋盘距离,dist=max(x1x2,y1y2)dist=max(|x_1-x_2|,|y_1-y_2|)
    • maskSize掩模的尺寸,当distanceTypecv2.DIST_L1cv2.DIST_C时,maskSize强制为3
    • dstType 目标图像的类型,默认为CV_32F
    • dst 目标图像,可以是8位32位浮点数据尺寸和src相同
import cv2
import numpy as np
import 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")
原图灰度图二值图
coinscoins_graycoins_morph
距离变换前景提取
coins_dist_transformcoins_foreground

2.3 确定未知区域#

  • 未知区域 = 图像 - 确定背景 - 确定前景
import cv2
import numpy as np
import 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")
前景图未知区域
coins_foregroundcoins_unknown

2.4 标注前景区域#

  • 使用函数cv2.connectedComponents()进行标注,函数会将背景标注为0,将其他对象使用从1开始的正整数进行标注。

  • 函数原型ret, labels = cv2.connectedComponents( image )

  • 参数说明

    • image 8位单通道待标注图像
    • ret:返回的标注的数量
    • labels标注的结果图像
import cv2
import numpy as np
import 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")
前景图前景区域标注
coins_foregroundcoins_markers

2.5 图像分割#

  • 使用函数cv2.watershed()实现基于分水岭算法图像分割

  • 函数原型markers = cv2.watershed( image, markers )

  • 参数说明

    • image 8位三通道图像
    • markers32位单通道标注结果。每一个像素要么被设置为初始种子值,要么被设置为-1,表示边界。
  • 基本步骤

    1. 通过形态学开运算对原始图像O去噪。
    2. 通过腐蚀操作获取背景B。
    3. 利用距离变换函数对原始图像进行运算,并进行阈值处理,得到前景F。
    4. 计算未知区域UN。其中,UN = O - B - F
    5. 前景区域进行标注
    6. 标注结果进行修正
    7. 使用分水岭算法完成图像分割
import cv2
import numpy as np
import 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 + 1
markers[unknown==255] = 0
# 图像分割:分水岭算法
markers = cv2.watershed(img,markers)
# 边缘标记
img[markers == -1] = [0, 255, 0]
cv2.imwrite("coins_watershed.png", img)
原图图像分割
coinscoins_watershed
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00