图像分割是一种基本的图像处理技术,是指将图像分成不同特性的区域,并对目标进行提取的技术,它是由图像处理到图像分析的关键步骤。
目标提取和图像理解都是在图像分割的基础上进行的。
分水岭算法会把临近像素间的相似性作为重要的参考因素,在空间位置上相近和灰度值相近的像素点互相连接起来构成一个封闭的区域。
可以把灰度图与地形图做对比。灰度值低的地方是山谷,灰度值高的地方是山峰。这样山峰包围了山谷,也天然的形成了分割线。
在真实的图像中,由于噪声的存在,会使很多局部极小值存在,使用分水岭算法常常存在过度分割的现象。为了解决过度分割的问题,可以使用基于标记图像的分水岭算法。通过标记来定义一些灰度层级,在该区域从这个灰度层级开始,这样就去除了一些很小的噪声极值。
形态学处理是对分水岭算法所使用的图像进行预处理,去除一些不必要的影响。比如开运算是县腐蚀后膨胀,可以去除图像内的噪声。
如果图像内的子图独立出现,可以直接使用腐蚀操作确定前景对象,如果子图连接在一起,可以借助距离变换函数cv.distanceTransform()提取图像的前景。距离变换函数计算二值图像内任意点到最近背景点的距离。该函数计算的是非零像素点到最近的零值像素点的距离。对上述计算结果进行阈值化处理,就可以得到图像内子图的一些形状信息。
使用距离变换函数确定一幅图像的前景图:
import numpy as np
import cv2 as cv
image = cv.imread("bi.jpg", 0)
ret1, thresh = cv.threshold(image, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
imageopen = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
distTransform = cv.distanceTransform(imageopen, cv.DIST_L2, 5)
ret2, fore = cv.threshold(distTransform, 0.4*distTransform.max(), 255, 0)
cv.imshow("imageGray", image)
cv.imshow("imageOpen", imageopen)
cv.imshow("distTransform", distTransform)
cv.imshow("fore", fore)
cv.waitKey()
cv.destroyAllWindows()
图像的开运算是先膨胀后腐蚀的运算,得到的背景信息小于实际的背景信息,而距离变换函数只是得到了图像的“中心信息”,即确定前景。对于一幅图像来说,除去这两种区域之外多剩下的就是未知区域。
import numpy as np
import cv2 as cv
image = cv.imread("bi.jpg", 0)
ret1, thresh = cv.threshold(image, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
imageopen = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
bg = cv.dilate(imageopen, kernel, iterations=3)
distTransform = cv.distanceTransform(imageopen, cv.DIST_L2, 5)
ret2, fore = cv.threshold(distTransform, 0.4*distTransform.max(), 255, 0)
fore = np.uint8(fore)
un = cv.subtract(bg, fore)
cv.imshow("imageGray", image)
cv.imshow("bg", bg)
cv.imshow("fore", fore)
cv.imshow("un", un)
cv.waitKey()
cv.destroyAllWindows()
在确定了前景图像后,可以通过OpenCV提供的cv.connectedComponents()函数对图像进行标注。该函数会将背景图像标记为0,将其他图像从1开始标记。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
image = cv.imread("bi.jpg", 0)
ret1, thresh = cv.threshold(image, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
imageopen = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
bg = cv.dilate(imageopen, kernel, iterations=3)
distTransform = cv.distanceTransform(imageopen, cv.DIST_L2, 5)
ret2, fore = cv.threshold(distTransform, 0.4*distTransform.max(), 255, 0)
fore = np.uint8(fore)
ret3, labels = cv.connectedComponents(fore)
print(ret3)
plt.subplot(121)
plt.imshow(fore)
plt.axis('off')
plt.subplot(122)
plt.imshow(labels)
plt.axis('off')
plt.show()
cv.waitKey()
cv.destroyAllWindows()
可以利用OpenCV提供的cv.watershed()函数实现图像的分水岭操作。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
image = cv.imread("bi.jpg")
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
imagergb = cv.cvtColor(image, cv.COLOR_BGR2RGB)
ret1, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
sure_bg = cv.dilate(opening, kernel, iterations=3)
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
ret2, sure_fg = cv.threshold(dist_transform, 0.005*dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg)
ret3, markers = cv.connectedComponents(sure_fg)
img = cv.watershed(image, markers)
plt.subplot(121)
plt.imshow(imagergb)
plt.axis('off')
plt.subplot(122)
plt.imshow(img)
plt.axis('off')
plt.show()
cv.waitKey()
cv.destroyAllWindows()