当前位置:主页 > python教程 > Python图像分割

Python基于均值漂移算法和分水岭算法实现图像分割

发布:2023-03-04 10:00:01 59


为网友们分享了相关的编程文章,网友谷成益根据主题投稿了本篇教程内容,涉及到Python均值漂移算法、图像分割、Python、分水岭算法、图像分割、Python图像分割、Python图像分割相关内容,已被604网友关注,下面的电子资料对本篇知识点有更加详尽的解释。

Python图像分割

一.基于均值漂移算法的图像分割

均值漂移(Mean Shfit)算法是一种通用的聚类算法,最早是1975年Fukunaga等人在一篇关于概率密度梯度函数的估计论文中提出[1]。它是一种无参估计算法,沿着概率梯度的上升方向寻找分布的峰值。Mean Shift算法先算出当前点的偏移均值,移动该点到其偏移均值,然后以此为新的起始点,继续移动,直到满足一定的条件结束。

图像分割中可以利用均值漂移算法的特性,实现彩色的图像分割。在OpenCV中提供的函数为pyrMeanShiftFiltering(),该函数严格来说并不是图像分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在OpenCV中它的后缀是滤波“Filter”,而不是分割“segment”。该函数原型如下所示:

dst = pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]])

– src表示输入图像,8位三通道的彩色的图像

– dst表示输出图像,需同输入图像具有相同的大小和类型

– sp表示定义漂移物理空间半径的大小

– sr表示定义漂移色彩空间半径的大小

– maxLevel表示定义金字塔的最大层数

– termcrit表示定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合

均值漂移pyrMeanShiftFiltering()函数的执行过程是如下:

  • 构建迭代空间。以输入图像上任一点P0为圆心,建立以sp为物理空间半径,sr为色彩空间半径的球形空间,物理空间上坐标为x和y,色彩空间上坐标为RGB或HSV,构成一个空间球体。其中x和y表示图像的长和宽,色彩空间R、G、B在0至255之间。
  • 求迭代空间的向量并移动迭代空间球体重新计算向量,直至收敛。在上一步构建的球形空间中,求出所有点相对于中心点的色彩向量之和,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得向量和的终点就是该空间球体的中心点Pn,迭代结束。
  • 更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,完成一个点的色彩均值漂移。
  • 对输入图像src上其他点,依次执行上述三个步骤,直至遍历完所有点后,整个均值偏移色彩滤波完成。

下面的代码是图像均值漂移的实现过程:

# -*- coding: utf-8 -*-
# By: Eastmount
import cv2
import numpy as np
import matplotlib.pyplot as plt

#读取原始图像灰度颜色
img = cv2.imread('scenery.png') 

spatialRad = 50 #空间窗口大小
colorRad = 50 #色彩窗口大小
maxPyrLevel = 2 #金字塔层数

#图像均值漂移分割
dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel)

#显示图像
cv2.imshow('src', img)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

当漂移物理空间半径设置为50,漂移色彩空间半径设置为50,金字塔层数 为2,输出的效果图如图1所示。

当漂移物理空间半径设置为20,漂移色彩空间半径设置为20,金字塔层数 为2,输出的效果图如图2所示。对比可以发现,半径为20时,图像色彩细节大部分存在,半径为50时,森林和水面的色彩细节基本都已经丢失。

写到这里,均值偏移算法对彩色的图像的分割平滑操作就完成了,为了达到更好地分割目的,借助漫水填充函数进行下一步处理,在下一篇文章将详细介绍,这里只是引入该函数。完整代码如下所示:

# -*- coding: utf-8 -*-
# By: Eastmount
import cv2
import numpy as np
import matplotlib.pyplot as plt

#读取原始图像灰度颜色
img = cv2.imread('scenery.png') 

#获取图像行和列
rows, cols = img.shape[:2]

#mask必须行和列都加2且必须为uint8单通道阵列
mask = np.zeros([rows+2, cols+2], np.uint8) 

spatialRad = 100 #空间窗口大小
colorRad = 100   #色彩窗口大小
maxPyrLevel = 2  #金字塔层数

#图像均值漂移分割
dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel)

#图像漫水填充处理
cv2.floodFill(dst, mask, (30, 30), (0, 255, 255),
              (100, 100, 100), (50, 50, 50),
              cv2.FLOODFILL_FIXED_RANGE)

#显示图像
cv2.imshow('src', img)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

输出的效果图如图3所示,它将天空染成黄色。

二.基于分水岭算法的图像分割

图像分水岭算法(Watershed Algorithm)是将图像的边缘轮廓转换为“山脉”,将均匀区域转换为“山谷”,从而提升分割效果的算法[3]。分水岭算法是基于拓扑理论的数学形态学的分割方法,灰度图像根据灰度值把像素之间的关系看成山峰和山谷的关系,高亮度(灰度值高)的地方是山峰,低亮度(灰度值低)的地方是山谷。接着给每个孤立的山谷(局部最小值)不同颜色的水(Label),当水涨起来,根据周围的山峰(梯度),不同的山谷也就是不同颜色的像素点开始合并,为了避免这个现象,可以在水要合并的地方建立障碍,直到所有山峰都被淹没。所创建的障碍就是分割结果,这个就是分水岭的原理[3]。

分水岭算法的计算过程是一个迭代标注过程,主要包括排序和淹没两个步骤。由于图像会存在噪声或缺失等问题,该方法会造成分割过度。OpenCV提供了watershed()函数实现图像分水岭算法,并且能够指定需要合并的点,其函数原型如下所示:

markers = watershed(image, markers)

– image表示输入图像,需为8位三通道的彩色的图像

– markers表示用于存储函数调用之后的运算结果,输入/输出32位单通道图像的标记结构,输出结果需和输入图像的尺寸和类型一致。

下面是分水岭算法实现图像分割的过程。假设存在一幅彩色硬币图像,如图4所示,硬币相互之间挨着。

第一步,通过图像灰度化和阈值化处理提取图像灰度轮廓,采用OTSU二值化处理获取硬币的轮廓。

# -*- coding: utf-8 -*-
# By: Eastmount
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取原始图像
img = cv2.imread('coin.jpg')

#图像灰度化处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像阈值化处理
ret, thresh = cv2.threshold(gray, 0, 255, 
cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

#显示图像
cv2.imshow('src', img)
cv2.imshow('res', thresh)
cv2.waitKey()
cv2.destroyAllWindows()

输出结果如图5所示。

第二步,通过形态学开运算过滤掉小的白色噪声。同时,由于图像中的硬币是紧挨着的,所以不能采用图像腐蚀去掉边缘的像素,而是选择距离转换,配合一个适当的阈值进行物体提取。这里引入一个图像膨胀操作,将目标边缘扩展到背景,以确定结果的背景区域。

# -*- coding: utf-8 -*-
# By: Eastmount
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取原始图像
img = cv2.imread('coin.jpg')

#图像灰度化处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像阈值化处理
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

#图像开运算消除噪声
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)

#图像膨胀操作确定背景区域
sure_bg = cv2.dilate(opening,kernel,iterations=3)

#距离运算确定前景区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)

#寻找未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

#用来正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']

#显示图像
titles = ['原始图像', '阈值化', '开运算',
          '背景区域', '前景区域', '未知区域']  
images = [img, thresh, opening, sure_bg, sure_fg, unknown]  
for i in range(6):  
   plt.subplot(2,3,i+1), plt.imshow(images[i], 'gray')  
   plt.title(titles[i])  
   plt.xticks([]),plt.yticks([])  
plt.show()

输出结果如图6所示,包括原始图像、阈值化处理、开运算、背景区域、前景区域、未知区域等。由图可知,在使用阈值过滤的图像里,确认了图像的硬币区域,而在有些情况,可能对前景分割更感兴趣,而不关心目标是否需要分开或挨着,那时可以采用腐蚀操作来求解前景区域。

第三步,当前处理结果中,已经能够区分出前景硬币区域和背景区域。接着我们创建标记变量,在该变量中标记区域,已确认的区域(前景或背景)用不同的正整数标记出来,不确认的区域保持0,使用cv2.connectedComponents()函数来将图像背景标记成0,其他目标用从1开始的整数标记。注意,如果背景被标记成0,分水岭算法会认为它是未知区域,所以要用不同的整数来标记。

最后,调用watershed()函数实现分水岭图像分割,标记图像会被修改,边界区域会被标记成0,完整代码如下所示。

# -*- coding: utf-8 -*-
# By: Eastmount
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取原始图像
img = cv2.imread('coin.jpg')

#图像灰度化处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像阈值化处理
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

#图像开运算消除噪声
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)

#图像膨胀操作确定背景区域
sure_bg = cv2.dilate(opening,kernel,iterations=3)

#距离运算确定前景区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)

#寻找未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

#标记变量
ret, markers = cv2.connectedComponents(sure_fg)

#所有标签加一,以确保背景不是0而是1
markers = markers+1

#用0标记未知区域
markers[unknown==255]=0

#分水岭算法实现图像分割
markers = cv2.watershed(img, markers)
img[markers == -1] = [255,0,0]

#用来正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']

#显示图像
titles = ['标记区域', '图像分割']  
images = [markers, img]  
for i in range(2):  
   plt.subplot(1,2,i+1), plt.imshow(images[i], 'gray')  
   plt.title(titles[i])  
   plt.xticks([]),plt.yticks([])  
plt.show()

最终分水岭算法的图像分割如图7所示,它将硬币的轮廓成功提取。

图8是采用分水岭算法提取图像Windows中心轮廓的效果图。

分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时应当看出,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证。另外,分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。

三.总结

本文主要讲解了图像分割方法,包括基于均值漂移算法的图像分割方法、基于分水岭算法的图像分割方法,通过这些处理能有效分割图像的背景和前景,识别某些图像的区域。

以上就是Python基于均值漂移算法和分水岭算法实现图像分割的详细内容,更多关于Python图像分割的资料请关注码农之家其它相关文章!


参考资料

相关文章

  • Python3实现的简单验证码识别功能示例

    发布:2022-10-26

    给网友们整理关于Python3的教程,这篇文章主要介绍了Python3实现的简单验证码识别功能,涉及Python针对验证码图片识别处理相关操作技巧,需要的朋友可以参考下


  • 怎样管理多个Python版本和虚拟环境

    发布:2020-01-26

    这篇文章主要介绍了详解如何管理多个Python版本和虚拟环境,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧


  • Python面向对象类编写细节分析(类,方法,继承,超类,接口)

    发布:2019-06-11

    这篇文章主要介绍了Python面向对象类编写细节,较为详细的分析了Python面向对象程序设计中类,方法,继承,超类,接口等相关概念、使用技巧与注意事项,需要的朋友可以参考下


  • Mac上Python使用ffmpeg完美解决方案(避坑必看!)

    发布:2023-04-14

    ffmpeg是一个强大的开源命令行多媒体处理工具,下面这篇文章主要给大家介绍了关于Mac上Python使用ffmpeg完美解决方案的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下


  • Python初始化列表的方法 如何初始化列表

    发布:2019-06-28

    Python是一种非常灵活的语言,可以通过多种方式执行单个任务,例如,可以通过多种方式执行初始化列表。下面本篇文章就来带大家了解几种Python初始化的方法,并比较一下这几种方法的效率,


  • python打包exe能运行但是没有结果解决方案

    发布:2019-06-27

    在本篇文章里小编给大家整理了关于python打包exe能运行但是没有结果解决方案以及相关扩展知识内容,需要的朋友们参考学习下。


  • Python函数常见几种return返回值类型

    发布:2023-03-02

    本文主要介绍了Python函数常见几种return返回值类型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


  • python常用程序算法

    发布:2021-04-27

    这篇文章主要介绍了python常用程序算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


网友讨论