图像处理之分割图像

我们在处理图像的时候,常常需要将图像的前景和背景做不同的处理,这时需要将前景和背景分割开。关于图像分割的方法我知道的有三种方法: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)

# 设定一个初始类中心flags
flags = cv2.KMEANS_RANDOM_CENTERS
# 应用K-means
compactness,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 + 1
markers[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 np
import cv2
from 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


  • 14
    点赞
  • 110
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
数字图像处理中的图像分割是将数字图像划分成互不相交的区域的过程,它是由图像处理图像分析的关键步骤。现有的图像分割方法主要分以下几类:基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法以及基于特定理论的分割方法等。其中,基于阈值的分割方法是最简单的一种方法,它是根据像素灰度值的大小将图像分成两个部分,即目标和背景。基于区域的分割方法是将图像分成若干个区域,每个区域内的像素具有相似的特征,例如灰度、纹理等。基于边缘的分割方法是根据图像中像素灰度值的不连续性来进行分割,例如Canny算子可以检测出图像中的边缘。基于特定理论的分割方法是根据特定的理论来进行分割,例如基于聚类的分割方法、基于小波变换的分割方法等。 在实际应用中,常用的图像分割算法包括阈值分割、区域生长、分水岭算法等。其中,阈值分割是最常用的一种方法,它是根据像素灰度值的大小将图像分成两个部分,即目标和背景。区域生长是一种基于像素相似性的分割方法,它从一个或多个种子像素开始,逐渐将相邻的像素加入到同一区域中。分水岭算法是一种基于图像梯度的分割方法,它将图像看作一个地形图,通过计算梯度来确定图像中的山峰和山谷,从而将图像分割成若干个区域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值