图像处理之分割图像

来源:互联网 发布:博雅软件集团2017 编辑:程序博客网 时间:2024/06/05 00:38

我们在处理图像的时候,常常需要将图像的前景和背景做不同的处理,这时需要将前景和背景分割开。关于图像分割的方法我知道的有三种方法:K-means、分水岭和GrabCut算法进行物体分割。不能够肯定的比较出谁优谁劣,各种算法是分各种场合以及设定参数的优化。在此,只是简单介绍,学习之路任重而道远!

K-means方法进行分割:

它是一种最常用的聚类算法。因为,人们不需要手动的为数据集里的每个个体添加标签,能自动的发现集群结构,进行分类。是一种无监督的学习。那么是什么定义了集群的呢?答案是通过中心和形状定义的。然后,通过打分判断依据是:在这个集群中的分数高于在其他聚类中的分数和与本集群中心点比其他集群中心点更相似。具体的步骤是:首先,把观测分配给最近的中心点。然后,把集群中心点修改为被分配给原中心点观测的均值,反复这两步操作,直到全部收敛。

关于OpenCV下的kmean算法,函数为cv2.kmeans()
函数的格式为:kmeans(data, K, bestLabels, criteria, attempts, flags)

其中,K(分类数)和 attempts(Kmeans算法重复次数)是需要根据具体的图像进行优化的参数。像bestLabels预设分类标签可以不需要用None表示,criteria为迭代停止的模式选择,格式为(type,max_iter,epsilon),其中type又有两种选择:cv2.TERM_CRITERIA_EPS :精确度(误差)满足epsilon停止和cv2.TERM_CRITERIA_MAX_ITER:迭代次数超过max_iter停止,也可以两者结合,满意任意一个就结束。而flags(初始类中心选择),有两种方法:cv2.KMEANS_PP_CENTERS ; cv2.KMEANS_RANDOM_CENTERS

下面,就尝试一下修改K(分类数)和 attempts(Kmeans算法重复次数)参数进行测试。

先将K设为默认值,调attempts次数。

# 以灰色导入图像img = cv2.imread('messi5.jpg',0)#image read be 'gray'plt.subplot(221),plt.imshow(img,'gray'),plt.title('original')plt.xticks([]),plt.yticks([])# 改变图像的维度img1 = img.reshape((img.shape[0]*img.shape[1],1))img1 = np.float32(img1)# 设定一个criteria,criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,10,1.0)# 设定一个初始类中心flagsflags = cv2.KMEANS_RANDOM_CENTERS# 应用K-meanscompactness,labels,centers = cv2.kmeans(img1,2,None,criteria,5,flags)compactness_1,labels_1,centers_1 = cv2.kmeans(img1,2,None,criteria,10,flags)compactness_2,labels_2,centers_2 = cv2.kmeans(img1,2,None,criteria,15,flags)img2 = labels.reshape((img.shape[0],img.shape[1]))img3 = labels_1.reshape((img.shape[0],img.shape[1]))img4 = labels_2.reshape((img.shape[0],img.shape[1]))plt.subplot(222),plt.imshow(img2,'gray'),plt.title('kmeans_attempts_5')plt.xticks([]),plt.yticks([])plt.subplot(223),plt.imshow(img3,'gray'),plt.title('kmeans_attempts_10')plt.xticks([]),plt.yticks([])plt.subplot(224),plt.imshow(img4,'gray'),plt.title('kmeans_attempts_15')plt.xticks([]),plt.yticks([])plt.savefig("kmeans_attempts.png")plt.show()



可以看出attempts次数不同,是会造成图像分割差异的。

再来调K值,这里将attempts次数设为10.得到的图像为:


也可以看出K初始值不同,同样造成图像分割差异。所以,可以说这两个参数的优化是很重要的,但是,也不容易优化。看下一种方法:

分水岭算法

之所以叫分水岭算法,是因为它里面有“水”的概念。把图像中低密度的区域(变化很少)想象成山谷,图像中高密度的区域(变化很多)想象成山峰。开始向山谷中注入水直到不同的山谷中的水开始汇集。为了阻止不同山谷的水汇聚,可以设置一些栅栏,最后得到的栅栏就是图像分割。

img = cv2.imread("water_coins.jpg")gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 将颜色转变为灰色之后,可为图像设一个阈值,将图像二值化。ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)# 下面用morphologyEx变换来除去噪声数据,这是一种对图像进行膨胀之后再进行腐蚀的操作,它可以提取图像特征:kernel = np.ones((3,3), np.uint8)opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations= 2)# 通过对morphologyEx变换之后的图像进行膨胀操作,可以得到大部分都是背景的区域:sure_bg = cv2.dilate(opening, kernel, iterations=3)# 接着通过distanceTransform来获取确定前景区域,原理是应用一个阈值来决定哪些区域是前景,越是远离背景区域的边界的点越可能属于前景。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和sure_bg的集合相减得到。sure_fg = np.uint8(sure_fg)unknown = cv2.subtract(sure_bg, sure_fg)# 现在有了这些区域,就可以设定“栅栏”来阻止水汇聚了,这通过connectedComponents函数来完成ret, markers = cv2.connectedComponents(sure_fg)# 在背景区域上加1, 这会将unknown区域设置为0:markers = markers + 1markers[unknown==255] = 0# 最后打开门,让水漫起来并把栅栏绘成红色markers = cv2.watershed(img, markers)img[markers == -1] = [255, 0, 0]plt.imshow(img), plt.xticks([]),plt.yticks([])plt.show()



能够看出还是能大多数完整分割。

接下来介绍GrabCut算法进行对图像的分割处理。

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

import numpy as npimport cv2from matplotlib import pyplot as plt# 首先加载图片,然后创建一个与所加载图片同形状的掩模,并用0填充。img = cv2.imread("messi5.jpg")mask = np.zeros(img.shape[:2], np.uint8)# 然后创建以0填充的前景和背景模型:bgdModel = np.zeros((1, 65), np.float64)fgdModel = np.zeros((1, 65), np.float64)# 在实现GrabCut算法前,先用一个标识出想要隔离的对象的矩形来初始化它,这个矩形我们用下面的一行代码定义(x,y,w,h):rect = (100, 50, 421, 378)# 接下来用指定的空模型和掩摸来运行GrabCut算法#mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT) # 5是指算法的迭代次数。# 然后,我们再设定一个掩模,用来过滤之前掩模中的值(0-3)。值为0和2的将转为0,值为1和3的将转化为1,这样就可以过滤出所有的0值像素(背景)。mask2 = np.where((mask==2)|(mask==0), 0, 1).astype("uint8")img = img * mask2[:, :, np.newaxis]# 最后可视化展现分割前后的图像plt.subplot(1, 2, 1)plt.imshow(img)plt.title("grabcut"), plt.xticks([]), plt.yticks([])plt.subplot(1, 2, 2)plt.imshow(cv2.imread("messi5.jpg"))plt.title("original"), plt.xticks([]), plt.yticks([])plt.savefig("grabcut.png")



可以看出来分割的并不完整,而且头发和手都没有被区分到前景中来,这是因为,在设定矩形的时候需要不断优化的,且因每一张图像都有差异,所以矩形的范围也是有差异的。还好在github上找到了一个grabcut算法脚本,能完美的解决这个问题。并用他的代码进行测试。如下图:





参考:

《OpenCV3计算机视觉Python语言实现》

OpenCV帮助文档:

http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_grabcut/py_grabcut.html

https://github.com/opencv/opencv/blob/master/samples/python/grabcut.py


阅读全文
0 0