目录
0 原理 1 举例说 1)二值化 2)去除图像中的所有的白噪声 3)提取肯定是硬币的区域 4)获得边界区域
5)标记区域 6)实施分水岭算法
在地理学中,分水岭是一个山脊,该山脊通过不同的水系来区分排水区域。集水盆地是把水排入河流或水库的地理区域。分水岭变换把这些概念应用到灰度图像处理中,从而解决许多图像分割问题。
理解分水岭变换要求我们把灰度图像视为一个拓扑表面,表面中f(x, y)的值被解释为高度。例如, 我们可以把下图(a)中的简单图像形象化为下图(b)中的三维表面。如果雨水降落到该表面上,那么雨水明显会流人两个集水盆地中。正好降落到分水岭脊线上的雨水会等概率地流到两个集水盆地中。 分水岭变换将找到灰度图像中的集水盆地和脊线。在解决图像分割问题方面,关键概念是把起始图像 变为另一幅图像,在变换后的图像中,集水盆地就是我们要识别的物体或区域。
OpenCV 采用了基于掩模的分水岭 算法,在这种算法中我们要设置那些山谷点会汇合,那些不会。这是一种交互 式的图像分割。我们要做的就是给我们已知的对象打上不同的标签。如果某个 区域肯定是前景或对象,就使用某个颜色(或灰度值)标签标记它。如果某个 区域肯定不是对象而是背景就使用另外一个颜色标签标记。而剩下的不能确定 是前景还是背景的区域就用 0 标记。这就是我们的标签。然后实施分水岭算法。 每一次灌水,我们的标签就会被更新,当两个不同颜色的标签相遇时就构建堤 坝,直到将所有山峰淹没,最后我们得到的边界对象(堤坝)的值为 -1。
首先看下面这个例子,再具体解释里面的含义:
我们从找到硬币的近似估计开始。我们可以使用 Otsu's 二值化。
import numpy as np
import cv2
from matplotlib import pyplot as plt
src = cv2.imread('test27.jpg')
img = src.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
这就需要使用形态学中的开运算。为了去除对象上小的空洞我们需要使用形态学闭运算。所以我们现在知道靠近 对象中心的区域肯定是前景,而远离对象中心的区域肯定是背景。而不能确定的区域就是硬币之间的边界。
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
开运算可参考:https://blog.csdn.net/yukinoai/article/details/86762342
a. 当硬币之间没有接触时
腐蚀操作可以去除边缘像素。剩下就可以肯定是硬币了。
b. 硬币之间是相互接触
距离变换再加上合适的阈值。接下来我们要找到肯定不是硬币的区域。这是就需要进行膨胀操作了。 膨胀可以将对象的边界延伸到背景中去。这样由于边界区域被去处理,我们就可以知道那些区域肯定是前景,那些肯定是背景。
# 膨胀
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# 距离变换
dist_transform = cv2.distanceTransform(opening, 1, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
膨胀操作可参考:https://blog.csdn.net/yukinoai/article/details/86762342
距离变换函数:
cv2.distanceTransform(src, distanceType, maskSize)
DIST_L1 = 1, //!< distance = |x1-x2| + |y1-y2|
DIST_L2 = 2, //!< the simple euclidean distance
DIST_C = 3, //!< distance = max(|x1-x2|,|y1-y2|)
DIST_L12 = 4, //!< L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
DIST_FAIR = 5, //!< distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
DIST_WELSCH = 6, //!< distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846
DIST_HUBER = 7 //!< distance = |x| DIST_MASK_3 = 3, //!< mask=3 DIST_MASK_5 = 5, //!< mask=5 DIST_MASK_PRECISE = 0 //!< mask=0 剩下的区域就是我们不知道该如何区分的了。这就是分水岭算法要做的。这些区域通常是前景与背景的交界处(或者两个前景的交界)。我们称之为边界。 现在知道了那些是背景那些是硬币了。那我们就可以创建标签(一个与原图像大小相同,数据类型为 in32 的数组),并标记其中的区域了。对我们已经 确定分类的区域(无论是前景还是背景)使用不同的正整数标记,对我们不确 定的区域使用 0 标记。我们可以使用函数 cv2.connectedComponents() 来做这件事。它会把将背景标记为 0,其他的对象使用从 1 开始的正整数标记。 但是,我们知道如果背景标记为 0,那分水岭算法就会把它当成未知区域了。所以我们想使用不同的整数标记它们。而对不确定的区域(函数 cv2.connectedComponents 输出的结果中使用 unknown 定义未知区域)标记为 0。 结果使用 JET 颜色地图表示。深蓝色区域为未知区域。肯定是硬币的区域 使用不同的颜色标记。其余区域就是用浅蓝色标记的背景了。 其中connectedComponents()函数: cv2.connectedComponents(image, labels, connectivity, ltype) 标签图像将会被修改,边界区域的标记将变为 -1 其中分水岭算法函数: cv2.watershed(img, markers) 最后所有程序如下: 结果如下:
4)获得边界区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
5)标记区域
# 标记
ret, markers1 = cv2.connectedComponents(sure_fg)
# 确保背景是1不是0
markers = markers1 + 1
# 未知区域标记为0
markers[unknown == 255] = 0
6)实施分水岭算法
markers3 = cv2.watershed(img, markers)
img[markers3 == -1] = [0, 0, 255]
import numpy as np
import cv2
from matplotlib import pyplot as plt
src = cv2.imread('test27.jpg')
img = src.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# 消除噪声
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 膨胀
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# 距离变换
dist_transform = cv2.distanceTransform(opening, 1, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
# 获得未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
# 标记
ret, markers1 = cv2.connectedComponents(sure_fg)
# 确保背景是1不是0
markers = markers1 + 1
# 未知区域标记为0
markers[unknown == 255] = 0
markers3 = cv2.watershed(img, markers)
img[markers3 == -1] = [0, 0, 255]
plt.subplot(241), plt.imshow(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)),
plt.title('Original'), plt.axis('off')
plt.subplot(242), plt.imshow(thresh, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(243), plt.imshow(sure_bg, cmap='gray'),
plt.title('Dilate'), plt.axis('off')
plt.subplot(244), plt.imshow(dist_transform, cmap='gray'),
plt.title('Dist Transform'), plt.axis('off')
plt.subplot(245), plt.imshow(sure_fg, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(246), plt.imshow(unknown, cmap='gray'),
plt.title('Unknow'), plt.axis('off')
plt.subplot(247), plt.imshow(np.abs(markers), cmap='jet'),
plt.title('Markers'), plt.axis('off')
plt.subplot(248), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)),
plt.title('Result'), plt.axis('off')
plt.show()