对于一个图像的灰度值,将图像放平,可以看成是类似与山谷与山顶的图像,灰度值小的就是山底。先找到若干个山底,同时加水,当加到一定程度时候,某些山顶会被淹没,此时在山顶修建大坝,避免两处水汇集在一起。此时这个大坝就是分水岭,即边缘。
利用OTSU方法首先进行二值化操作:
详情请看:图像阈值处理-OpenCV_独憩的博客-CSDN博客
import cv2.cv2
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread(r'XXXXX\water_coins.jpg')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
对于分水岭算法,我们首先要得到边界可能存在的区域,你们对于上述图像来说,我们要得到的图像为”圆环“
开始直接我们采用开运算去除噪声;
故我们先需要得到一些确定的”背景“,和一些确定的”内部“,利用两者相减得到边界可能存在的区域,即圆环;
确定的”背景“可以使原二值图像”膨胀“得到,确定的”内部“可以使原二值图像”腐蚀“得到,具体原理见:图像膨胀、腐蚀、开运算、闭运算---python.opencv_独憩的博客-CSDN博客
kernel = np.ones((3,3),np.uint8)
#去除噪声
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 3)
#膨胀得到背景
sure_bg = cv.dilate(opening,kernel,iterations=3)
#腐蚀得到前景
sure_fg = cv2.erode(opening, kernel, iterations=3)
sure_fg = np.uint8(sure_fg)
#两者相减得到边缘可能存在区域
unknown = cv.subtract(sure_bg,sure_fg)
plt.figure()
plt.subplot(1,3,1)
plt.imshow(sure_bg,'gray')
plt.title('sure background area')
plt.xticks([]),plt.yticks([])
plt.subplot(1,3,2)
plt.imshow(sure_fg,'gray')
plt.title('sure foreground area')
plt.xticks([]),plt.yticks([])
plt.subplot(1,3,3)
plt.imshow(unknown,'gray')
plt.title('unknown')
plt.xticks([]),plt.yticks([])
plt.show()
但是从图像看出,unknown图像中由于硬币重叠,有部分边界被抵消,故我们采用另外一种方法:
首先介绍一个函数:
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
我们只需要知道,这个函数的作用是得到图像(灰度)中所有非零像素与最近的零像素的距离
我们可以通过对dist_transform图像进行阈值处理得到确定的”内部“,这样的好处是可以控制圆环的粗细,阈值处理 cv.threshold具体见:图像阈值处理-OpenCV_独憩的博客-CSDN博客
kernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 3)
sure_bg = cv.dilate(opening,kernel,iterations=3)
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.5*dist_transform.max(),255,0)
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)
plt.figure()
plt.subplot(1,3,1)
plt.imshow(sure_bg,'gray')
plt.title('sure background area')
plt.xticks([]),plt.yticks([])
plt.subplot(1,3,2)
plt.imshow(sure_fg,'gray')
plt.title('sure foreground area')
plt.xticks([]),plt.yticks([])
plt.subplot(1,3,3)
plt.imshow(unknown,'gray')
plt.title('unknown')
plt.xticks([]),plt.yticks([])
plt.show()
然后我们需要对背景,前景(内部),边界区域进行分类:
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers+1 #使得背景为1
markers[unknown==255] = 0 #使得边界区域为0
markers_copy = markers.copy()
markers_copy[markers==0] = 150 # 灰色表示背景
markers_copy[markers==1] = 0 # 黑色表示背景
markers_copy[markers>1] = 255 # 白色表示前景
markers_copy = np.uint8(markers_copy)
plt.figure()
plt.imshow(markers_copy,'gray')
plt.title('markers')
plt.xticks([]),plt.yticks([])
plt.show()
最后直接调用分水岭算法函数:
markers = cv2.watershed(img, markers)
img=cv.cvtColor(img,cv.COLOR_BGR2RGB)
img[markers==-1] = [255,0,0]
plt.figure()
plt.imshow(img,'gray')
plt.xticks([]),plt.yticks([])
plt.show()