图像分割算法

news2024/11/22 18:22:59

文章目录

  • 前言
  • 1. 基于区域的分割方法
    • 1.1 区域生长算法
    • 1.2 区域分裂合并算法
    • 1.3 分水岭算法
      • 1.3.1 分水岭算法原理
      • 1.3.2 opencv-python中分水岭算法的应用
  • 2. 基于图的分割方法
      • 2.1 Grabcut图像分割
  • 源码仓库地址

前言

图像分割是指将图像分成若干互不重叠的子区域,使得同一个子区域内的特征具有一定相似性,不同子区域的特征呈现较为明显的差异。之前介绍了基于阈值的分割方法,比如Otsu法等;基于边缘检测的分割方法,比如Sobel算子、Canny算子等。下面介绍基于区域的分割方法基于图的分割方法

1. 基于区域的分割方法

基于区域的分割算法将具有相似特征的像素集合聚集构成一个区域,这个区域中的相邻像素之间具有相似的性质,主要包括区域生长算法、区域分裂合并算法和分水岭算法等。

1.1 区域生长算法

区域生长的基本思想是将具有相似性质的像素集合起来构成区域。具体先对每个需要分割的区域找一个种子像素作为生长起点,然后将种子像素和周围邻域中与种子像素有相同或相似性质的像素(根据某种事先确定的生长或相似准则来判定)合并到种子像素所在的区域中。将这些新像素当作新的种子继续上面的过程,直到没有满足条件的像素可被包括进来,这样一个区域就生长成了。

区域生长实现步骤如下:

  1. 对图像顺序扫描,找到第1个还没有归属的像素,设该像素为(x0, y0);
  2. 以(x0,y0)为中心,考虑(x0,y0)的4邻域或者8邻域像素(x,y)与种子像素的灰度值之差的绝对值小于某个阈值T,如果满足条件,将(x,y)与(x0,y0)合并(在同一区域内),同时将(x,y)压入堆栈;
  3. 从堆栈中取出一个像素,把它当作(x0, y0)返回到步骤2;
  4. 当堆栈为空时,返回到步骤1;
  5. 重复步骤1-4直到图像中的每个点都有归属时,生长结束。

下图为测试效果(左侧为原图灰度图像,右侧为区域生长图像):

在这里插入图片描述

需要注意的是:当选取的种子不同时,得到的区域生长图像也会不同。

1.2 区域分裂合并算法

区域生长是从某个或者某些像素点出发,最后得到整个区域,进而实现目标提取。而分裂合并可以看做是区域生长的逆过程:从整个图像出发,不断分裂得到各个子区域,然后再把前景区域合并,实现目标提取。分裂合并的假设是对于一幅图像,前景区域由一些相互连通的像素组成。因此,如果把一幅图像分裂到像素级,那么就可以判定该像素是否为前景像素。当所有像素点或者子区域完成判断以后,把前景区域或者像素合并就可得到前景目标。

假定一幅图像分为若干区域,按照有关区域的逻辑词P的性质,各个区域上所有的像素将是一致的。区域分裂合并的算法如下:

  1. 将整幅图像设置为初始区域;
  2. 选择一个区域R,若P(R)错误,则将该区域分为4个子区域;
  3. 考虑图像中任意两个或更多的邻接子区域R1,R2,…,Rn;
  4. 如果P(R1∪R2∪…∪Rn)正确,则将这n个区域合并为一个区域;
  5. 重复上述步骤,直到不能再进行区域分裂合并。

下图为测试效果:

在这里插入图片描述

1.3 分水岭算法

1.3.1 分水岭算法原理

图像的灰度空间很像地球表面的整个地理结构,每个像素的灰度值代表高度。其中的灰度值较大的像素连成的线可以看做山脊,也就是分水岭。其中的水就是用于二值化的gray threshold level,二值化阈值可以理解为水平面,比水平面低的区域会被淹没,刚开始用水填充每个孤立的山谷(局部最小值)。当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。

在该算法中,空间上相邻并且灰度值相近的像素被划分为一个区域。

分水岭算法的整个过程如下:

  1. 把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值;
  2. 找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点;
  3. 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类;
  4. 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素进行了分区。

用上面的算法对图像进行分水岭运算,由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域。

其中的解决方法有:

(1)对图像进行高斯平滑操作,抹除很多小的最小值,这些小分区就会合并。

(2)不从最小值开始增长,可以将相对较高的灰度值像素作为起始点(需要用户手动标记),从标记处开始进行淹没,则很多小区域都会被合并为一个区域,这被称为基于图像标记(mark)的分水岭算法

1.3.2 opencv-python中分水岭算法的应用

在OpenCV中,我们需要给不同区域贴上不同的标签。用大于1的整数表示我们确定为前景或对象的区域,用1表示我们确定为背景或非对象的区域,最后用0表示我们无法确定的区域。然后应用分水岭算法,我们的标记图像将被更新,更新后的标记图像的边界像素值为-1。

具体步骤如下:

  1. 对图进行灰度化和二值化得到二值图像;
  2. 通过膨胀得到确定的背景区域,通过距离转换得到确定的前景区域,剩余部分为不确定区域;
  3. 对确定的前景图像进行连接组件处理,得到标记图像;
  4. 根据标记图像对原图像应用分水岭算法,更新标记图像。

测试代码如下:

import numpy as np
import cv2
import matplotlib.pyplot as plt

img = cv2.imread('lenna.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

# noise removal
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

sure_bg = cv2.dilate(opening, kernel, iterations=2)  # sure background area
sure_fg = cv2.erode(opening, kernel, iterations=2)  # sure foreground area
unknown = cv2.subtract(sure_bg, sure_fg)  # unknown area

# Perform the distance transform algorithm
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
# Normalize the distance image for range = {0.0, 1.0}
cv2.normalize(dist_transform, dist_transform, 0, 1.0, cv2.NORM_MINMAX)

# Finding sure foreground area
ret, sure_fg = cv2.threshold(dist_transform, 0.5*dist_transform.max(), 255, 0)

# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)

# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers+1
# Now, mark the region of unknown with zero
markers[unknown==255] = 0

markers_copy = markers.copy()
markers_copy[markers==0] = 150  # 灰色表示背景
markers_copy[markers==1] = 0    # 黑色表示背景
markers_copy[markers>1] = 255   # 白色表示前景

markers_copy = np.uint8(markers_copy)

# 使用分水岭算法执行基于标记的图像分割,将图像中的对象与背景分离
markers = cv2.watershed(img, markers)
img[markers==-1] = [0,0,255]  # 将边界标记为红色

plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('Watershed_image'), plt.axis('off')
plt.show()

效果如下:

在这里插入图片描述

2. 基于图的分割方法

此类方法把图像分割问题与图的最小割(min cut)问题相关联。首先将图像映射为带权无向图G=<V,E>,图中每个节点N∈V对应于图像中的每个像素,每条边∈E连接着一对相邻的像素,边的权值表示了相邻像素之间在灰度、颜色或纹理方面的非负相似度。而对图像的一个分割s就是对图的一个剪切,被分割的每个区域C∈S对应着图中的一个子图。而分割的最优原则就是使划分后的子图在内部保持相似度最大,而子图之间的相似度保持最小。基于图的分割方法的本质就是移除特定的边,将图划分为若干子图从而实现分割,常用的方法有GraphCut,GrabCut和Random Walk等。

2.1 Grabcut图像分割

Grabcut是基于图割(graph cut)实现的图像分割算法,它需要用户输入一个bounding box作为分割目标位置,实现对目标与背景的分离/分割。Grabcut分割速度快,效果好,支持交互操作,因此在很多APP图像分割/背景虚化的软件中经常使用。

算法流程如下:

  1. 在图片中定义含有(一个或多个)物体的矩形;
  2. 矩形外的区域被自动认为是背景;
  3. 对于用户定义的矩形区域,可用背景中数据来区分是前景还是背景;
  4. 用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或背景;
  5. 图像中的每一个像素都被看作通过虚拟边与周围像素连接,而每条边都有一个属于前景或背景的概率,这基于它和周围像素颜色上的相似性;
  6. 每一个像素(即算法中的节点)会与一个前景或背景节点连接;
  7. 在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。

OpenCV中使用cv2.grabCut()函数来实现图像分割,其函数原型如下:

cv2.grabCut(img, rect, mask, bgdModel, fgdModel, iterCount, mode = GC_EVAL)

参数说明:

  • img:输入的三通道图像;

  • rect:表示roi区域;

  • mask:输入的单通道图像,初始化方式为GC_INIT_WITH_RECT表示ROI区域可以被初始化为:

    GC_BGD:定义为明显的背景像素 0

    GC_FGD:定义为明显的前景像素 1

    GC_PR_BGD:定义为可能的背景像素 2

    GC_PR_FGD:定义为可能的前景像素 3

  • bgdModel:表示临时背景模型数组;

  • fgdModel:表示临时前景模型数组;

  • iterCount:表示图割算法迭代次数, 次数越多,效果越好;

  • mode:当使用用户提供的roi时使用GC_INIT_WITH_RECT。

测试代码如下:

import cv2
import numpy as np
from matplotlib import pyplot as plt 


img = cv2.imread('lenna.jpg')
r = cv2.selectROI('input', img, False)  # 返回 (x_min, y_min, w, h)
print("input:", r)

# roi区域
roi = img[int(r[1]):int(r[1] + r[3]), int(r[0]):int(r[0] + r[2])]

# 原图mask
mask = np.zeros(img.shape[:2], dtype=np.uint8)

# 矩形roi
rect = (int(r[0]), int(r[1]), int(r[2]), int(r[3]))  # 包括前景的矩形,格式为(x,y,w,h)

bgdmodel = np.zeros((1, 65), np.float64)  # bg模型的临时数组
fgdmodel = np.zeros((1, 65), np.float64)  # fg模型的临时数组

cv2.grabCut(img, mask, rect, bgdmodel, fgdmodel, 11, mode=cv2.GC_INIT_WITH_RECT)

# 提取前景和可能的前景区域
mask2 = np.where((mask == 1) + (mask == 3), 255, 0).astype('uint8')
print(mask2.shape)

result = cv2.bitwise_and(img, img, mask=mask2)

plt.subplot(121), plt.imshow(cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)), plt.title('roi_img'), plt.axis('off')
plt.subplot(122), plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)), plt.title('Grabcut_image'), plt.axis('off')
plt.show()


效果如下:

在这里插入图片描述


源码仓库地址

🌼图像处理、机器学习的常用算法汇总

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

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

相关文章

如何卸载MySQL数据库以及删除所有有关信息

目录 前言 第一步 卸载mysql程序 第二步 删除安装目录的mysql项目 第三步 删除MySQL的相关注册表 第四步 删除C盘下的 C:\ProgramData\MySQL所有的文件 第五步 删除 C:\Documents and Settings\All Users\Application Data\MySQL 下的文件夹 第六步 重启电脑 尾语 前言…

使用crictl pull时报错:“unknown service runtime.v1alpha2.ImageService”

如有错误&#xff0c;敬请谅解&#xff01; 此文章仅为本人学习笔记&#xff0c;仅供参考&#xff0c;如有冒犯&#xff0c;请联系作者删除&#xff01;&#xff01; 引言&#xff1a; crictl 是 kubernetes cri-tools 的一部分&#xff0c;是专门为 kubernetes 使用 …

【详解】String、StringBuffer、StringBuilder的基本用法及区别

一、String 1.常用的输入方式 Scanner in new Scanner&#xff08;System.in&#xff09;; //输入方法一 String s1 in.next(); //in.next(): 读到空格就停止扫描&#xff08;输入&#xff09;。//输入方法二 String s2 in.nextLine(); //in.nextLine():读到回车就停止扫描…

Avalon 学习系列 (一) —— 初步入门

相关链接 Avalon github地址 Avalon 快速入门 基于 Avalon的组件库-OniUI Avalon 入门教程 Avalon CDN 简介 Avalon 是司徒正美开发和维护的一款基于虚拟 DOM 与属性劫持的迷你、 易用、 高性能 的 前端 MVVM 框架&#xff0c;最早发布于2012.09.15。 其拥有超优秀的兼容性&a…

Python中的模块包第三方库详解

模块&包 模块 一个.py文件就是一个模块&#xff0c;里面是一些函数和变量&#xff0c;需要的时候可以导入。 模块命名规范: 1.以英文开头&#xff0c;不出现中文 2.模块名不应与系统内置函数重名 包 包本身就是一个文件夹&#xff0c;如果文件夹内有__init__.py文件&…

浅谈iic时序

一、iic通信核心要点 在iic通信中&#xff0c;进行数据传输的时候&#xff0c;遵循在scl时钟线高的时候保持sda电平稳定&#xff0c;这个电平值就是要写入的值&#xff0c;然后&#xff0c;在scl时钟线拉低的时候去改变sda上的值&#xff0c;达到自己想要输出的值 所以说&…

如何做架构设计

1、设计很重要 我们可以看一下周边的事物&#xff0c;那些好的东西&#xff0c;他们并不会天然存在&#xff0c;都是被设计出来的&#xff0c;因此设计就是创造和改善事物的重要过程。设计的重要之处在于&#xff0c;最初的设计往往决定最终的结果&#xff0c;甚至决定着事物的…

端口扫描伪装技术实践

端口扫描伪装技术实践 1.-f&#xff08;分段传输&#xff09;2.-mtu&#xff08;使用指定的MTU&#xff09;3.-D&#xff08;使用诱饵主机隐蔽扫描&#xff09;4.--source-port&#xff08;源端口欺骗&#xff09;5.--data-length &#xff08;发送报文时附加随机数据&#xff…

入职字节外包一个月,我离职了····

有一种打工人的羡慕&#xff0c;叫做“大厂”。 真是年少不知大厂香&#xff0c;错把青春插稻秧。 但是&#xff0c;在深圳有一群比大厂员工更庞大的群体&#xff0c;他们顶着大厂的“名”&#xff0c;做着大厂的工作&#xff0c;还可以享受大厂的伙食&#xff0c;却没有大厂…

电子元器件解析02之电容(二)——电容分类与应用场景

书接上文&#xff1a;电子元器件解析02之电容(一)——定义与性能参数&#xff1a;https://blog.csdn.net/weixin_42837669/article/details/131142286 摘要 本文总结了各种不同介质电容的特性&#xff0c;包括陶瓷电容、电解电容、薄膜电容等&#xff1b;同时对一些特殊场合的电…

Hadoop Distributed System (HDFS) 写入和读取流程

一、HDFS HDFS全称是Hadoop Distributed System。HDFS是为以流的方式存取大文件而设计的。适用于几百MB&#xff0c;GB以及TB&#xff0c;并写一次读多次的场合。而对于低延时数据访问、大量小文件、同时写和任意的文件修改&#xff0c;则并不是十分适合。 目前HDFS支持的使用…

HTTP协议【网络基础/应用层】

文章目录 1. 网络基础 TCP/IP2. 与HTTP密切相关的协议2.1 负责传输的 IP 协议路由选择 2.2 确保可靠性的 TCP 协议三次握手 2.3 负责域名解析的 DNS 服务2.4 各种协议和 HTTP 协议的关系 3. URL和编码问题3.1 介绍格式 3.2 编码问题 4. 初识 HTTP 协议4.1 C-S 模式4.2 通过响应…

vue中开发包、生产包、全局包的区别以及安装语法

目录 开发包 (devDependencies) 安装方法 生产包 (dependencies) 安装方法 全局包 (Global build) 安装方法 vue中有三种不同类型的包&#xff1a;开发包 (Development build)&#xff0c;生产包 (Production build) 和全局包 (Global build)。下面我们分别解释它们的区别…

EVE-NG 平台搭建以及发布“Ruijieroute”“Ruijieswitch”

下载相关资源 1、锐捷模拟器镜像文件&#xff1a;锐捷镜像官方下载链接 2、VMware虚拟化平台&#xff1a;可以是VMware vSphere、也可以是VMware Workstation&#xff1a;VMware Workstation Pro官方下载链接 当然&#xff0c;我们也可以使用其他虚拟化平台&#xff1a;Orac…

什么,你还在用 momentJs 处理相对时间

我想&#xff0c;下面这段代码&#xff0c;你是不是在开发中常常这样使用来计算距离现在过去了多长时间&#xff1a; import moment from moment // 61k (gzipped:19.k) function Relative(props) {const timeString moment(props.date).fromNow()return <>{timeString…

Tomcat实现ThreadPoolExecutor和JDK线程池区别

1.1 tomcat线程池和juc线程池流程 jdk 线程池策略&#xff1a; 当线程池中线程数量小于 corePoolSize&#xff0c;每来一个任务&#xff0c;就会创建一个线程执行这个任务当前线程池线程数量大于等于 corePoolSize&#xff0c;则每来一个任务&#xff0c;会尝试将其添加到任务…

深入理解深度学习——注意力机制(Attention Mechanism):位置编码(Positional Encoding)

分类目录&#xff1a;《深入理解深度学习》总目录 相关文章&#xff1a; 注意力机制&#xff08;AttentionMechanism&#xff09;&#xff1a;基础知识 注意力机制&#xff08;AttentionMechanism&#xff09;&#xff1a;注意力汇聚与Nadaraya-Watson核回归 注意力机制&#…

【CSS---定位基础篇】

CSS---定位基础篇 CSS-----基础定位:一 、 学习定位原因&#xff1a;&#xff08;定位的作用&#xff09;二 、定位组成&#xff1a;2.1 四种定位模式&#xff1a;&#xff08;1&#xff09;静态定位&#xff08;了解&#xff09;&#xff1a;&#xff08;2&#xff09;相对定位…

8.1 正弦波振荡电路(1)

正弦波振荡电路是在没有外加输入信号的情况下&#xff0c;依靠电路自激振荡而产生正弦波输出电压的电路。它广泛地应用于测量、遥控、通讯、自动控制、热处理和超声波电焊等加工设备之中&#xff0c;也作为模拟电子电路的测试信号。 一、概述、 1、产生正弦波振荡的条件 在负…

“不擅长开发,所以选择测试”,软件测试面试时这么说就凉了...

见过不少软件测试岗位&#xff0c;在面试时&#xff0c;问到选择软件测试作为职业的原因时 有不少测试工程师会回答&#xff1a;因为不擅长或不喜欢开发的工作。 这个想法&#xff0c;这个回答&#xff0c;就已经在面试官眼里打低分了。 现在网上也有不少类似观点 “不喜欢开…