python的opencv操作记录13——区域生长及分水岭算法
文章目录
- 图像区域基本算法——形态学运算
- 腐蚀与膨胀
- 开运算与闭运算
- opencv中的形态学运算
- 距离计算——distanceTransform函数
- 连通域
- 连通的定义
- 计算连通域——connectedComponents
- 连通域实验
- 基于区域的分割
- 区域生长算法
- 自定义一个最简单区域生长算法实现
- 区域分割
- 一般区域分割
- opencv中的分水岭算法
- 分水岭算法原理简单说明
- 分水岭算法使用
前面两篇文章说的分割,一个是基于阈值的分割,一个是基于边缘算法的分割。在传统的图像处理算法中,还有一个大类是基于区域的分割。
图像区域基本算法——形态学运算
基于区域的分割,需要先补充一点其他的预备知识,首先是图像形态学。
图像形态学就是对图像在形态上的一些算法,或者说运算。
腐蚀与膨胀
腐蚀和膨胀使形态学运算中最基本的用法,这个在之前的文章里描述过opencv中的原理和具体用法:
https://blog.csdn.net/pcgamer/article/details/124729236?spm=1001.2014.3001.5502
这里就不多说了。
开运算与闭运算
在形态学运算中,还定义了另外了两个运算:
- 开运算:先腐蚀图像,再膨胀图像,同样需要一个kernel。
- 闭运算:先膨胀图像,再腐蚀图像,同样需要一个kernel。
opencv中的形态学运算
opencv中除了提供了腐蚀,膨胀这些基本函数之外,还弄了一个综合函数:cv2.morphologyEx
这个函数可以对图像进行各种形态学操作,由其中一个参数op确认,可执行的运算列表根据枚举量MorphTypes确定:
官网上的这张图像说的非常的明白。
其他的参数和腐蚀和膨胀基本类似。
距离计算——distanceTransform函数
cv2.distanceTransform这个函数计算了一幅图像当中每个点与最近的0像素点之间的距离,如果本身像素为0,那么就等于0.
-
关于距离的定义,可以有三种选项(opencv官网)
-
还有一个参数是maskSize,我理解是在哪个范围之内进行计算,官网上也是提供了三个选项:
如果是DIST_MASK_PRECISE的话,就是在整张图中进行计算。
-
具体是用什么算法优化和减少计算量,官网上也提供了文献,有兴趣的朋友可以去了解了解。
连通域
连通域是图像运算中的一个重要概念,就是判断两个区域是否是连接的。
连通的定义
首先,两个像素怎么才叫连通,这个是可以定义的,比如两个像素像素值相同算连通,或者说两者相差不超过N之类。
其次,一个像素和周边的像素比较的时候,一般有下面几种方式。
- 4连通,像素和周边的四个像素点进行比较(按照上面的规则进行比较),就是上、下、左、右四个像素点。如果4个点都是与当前像素点相连通,那么说这几个像素点组成的区域就是一个4连通区域,很多区域生长算法就是以这个为标准来进行计算和生长的。
- 或者反过来说,一个区域是一个4连通区域的话,这个区域中的像素点与其周边的四个像素点都是连通的(最外边的一圈除外)
- 8连通,和上面的类似,就是像素点的8个邻域像素点都是连通的。
计算连通域——connectedComponents
这个函数就是用来做连通域计算的,有如下的参数:
-
image: 图像,单通道的8位图像。
-
connectivity,使用4连通还是8连通。直接填4或者8.
-
ltype,返回的图像数据(在返回值那里说)使用CV_32S或者CV_16U。
-
ccltype,实用计算连通域的算法(我的python版本好像没有这个入参):
每一种都有对应的论文去阐述,不细说。
返回值有两个:
- ret,返回有几个连通域,就是上面的ltype
- labels,连通域的图像。实际上,计算的结果就是给输入图像的每个区域打个标记,标记一下,这是哪个连通域的。如果标记为0,则为背景。
连通域实验
直接上代码:
img = cv2.imread('/Users/zoulei/files/personal/blog/images/car.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)cv2.imshow("grey", gray)ret, markers = cv2.connectedComponents(gray)img[markers == 0] = [0, 0, 0]#就直接使用三原色对不同的连通域进行涂色
colors = [[255, 0, 0],[0, 255, 0],[0, 0, 255]
]for i in range(1, ret+1):# python中的特有方式,好用的很img[markers == i] = colors[i % 3]cv2.imshow("component", img)
结果如下:
结果不太理想,还需要别的动作。不是这篇的重点,后续再说
基于区域的分割
区域生长算法
基于区域的分割逻辑上比较简单,就是需要分割出来的区域理论上来说,其中的像素值是相似的,或者说是有关联的。那么算法可以引入一个先验知识,或者说一个前提,算法的初始条件中就需要增加一个:种子点。
这个种子点就是从各个需要分割区域中挑选出来的点(通过各种手段,可以是手动,可以是自动,后续会详细讲到)来根据相似性进行扩展(生长),一直生长到区域的边缘为止(停止生长)。
所以区域生长算法的几个关键问题就是:
- 如何挑选生长点
- 如何确定生长原则
- 如何确定生长停止条件
各种各样的算法就是针对这三个问题提出各自的解决方案。
自定义一个最简单区域生长算法实现
为了理解这个算法,我弄了一个最简单区域生长。
- 固定一个点作为种子节点
- 往四个方向生长,只要像素差值小于20就生长,而且生长规则是直接赋值
- 停止规则也很简单,100个迭代
代码如下:
if __name__ == '__main__':img = cv2.imread("/Users/zoulei/files/personal/blog/images/tubeImg.jpeg")grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)cv2.imshow("origin", grey)# 随便挑一个点作为种子点point_x = 100point_y = 100grey[point_x, point_y] = 255# 四个生长方向# 如果差值超过20就停下# 暂时先生成20个迭代for i in range(100):if grey[point_x - i, point_y] - 255 < 20:grey[point_x - i, point_y] = 255if grey[point_x + i, point_y] - 255 < 20:grey[point_x + i, point_y] = 255if grey[point_x, point_y - i] - 255 < 20:grey[point_x, point_y - i] = 255if grey[point_x, point_y + i] - 255 < 20:grey[point_x, point_y + i] = 255cv2.imshow("changed", grey)cv2.waitKey()cv2.destroyAllWindows()
输出的图像为:
也就是说,通过4个方向的简单生长变成了一个十字形的区域,或者说分割出了一个十字形的区域。
我们可以通过修改种子点,生长规则和停止规则来确定一种新的区域分割算法,分水岭算法就是其中的一个代表算法,而opencv里提供了函数watershed来实现一种分水岭算法(分水岭算法也有很多种变形,我只说说opencv的实现算法)。
区域分割
一般区域分割
一般来说,可以直接使用连通域函数函数来做区域分割,我们也拿一个硬币图来实验。
当然不能直接使用连通域计算的函数,还需要做一些预处理:
img = cv2.imread('/Users/zoulei/files/personal/blog/images/coin.jpg')grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)ret, thresh_img = cv2.threshold(grey, 50, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))opening = cv2.morphologyEx(thresh_img, cv2.MORPH_OPEN, kernel, iterations=2)ret, markers = cv2.connectedComponents(opening)for i in range(1, ret+1):# python中的特有方式,好用的很img[markers == i] = colors[i % 3]
结果不甚理想:
感觉是最下面两个分成了两个区域,多膨胀几次试试看:
img = cv2.imread('/Users/zoulei/files/personal/blog/images/coin.jpg')grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)ret, thresh_img = cv2.threshold(grey, 50, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))opening = cv2.morphologyEx(thresh_img, cv2.MORPH_OPEN, kernel, iterations=2)dialate = cv2.dilate(opening, kernel, iterations=3)ret, markers = cv2.connectedComponents(dialate)for i in range(1, ret+1):# python中的特有方式,好用的很img[markers == i] = colors[i % 3]
很明显,如果两个区域隔的比较近,一膨胀就容易让两个区域变成一个连通域。那么针对这种两个区域有比较接近的边界的情况,有一种区域生长算法是叫做分水岭算法。
opencv中的分水岭算法
watershed函数有两个参数:
- imgage, 输入图像,一个3通道图。
- marker,单通道的图像,有点类似于掩膜的作用,具体的作用我们下面会具体讲到。
分水岭算法原理简单说明
首先说一下,为什么要用分水岭算法,我一直有个疑问的是opencv里面提供了connectedComponents函数来计算连通域(也有不同的算法,opencv中提供了参考文献)。那么分水岭算法和那些有什么区别呢?我个人的理解是在需分割区域的边界比较接近的时候比较有用,就和我上面提到的例子一样,一般来说用基于区域的算法进行分割的时候,通常会使用膨胀算法对内部的杂质或者空洞就行补充,但是如果使用了膨胀,因为分割区域挨的比较近,就会导致变成一个连通区域了。
这个时候就可以用到分水岭算法
分水岭算法的基本逻辑是把整张图像的灰度值或者是像素值想象成一个地形图,灰度值较低的是山谷,或者说盆地;灰度值较高的像素点就是山峰。
- 假设从图像上的盆地开始往图像里注水,水平面慢慢上升就会形成一个一个的水坑或者湖,就是一个一个的区域。
- 当两块水域连接到一起的时候,这里就可以停止注水了,这样就形成了一个边界,就可以完成区域分割操作了。
- 在opencv的分水岭算法中,通过第二个参数marker来给出种子点进行生长。
- opencv的分水岭算法中,是需要基于连通域算法的。
上面是一个基本原理,下面来说说具体的使用。
分水岭算法使用
我们还是使用上面的硬币图来进行实验。
我简单说说我对分水岭算法的理解:
- 上面提到了,分水岭算法是基于连通域算法的基础的。也就是说要通过连通域算法来给图像的每个区域进行编号,这些编号就是给分水岭算法提供的种子点,标记了这些已经是一块一块的区域了。
- 分水岭分割算法的目标是要区分出前景区域和背景区域来。
- 分水岭算法中的markker主要分为三类区域,前景区域(数字标记为1,2,3等等);背景区域,数字标记为-1。unkonwn区域,数字标记为0。这个就是用于解决连通域区域无法很好解决的边界过近,通过膨胀会连在一起的情况。
- 分水岭算法的主要逻辑就是通过确定好一些前景区域(种子点),然后确定一些背景区域,中间模糊的区域就是unknown区域。然后通过注水过程来在模糊区域中找到边界。
根据上面的基本逻辑,详细说说应用分水岭算法的代码:
- 计算背景区域
img = cv2.imread('./images/coin.jpg')grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)ret, thresh_img = cv2.threshold(grey, 50, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))opening = cv2.morphologyEx(thresh_img, cv2.MORPH_OPEN, kernel, iterations=2)# 背景区域sure_bg = cv2.dilate(opening, kernel, iterations=2)
上面代码中最后一句是通过一次膨胀来确定背景区域。我是这么理解这个代码的,通过一次threshold的区域分割后,基本上已经将前景区域大致轮廓找到了,经过一次膨胀后,就会比目标区域的范围更大,就可以把这样的一个区域称作为背景区域。
显示出来看一下:
很明显是比目标区域宽一些了。
- 计算前景区域
# 前景区域dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)ret, sure_fg = cv2.threshold(dist_transform, 0.2 * dist_transform.max(), 255, cv2.THRESH_BINARY)
这几句代码我理解了一阵子。
distanceTransform函数计算的就是图像中离0最近的像素点的距离。
那么上面的第一句代码计算得到的就是经过开运算之后的前景区域离后面的黑色背景区域的距离。
配合下一句代码的threshold函数,对计算的距离进行阈值运算,就是如果是小于距离最大值的0.2的点就为像素值0,否则则是255.
这两句可以理解为就是缩小开运算之后的目标区域,这部分区域确定为前景区域。
显示出来看一下就是:
区域小多了。那么实际上的边界就会存在于这个前景和背景之间!分水岭算法的任务就是要在这个区域中找到真实的边界。
- 在unknown区域中应用分水岭算法找到边界
fg和bg的中间范围就是上面逻辑中提到的unknown区域,也就是不确定区域。
显示出来结果如下:
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)ret, markers = cv2.connectedComponents(sure_fg)markers[unknown == 255] = 0markers = cv2.watershed(img, markers)img[markers==-1] = [0, 0, 255]
- 上面的代码中,用bg图减去fg图就是中间区域。
- 然后通过连通域算法得到前景区域中的区域标记。
- markers[unknown == 255],这句代码就是通过把unknown这个mat中等于255的,在marker这个mat的标记中标记为0.还记得上面提到的逻辑么,0就表示unknown区域。
- 最后应用watershed算法。
- 最后一句,watershed算法计算完成后,marker中为-1的像素值就是边界(marker的尺寸和原图一样)
最终的结果如下:
通过分水岭算法就可以较为准确的找到边界。
当然上图中还有一些多余的分割线,我觉得是可以通过对上面的unknown区域做一些改进和处理,是可以去除掉这些分割线的,这里就不在这里多说了。有兴趣的朋友也可以自行尝试。
相关文章:

python的opencv操作记录13——区域生长及分水岭算法
文章目录图像区域基本算法——形态学运算腐蚀与膨胀开运算与闭运算opencv中的形态学运算距离计算——distanceTransform函数连通域连通的定义计算连通域——connectedComponents连通域实验基于区域的分割区域生长算法自定义一个最简单区域生长算法实现区域分割一般区域分割open…...

一文看懂网上下单的手机流量卡为什么归属都是随机的!
最近很多网上下单的小伙伴们心中似乎都有一个疑问。那就是网上很多手机卡、流量卡都不能自选号码和归属地,就算能自选号码,归属地也是随机的而且很多都不会跟你说具体的城市,这是为什么呢?莫非其中有什么不可告人的秘密吗?小伙伴…...

python Pytest生成alluer测试报告的完整教程
1.下载allure包到本地,解压 网上很多资料,这边不提供了 2.配置环境变量 将上面解压后bin文件的路径复制,添加到环境变量Path下 3.验证环境变量配置是否功 在cmd中输入allure,回车 。查看allure是否成功: 4.pyc…...

4-spring篇
ApplicationContext refresh的流程 12个步骤 prepareRefresh 这一步创建和准备了Environment对象,并赋值给了ApplicationContext的成员变量 要理解Environment对象的作用 obtainFreshBeanFactory ApplicationContext 里面有一个成员变量,Beanfactory b…...
提升 Web 应用程序的性能:如何使用 JavaScript 编写缓存服务
缓存是一种重要的优化技术,用于加速数据访问和降低服务器负载。缓存存储经常访问的数据,以便在需要时可以快速检索。在本文中,我们将探索如何使用简单的数据结构在 JavaScript 中编写缓存服务。 编码缓存服务的第一步是定义将用于访问缓存的…...

供应商绩效管理指南:挑战、考核指标与管理工具
管理和优化供应商绩效既关键又具有挑战性。要知道价格并不是一切,如果你的供应商在商定的价格范围内向你开具发票,但服务达不到标准或货物不合格,你也无法达到节约成本的目标。 供应商绩效管理可以深入了解供应商可能带来的风险,…...

干货文稿|详解深度半监督学习
分享嘉宾 | 范越文稿整理 | William嘉宾介绍Introduction to Semi-Supervised Learning传统机器学习中的主流学习方法分为监督学习,无监督学习和半监督学习。这里存在一个是问题是为什么需要做半监督学习?首先是希望减少标注成本,因为目前可以…...

信箱|邮箱系统
技术:Java、JSP等摘要:在经济全球化和信息技术飞速发展的今天,通过邮件收发进行信息传递已经成为主流。目前,基于B/S(Browser/Server)模式的MIS(Management information system)日益…...
JS数组拓展
1、Array.from Array.from 方法用于将两类对象转为真正的数组: 类似数组的对象,所谓类似数组的对象,本质特征只有一点,即必须有length属性。 因此,任何有length属性的对象,都可以通过Array.from方法转为数组 和 可遍历…...
一道很考验数据结构与算法的功底的笔试题:用JAVA设计一个缓存结构
我在上周的笔试中遇到了这样一道题目,觉得有难度而且很考验数据结构与算法的功底,因此Mark一下。 需求说明 设计并实现一个缓存数据结构: 该数据结构具有以下功能: get(key) 如果指定的key存在于缓存中,则返回与该键关联的值&am…...
(10)C#传智:命名空间、String/StringBuilder、指针、继承New(第10天)
内容开始多了,慢品慢尝才有滋味。 一、命名空间namespace 用于解决类重名问题,可以看作类的文件夹. 若代码与被使用的类,与当前的namespace相同,则不需要using. 若namespace不同时,调用的方法:…...

基于Jetson Tx2 Nx的Qt、树莓派等ARM64架构的Ptorch及torchvision的安装
前提 已经安装好了python、pip及最基本的依赖库 若未安装好点击python及pip安装请参考这篇博文 https://blog.csdn.net/m0_51683386/article/details/129320492?spm1001.2014.3001.5502 特别提醒 一定要先根据自己板子情况,找好python、torch、torchvision的安…...

MySQL存储引擎详解及对比和选择
什么是存储引擎? MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善…...

【推拉框-手风琴】vue3实现手风琴效果的组件
简言 在工作时有时会用到竖形手风琴效果的组件。 在此记录下实现代码和实现思路。 手风琴实现 结构搭建 搭建结构主要实现盒子间的排列效果。 用flex布局或者其他布局方式将内容在一行排列把每一项的内容和项头用盒子包裹, 内容就是这一项要展示的内容…...

滑动窗口最大值:单调队列
239. 滑动窗口最大值 难度困难2154收藏分享切换为英文接收动态反馈 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例…...
负载均衡算法
静态负载均衡 轮询 将请求按顺序轮流地分配到每个节点上,不关心每个节点实际的连接数和当前的系统负载。 优点:简单高效,易于水平扩展,每个节点满足字面意义上的均衡; 缺点:没有考虑机器的性能问题&…...

C语言数组二维数组
C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量,比如 runoob0、runoob1、…、runoob99,而是…...

7年测试工程师,裸辞掉17K的工作,想跳槽找更好的,还是太高估自己了....
14年大学毕业后,在老师和朋友的推荐下,进了软件测试行业,这一干就是7年时间,当时大学本来就是计算机专业,虽然专业学的一塌糊涂,但是当年的软件测试属于新兴行业,人才缺口比较大,而且…...

企业为什么需要做APP安全评估?
近几年新型信息基础设施建设和移动互联网技术的不断发展,移动APP数量也呈现爆发式增长,进而APP自身的“脆弱性”也日益彰显,这对移动用户的个人信息及财产安全带来巨大威胁和挑战。在此背景下,国家出台了多部法律法规,…...

重回利润增长,涪陵榨菜为何能跑赢周期?
2022年消费市场持续低迷,疫情寒冬之下,不少食品快消企业均遭遇严重的业绩下滑,但一年里不断遭遇利空打击的“榨菜茅”涪陵榨菜,不仅安然躲过“酸菜劫”、走出“钠”争议,而且顺利将产品价格提起来,并在寒冬…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...