亲宝软件园·资讯

展开

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

Eastmount 人气:0

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

均值漂移(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()函数的执行过程是如下:

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

# -*- 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中心轮廓的效果图。

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

三.总结

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

加载全部内容

相关教程
猜你喜欢
用户评论