百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

OpenCV(36)——用分水岭算法实现图像的分割与提取

yuyutoo 2024-10-13 00:29 6 浏览 0 评论

图像分割

了解分水岭算法之前,我们需要了解什么是图像的分割。

在图像的处理过程中,经常需要从图像中将前景对象作为目标图像分割或者提取出来。例如,在视频监控中,观测到的是固定背景下的视频内容,而我们对背景本身并无兴趣,感兴趣的是背景中出现的车辆,行人或者其他对象。我们希望将这些对象从视频中提取出来,而忽略那些没有对象进入背景的视频内容。

分水岭算法

图像分割是图像处理过程中一种非常重要的操作。分水岭算法将图像形象地比喻为地理学上的地形表面,实现图像分割,该算法非常有用。

下面,博主对分水岭算法的相关内容做简单的介绍。(详细可以参考冈萨雷斯的《数字图像处理》一书)

任何一副灰度图像,都可以被看作是地理学上的地形表面,灰度值越高的区域可以被看成是山峰,灰度值越低的区域可以被看成是山谷。

如果我们向每个山谷中灌注不同颜色的水。那么随着水位的不断升高,不同山谷的水就汇聚到一起。在这个过程中,为了防止不同山谷的水交汇,我们需要在水流可能汇合的地方构建堤坝。该过程将图像分为两个不同的集合:集水盆地和分水岭线。我们构建的堤坝就是分水岭线,也即对原始图像的分割。这就是分水岭算法的原理。

不过,一般的图像都存在着噪声,采用分水岭算法时,会经常得到过度分割的结果。为了改善图像分割的效果,人们提出了基于掩摸的改进的分水岭算法。改进的分水岭算法允许用户将它认为是同一个分割区域的部分标注出来。这样,分水岭算法在处理时,就会将标注的部分处理为同一个分割区域。

如果对于该理论不怎么了解,可以使用软件PowerPoint中的“删除背景”功能进行观察配合理解。

waterShed函数

在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()获取图像边界。运行之后,效果如下:

distanceTransform函数

当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了。这个时候,就需要借助变换函数cv2.distanceTransform()方便地将前景对象提取出来。

cv2.distanceTransform()反应了各个像素点与背景(值为0的像素点)的距离关系。通常情况下:

1.如果前景对象的中心距离值为0的像素点距离较远,会得到一个较大的值。2.如果前景对象的边缘距离值为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

ConnectedComponents函数

明确了确定前景后,就可以对确定前景进行标注了。在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。效果如下:

实战分水岭算法

经过前文的介绍,我们了解了使用分水岭算法进行图像分割的基本步骤:

1.通过形态学开运算对原始图像0进行去噪2.通过腐蚀操作获取“确定背景B”。需要注意,这里得到“原始图像-确定背景”即可3.利用距离变换函数对原始图像进行运算,并对其进行阈值处理,得到“确定前景F”4.计算未知区域UN(UN=0-B-F)5.利用函数cv2.connectedComponents()对原始图像0进行标注6.对函数cv2.connectedComponents()的标注结果进行修正7.使用分水岭函数完成图像分割

完整代码如下:

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()

运行之后,我们就可以得到分割的图像:

当然,参数可以调整,可以看到大致的硬币被完整的分割出来了。

相关推荐

.NET 奇葩问题调试经历之3——使用了grpc通讯类库后,内存一直增长......

...

全局和隐式 using 指令详解(全局命令)

1.什么是全局和隐式using?在.NET6及更高版本中,Microsoft引入了...

请停止微服务,做好单体的模块化才是王道:Spring Modulith介绍

1、介绍模块化单体是一种架构风格,代码是根据模块的概念构成的。对于许多组织而言,模块化单体可能是一个很好的选择。它有助于保持一定程度的独立性,这有助于我们在需要的时候轻松过渡到微服务架构。Spri...

ASP.NET程序集引用之痛:版本冲突、依赖地狱等解析与实战

我是一位多年后端经验的工程师,其中前几年用ASP.NET...

.NET AOT 详解(.net 6 aot)

简介AOT(Ahead-Of-TimeCompilation)是一种将代码直接编译为机器码的技术,与传统的...

一款基于Yii2开发的免费商城系统(一款基于yii2开发的免费商城系统是什么)

哈喽,我是老鱼,一名致力于在技术道路上的终身学习者、实践者、分享者!...

asar归档解包(游戏arc文件解包)

要学习Electron逆向,首先要有一个Electron开发的程序的发布的包,这里就以其官方的electron-quick-start作为例子来进行一下逆向的过程。...

在PyCharm 中免费集成Amazon CodeWhisperer

CodeWhisperer是Amazon发布的一款免费的AI编程辅助小工具,可在你的集成开发环境(IDE)中生成实时单行或全函数代码建议,帮助你快速构建软件。简单来说,AmazonCodeWhi...

2014年最优秀JavaScript编辑器大盘点

1.WebstormWebStorm是一种轻量级的、功能强大的IDE,为Node.js复杂的客户端开发和服务器端开发提供完美的解决方案。WebStorm的智能代码编辑器支持JavaScript,...

基于springboot、tio、oauth2.0前端vuede 超轻量级聊天软件分享

项目简介:基于JS的超轻量级聊天软件。前端:vue、iview、electron实现的PC桌面版聊天程序,主要适用于私有云项目内部聊天,企业内部管理通讯等功能,主要通讯协议websocket。支持...

JetBrains Toolbox推出全新产品订阅授权模式

捷克知名软件开发公司JetBrains最为人所熟知的产品是Java编程语言开发撰写时所用的集成开发环境IntelliJIDEA,相信很多开发者都有所了解。而近期自2015年11月2日起,JetBr...

idea最新激活jetbrains-agent.jar包,亲测有效

这里分享一个2019.3.3版本的jetbrains-agent.jar,亲测有效,在网上找了很多都不能使用,终于找到一个可以使用的了,这里分享一下具体激活步骤,此方法适用于Jebrains家所有产品...

CountDownTimer的理解(countdowntomars)

CountDownTimer是android开发常用的计时类,按照注释中的说明使用方法如下:kotlin:object:CountDownTimer(30000,1000){...

反射为什么性能会很慢?(反射时为什么会越来越长)

1.背景前段时间维护一个5、6年前的项目,项目总是在某些功能使用上不尽人意,性能上总是差一些,仔细过了一下代码发现使用了不少封装好的工具类,工具类里面用了好多的反射,反射会影响到执行效率吗?盲猜了一...

btrace 开源!基于 Systrace 高性能 Trace 工具

介绍btrace(又名RheaTrace)是抖音基础技术团队自研的一款高性能AndroidTrace工具,它基于Systrace实现,并针对Systrace不足之处加以改进,核心改进...

取消回复欢迎 发表评论: