Python基于均值漂移算法和分水岭算法实现图像分割
作者:Eastmount
一.基于均值漂移算法的图像分割
均值漂移(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图像分割的资料请关注脚本之家其它相关文章!