OpenCV图像处理之——分水岭算法的图像分割
- 1. 效果图
- 2. 原理
- 3. 源码
- 参考
这篇博客将介绍如何使用分水岭算法进行基于标记的图像分割,OpenCV通过cv2.watershed()实现;
1. 效果图
官方示例——水果分割 效果图如下:
共分了8块,可交互式的进行分割;
原图 VS 硬币分割效果图如下:
可以看到有的硬币及边界被正确检测,有的并没有;(检测圆并不推荐用霍夫圆方法检测,而推荐用Canny边缘检测,这是在阈值化等处理后,有的边缘并不是特别规则的圆)
硬币检测过程图如下:
分别为:原始图 VS 灰度图 VS 阈值化图 VS 形态学Opening图
确定背景图 VS 距离阈值图 VS 确定前景图 VS 未知区域
标记图 VS 标记边界图 VS 分水岭结果图
- 1 origin:原始的RGB图
- 2 gray:灰度图
- 3 thresh:简单轮廓检测前置步骤:背景与前景分离
- 4 opening 形态学开操作,去除对象中的任何小孔
- 5 sure_bg 膨胀将对象边界增加到背景,得到确定的背景
- 6 distance transform 距离变换图,可以找到确定的前景,也可以找到互相有关联的前景;
- 7 sure_fg 找到距离变换并应用适当的阈值或者腐蚀,以找到确定的前景
- 8 unknown 确定背景-确定前景,得到未知区域图
- 9 markers 对未知区域进行标记(背景深蓝色,前景浅蓝色)
- 10 marker boundaries 深蓝色区域表示未知区域。确定背景的剩余区域以浅蓝色显示。
- 11 res 应用分水岭算法得到结果,边界区域被标记为-1;
2. 原理
在某些情况下,可能只对前景分割感兴趣,而不是对相互接触的对象进行分离。在这种情况下,不需要使用距离变换,只需要腐蚀就足够了。腐蚀只是另一种提取全部确定前景区域的方法。
可以看到左图距离变换能找到相互接触的对象(硬币是有互相粘连的地方);
3. 源码
# 分水岭算法
import numpy as np
import cv2
from matplotlib import pyplot as plt
dict = {}
img = cv2.imread('coins.jpg')
cv2.imshow("origin", img)
dict['origin'] = img
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
dict['gray'] = gray
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
dict['thresh'] = thresh
# 去掉噪音
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
dict['opening'] = opening
# 寻找确定的背景
sure_bg = cv2.dilate(opening, kernel, iterations=3)
dict['sure_bg'] = sure_bg
# 寻找确定的前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
dict['dist_transform'] = dist_transform
dict['sure_fg'] = sure_fg
# 用背景减前景,得到不确定的区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
dict['unknown'] = unknown
# 创建标记(它是一个与原始图像大小相同的数组,但数据类型为 int32)并标记其中的区域。
# 标记背景、不确定对象(默认背景为0,其他为1)
ret, markers = cv2.connectedComponents(sure_fg)
dict['markers'] = markers
# 对所有标签加1,将得到背景为1
markers = markers + 1
# 标记未知区域为0
markers[unknown == 255] = 0
# 应用分水岭算法,然后标记图像,边界区域将被标记为-1
markers = cv2.watershed(img, markers)
img[markers == -1] = [255, 0, 0]
cv2.imshow("watershed res", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
dict['marker boundaries'] = markers
dict['res'] = img
size = len(dict)
print(size, '\n')
for i, key in enumerate(dict):
print(i + 1, key, len(list(dict[key].shape)))
plt.subplot(3, 4, i + 1)
if (len(list(dict[key].shape)) > 2): # 由于matplot需要RGB以显示彩色图,而OpenCV是用BGR来表示彩色图的
plt.imshow(cv2.cvtColor(dict[key], cv2.COLOR_BGR2RGB)) # 为确保正确显示RGB图,进行颜色空间转换
else:
plt.imshow(dict[key])
plt.title(key) # 图片的标题
plt.xticks([]) # 去掉x轴的刻度
plt.yticks([]) # 去掉y轴的刻度
plt.show()
参考
- https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_watershed/py_watershed.html#watershed
- https://github.com/seminar2012/opencv/blob/master/samples/python/watershed.py