python的opencv操作记录13——区域生长及分水岭算法

news2025/1/10 22:04:34

文章目录

  • 图像区域基本算法——形态学运算
    • 腐蚀与膨胀
    • 开运算与闭运算
    • 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 = 100
    point_y = 100

    grey[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] = 255
        if grey[point_x + i, point_y] - 255 < 20:
            grey[point_x + i, point_y] = 255
        if grey[point_x, point_y - i] - 255 < 20:
            grey[point_x, point_y - i] = 255
        if grey[point_x, point_y + i] - 255 < 20:
            grey[point_x, point_y + i] = 255

    cv2.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的分水岭算法中,是需要基于连通域算法的。

上面是一个基本原理,下面来说说具体的使用。

分水岭算法使用

我们还是使用上面的硬币图来进行实验。
我简单说说我对分水岭算法的理解:

  1. 上面提到了,分水岭算法是基于连通域算法的基础的。也就是说要通过连通域算法来给图像的每个区域进行编号,这些编号就是给分水岭算法提供的种子点,标记了这些已经是一块一块的区域了。
  2. 分水岭分割算法的目标是要区分出前景区域和背景区域来。
  3. 分水岭算法中的markker主要分为三类区域,前景区域(数字标记为1,2,3等等);背景区域,数字标记为-1。unkonwn区域,数字标记为0。这个就是用于解决连通域区域无法很好解决的边界过近,通过膨胀会连在一起的情况。
  4. 分水岭算法的主要逻辑就是通过确定好一些前景区域(种子点),然后确定一些背景区域,中间模糊的区域就是unknown区域。然后通过注水过程来在模糊区域中找到边界。

根据上面的基本逻辑,详细说说应用分水岭算法的代码:

  1. 计算背景区域
  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的区域分割后,基本上已经将前景区域大致轮廓找到了,经过一次膨胀后,就会比目标区域的范围更大,就可以把这样的一个区域称作为背景区域。

显示出来看一下:
在这里插入图片描述

很明显是比目标区域宽一些了。

  1. 计算前景区域
  # 前景区域
    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.
这两句可以理解为就是缩小开运算之后的目标区域,这部分区域确定为前景区域。

显示出来看一下就是:
在这里插入图片描述

区域小多了。那么实际上的边界就会存在于这个前景和背景之间!分水岭算法的任务就是要在这个区域中找到真实的边界

  1. 在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] = 0

markers = 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区域做一些改进和处理,是可以去除掉这些分割线的,这里就不在这里多说了。有兴趣的朋友也可以自行尝试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决vscode无法自动更新

一&#xff0e;前言 要在&#xff56;&#xff53;&#xff43;&#xff4f;&#xff44;&#xff45;里面安装插件&#xff0c;被提示版本不匹配&#xff0c;然后得更新&#xff0c;然后我发现我的&#xff07;帮助&#xff07;菜单栏下没有检查更新&#xff0c;然后我去&…

19- CNN进行Fashion-MNIST分类 (tensorflow系列) (项目十九)

项目要点 Fashion-MNIST总共有十个类别的图像。代码运行位置 CPU: cputf.config.set_visible_devices(tf.config.list_physical_devices("CPU"))fashion_mnist keras.datasets.fashion_mnist # fashion_mnist 数据导入训练数据和测试数据拆分: x_valid, x_train…

WebAPI

WebAPI知识详解day11.Web API 基本认知作用和分类什么是DOM&#xff1f;DOM树的概念DOM对象2.获取DOM对象通过css选择器获取dom对象通过其他方法获取dom3.设置/修改DOM元素内容方法1. document.write() 方法方法2. 对象.innerText 属性方法3. 对象.innerHTML4.设置/修改DOM元素…

【TypeScript】的上手学习指南!

目录TS简介TypeScript是什么&#xff1f;为什么要推荐使用TypeScript生态支持安装TypeScriptTS简介 TypeScript是什么&#xff1f; TypeScript官网 简介&#xff1a;TypeScript是JavaScript类型的超集&#xff0c;它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计…

JVM之执行引擎详解

1、概述javac将.java文件编译成.class文件&#xff0c;然后.class文件通过类加载子系统将字节码加载到运行时数据区&#xff0c;这时运行时数据区中的数据是java字节码&#xff0c;字节码是不能直接运行在操作系统之上&#xff0c;因为字节码指令并非等价于本地机器指令&#x…

Python练习题

作业1&#xff1a;判断一个数&#xff0c;是否是2的指数 2的指数 0000 0010 0000 0001 0000 0100 0000 0011 0000 1000 0000 0111 0001 0000 0000 1111 提示&#xff1a;所有2的指数&#xff1a;n&(n - 1) 0 exponent int(…

mybatisplus复习(黑马)

学习目标能够基于MyBatisPlus完成标准Dao开发能够掌握MyBatisPlus的条件查询能够掌握MyBatisPlus的字段映射与表名映射能够掌握id生成策略控制能够理解代码生成器的相关配置一、MyBatisPlus简介MyBatisPlus&#xff08;简称MP&#xff09;是基于MyBatis框架基础上开发的增强型工…

Linux:进程间通信

目录 进程间通信目的 进程间通信分类 管道 System V IPC POSIX IPC 什么是管道 站在文件描述符角度-深度理解管道 管道使用 管道通信的四种情况 管道通信的特点 进程池管理 命名管道 创建一个命名管道 命名管道的打开规则 命名管道通信实例 匿名管道与命名管道的…

C语言实现三子棋【详解+全部源码】

大家好&#xff0c;我是你们熟悉的恒川 今天我们用C语言来实现三子棋 实现的过程很难&#xff0c;但我们一定要不放弃 三子棋1. 配置运行环境2. 三子棋游戏的初步实现2.1 建立三子棋分布模块2.2 创建一个名为board的二维数组并进行初始化2.3 搭建棋盘3. 接下来该讨论的事情3.1 …

【微信小程序】-- 网络数据请求(十九)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

Jmeter接口测试教程之【参数化技巧总结】,总有一个是你不知道的

目录&#xff1a;导读 一、随机值 二、随机字符串 三、时间戳 四、唯一字符串UUID 说起接口测试&#xff0c;相信大家在工作中用的最多的还是Jmeter。 大家看这个目录就知道jmeter的应用有多广泛了&#xff1a;https://www.bilibili.com/video/BV1e44y1X78S/? JMeter是一个…

C#:Krypton控件使用方法详解(第十一讲) ——kryptonScrollBar

今天介绍的Krypton控件中的kryptonScrollBar。下面介绍这个控件的外观属性&#xff1a;BackColor属性&#xff1a;表示控件的背景色&#xff0c;属性值如下图所示&#xff1a;BackgroundImage属性&#xff1a;表示用于该控件的背景图像&#xff0c;属性值可以为本地导入图片。B…

final修饰符使用中遇到的一些问题

文章目录final修饰符1. final不能用来修饰构造方法2. final修饰变量的一些注意问题2.1 final修饰成员变量2.2 final修饰引用类型2.2.1 演示代码中lombok链式编程介绍final修饰符 final具有“不可改变”的含义&#xff0c;它可以修饰非抽象类、非抽象成员方法和变量。 用final…

day3 动态的web 开发一个带有Servlet(Java小程序)的webapp

文章目录对于一个动态的web应用来说&#xff0c;一个请求和响应的过程有多少个角色参与&#xff0c;角色和角色之间有多少个协议开发一个带有Servlet&#xff08;Java小程序&#xff09;的webapp&#xff08;重点&#xff09;对于一个动态的web应用来说&#xff0c;一个请求和响…

【算法经典题集】二分(持续更新~~~)

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;算法经典题集&#x1f50a;本专栏涉及到的知识点或者题目是算法专栏的补充与应用&#x1f4aa;种一棵树最好是十年前其次是现在二分整数二分机器人…

java25种设计模式之工厂模式

Java设计模式 - 工厂模式 工厂模式是一种创建模式&#xff0c;因为此模式提供了更好的方法来创建对象。 在工厂模式中&#xff0c;我们创建对象而不将创建逻辑暴露给客户端。 例子 在以下部分中&#xff0c;我们将展示如何使用工厂模式创建对象。 由工厂模式创建的对象将是…

AI for Science系列(一) :飞桨加速CFD(计算流体力学)原理与实践

AIScience专栏由百度飞桨科学计算团队出品&#xff0c;给大家带来在AI科学计算领域中的一系列技术分享&#xff0c;欢迎大家关注和积极讨论&#xff0c;也希望志同道合的小伙伴加入飞桨社区&#xff0c;互相学习&#xff0c;一起探索前沿未知。 作为系列分享的第一篇&#xff0…

工作实战之代码规范

目录 前言 一、项目初期遇到的问题 1.项目整体架构搭建&#xff0c;微服务架构从头到尾分几层&#xff0c;每层负责那些功能 2.项目服务划分 3.项目服务命名约定 4.项目层级划分 5.项目包名约定 6.技术选型&#xff0c;包括微服务组件&#xff0c;日志组件&#xff0c;数…

WebGIS行政区炫酷特效——流光特效教程

先来看下效果: 图片截图: 流光特效的思路是从行政区的边界中随着时间不断的取若干段线条换成另一种高亮颜色。 流光的第一步首先是发光,发光的教程在这里: GIS矢量图形多边形地块行政区发光,阴影发光特效实现_疯狂的GISer的博客-CSDN博客 学会发光以后,接下来需要做的…

python实现图书管理系统(超详细)

python实现图书管理系统 图书管理系统python实现图书管理系统图书管理系统:图书的功能&#xff1a;现在开始添加数据:增加数据删除数据查询数据图书位置修改图书的借出图书的还回主体界面完整代码功能运行截图实验环境&#xff1a;PyCharm 2021.1.3 x64 专业版图书管理系统: 数…