随着当今世界的发展,计算机视觉技术的应用越来越广泛。伴随着硬件设备的不断升级,构造复杂的计算机视觉应用变得越来越容易了。OpenCV像是一个黑盒,让我们专注于视觉应用的开发,而不必过多的关注基础图象处理的具体细节。
了解分水岭算法之前,我们需要了解什么是图像的分割。
在图像的处理过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来。例如,在视频监控中,观测到的是固定背景下的视频内容,而我们对背景本身并无兴趣,感兴趣的是背景中出现的车辆,行人或者其他对象。我们希望将这些对象从视频中提取出来,而忽略那些没有对象进入背景的视频内容。
图像分割是图像处理过程中一种非常重要的操作。分水岭算法将图像形象地比喻为地理学上的地形表面,实现图像分割,该算法非常有用。
下面,博主对分水岭算法的相关内容做简单的介绍。(详细可以参考冈萨雷斯的《数字图像处理》一书)
任何一副灰度图像,都可以被看作是地理学上的地形表面,灰度值越高的区域可以被看成是山峰,灰度值越低的区域可以被看成是山谷。
如果我们向每个山谷中灌注不同颜色的水。那么随着水位的不断升高,不同山谷的水就汇聚到一起。在这个过程中,为了防止不同山谷的水交汇,我们需要在水流可能汇合的地方构建堤坝。该过程将图像分为两个不同的集合:集水盆地和分水岭线。我们构建的堤坝就是分水岭线,也即对原始图像的分割。这就是分水岭算法的原理。
不过,一般的图像都存在着噪声,采用分水岭算法时,会经常得到过度分割的结果。为了改善图像分割的效果,人们提出了基于掩摸的改进的分水岭算法。改进的分水岭算法允许用户将它认为是同一个分割区域的部分标注出来。这样,分水岭算法在处理时,就会将标注的部分处理为同一个分割区域。
如果对于该理论不怎么了解,可以使用软件PowerPoint中的“删除背景”功能进行观察配合理解。
在OpenCV中,可以使用函数cv2.watershed()函数实现分水岭算法。不过,具体实现的过程,还需要借助形态学函数,距离变换函数cv2.distanceTransform(),cv2.connectedComponents()来完成图像分割。
在使用分水岭算法之前,我们需要对图像进行简单的形态学处理。一般情况下,我们都是使用形态学中的开运算,因为开运算是先腐蚀后膨胀的操作,能够去除图像内的噪声。
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("36.jpg") k=np.ones((5,5),dtype=np.uint8) e=cv2.erode(img,k) result=cv2.subtract(img,e) plt.subplot(131) plt.imshow(img, cmap="gray") plt.axis('off') plt.subplot(132) plt.imshow(e, cmap="gray") plt.axis('off') plt.subplot(133) plt.imshow(result, cmap="gray") plt.axis('off') plt.show()
回顾一下,我们前面的开运算函数为cv2.erode(),这里我们首先经过开运算去除噪声。然后减法运算cv2.subtract()获取图像边界。运行之后,效果如下:
当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了。这个时候,就需要借助变换函数cv2.distanceTransform()方便地将前景对象提取出来。
cv2.distanceTransform()反应了各个像素点与背景(值为0的像素点)的距离关系。通常情况下:
下面,我们来使用该函数确定一副图像的前景,并观察效果。
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("36.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) k = np.ones((5, 5), dtype=np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, k, iterations=2) distTransform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) ret, fore = cv2.threshold(distTransform, 0.7 * distTransform.max(), 255, 0) plt.subplot(131) plt.imshow(img, cmap="gray") plt.axis('off') plt.subplot(132) plt.imshow(distTransform, cmap="gray") plt.axis('off') plt.subplot(133) plt.imshow(fore, cmap="gray") plt.axis('off') plt.show()
这里,我们使用cv2.morphologyEx函数进行开运算,同时使用cv2.distanceTransform得到距离图像,最后在通过cv2.threshold对距离图像进行阈值处理,确定前景。运行之后,效果如下:
通过距离函数,我们获取到了图像的“中心”,也就是“确定前景”。为了方便后续的讲解,我们将确定前景称为F。
图像中有了确定前景F和确定背景B,剩下的区域就是未知区域UN了。这部分区域正是分水岭算法要进一步明确的区域。
针对一副图像0,通过以下关系能够得到未知区域UN:
未知区域UN=图像0-确定背景B-确定前景F
由上述公式变换得到:
未知区域UN=(图像0-确定背景B)-确定前景F
其中(图像0-确定背景B)就是我们开始的减法操作,通过形态学膨胀得到。也只需要将上面的代码添加4行并更改显示的代码内容:
bg=cv2.dilate(opening,k,iterations=3) fore=np.uint8(fore) un=cv2.subtract(bg,fore) plt.subplot(221) plt.imshow(img, cmap="gray") plt.axis('off') plt.subplot(222) plt.imshow(bg, cmap="gray") plt.axis('off') plt.subplot(223) plt.imshow(fore, cmap="gray") plt.axis('off') plt.subplot(224) plt.imshow(un, cmap="gray") plt.axis('off') plt.show()
运行之后,效果如下:
左上为原图
右上为原图膨胀后得到的图像bg,其背景图像是确定背景B。前景图像是“原始图像0-确定背景B”
左下为确定前景图像fore
右下为未知区域图像UN
明确了确定前景后,就可以对确定前景进行标注了。在OpenCV中,它提供了cv2.ConnectedComponents()函数进行标注。
该函数会将背景标注为0,将其他的对象使用从1开始的正整数标注。它只有一个参数8位单通道的待标注图像。
返回值有两个:retval为返回的标注数量,labels为标注的结果图像。
下面,我们来使用该函数进行标注。代码如下(同样更改上面bg下面代码就行):
bg = cv2.dilate(opening, k, iterations=3) fore = np.uint8(fore) ret, markets = cv2.connectedComponents(fore) unknown=cv2.subtract(bg,fore) markets=markets+1 markets[unknown==255]=0 plt.subplot(131) plt.imshow(img, cmap="gray") plt.axis('off') plt.subplot(132) plt.imshow(fore, cmap="gray") plt.axis('off') plt.subplot(133) plt.imshow(markets, cmap="gray") plt.axis('off') plt.show()
修改上面fore = np.uint8(fore)的代码,并修改输出内容。运行之后,我们会得到原图,前景图像的中心点图像fore以及标注后的结果图像markets。效果如下:
经过前文的介绍,我们了解了使用分水岭算法进行图像分割的基本步骤:
完整代码如下:
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("36.jpg") plt.subplot(121) plt.imshow(img, cmap="gray") plt.axis('off') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) k = np.ones((5, 5), dtype=np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, k, iterations=2) distTransform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) ret, fore = cv2.threshold(distTransform, 0.2 * distTransform.max(), 255, 0) bg = cv2.dilate(opening, k, iterations=3) fore = np.uint8(fore) ret, markets = cv2.connectedComponents(fore) unknown = cv2.subtract(bg, fore) markets = markets + 1 markets[unknown == 255] = 0 markets = cv2.watershed(img, markets) img[markets == -1] = [255, 0, 0] plt.subplot(122) plt.imshow(img, cmap="gray") plt.axis('off') plt.show()
运行之后,我们就可以得到分割的图像:
当然,参数可以调整,可以看到大致的硬币被完整的分割出来了。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
长按识别二维码并关注微信
更方便到期提醒、手机管理