目标
在本节中将要学习:
GrabCut 算法原理,使用 GrabCut 算法提取图像的前景
创建一个交互是程序完成前景提取
原理
GrabCut 算法是由微软剑桥研究院的 Carsten_Rother,Vladimir_Kolmogorov和 Andrew_Blake 在文章《GrabCut”: interactive foreground extraction using iterated graph cuts》中共同提出的。 此算法在提取前景的操作过程中需要很少的人机交互,结果非常好。
从用户的角度来看它到底是如何工作的呢? 开始时用户需要用一个矩形将前景区域框住(前景区域应该完全被包括在矩形框内部)。 然后算法进行迭代式分割直达达到最好结果。 但是有时分割的结果不够理想,比如把前景当成了背景, 或者把背景当成了前景。 在这种情况下,就需要用户来进行修改了。 用户只需要在不理想的部位画一笔(点一下鼠标)就可以了。 画一笔就等于在告诉计算机: “嗨,老兄,你把这里弄反了,下次迭代的时候记得改过来呀!”。 然后,在下一轮迭代的时候就会得到一个更好的结果了。
如下图所示。运动员和足球被蓝色矩形包围在一起。 其中做了几个修正, 白色画笔表明这里是前景,黑色画笔表明这里是背景。 最后得到了一个很好的结果。
在整个过程中到底发生了什么呢?
用户输入一个矩形。矩形外的所有区域肯定都是背景(在前面已经提到,所有的对象都要包含在矩形框内)。 矩形框内的东西是未知的。同样用户确定前景和背景的任何操作都不会被程序改变。
计算机会对输入图像做一个初始化标记。它会标记前景和背景像素。
使用一个高斯混合模型(GMM)对前景和背景建模。
根据输入,GMM 会学习并创建新的像素分布。 对那些分类未知的像素(可能是前景也可能是背景), 可以根据它们与已知分类(如背景)的像素的关系来进行分类(就像是在做聚类操作)。
这样就会根据像素的分布创建一副图。图中的节点就是像素点。 除了像素点做节点之外还有两个节点:
Source_node
和Sink_node
。 所有的前景像素都和Source_node
相连。 所有的背景像素都和Sink_node
相连。将像素连接到
Source_node/end_node
的(边)的权重由它们属于同一类(同是前景或同是背景)的概率来决定。 两个像素之间的权重由边的信息或者两个像素的相似性来决定。 如果两个像素的颜色有很大的不同,那么它们之间的边的权重就会很小。使用 mincut 算法对上面得到的图进行分割。 它会根据最低成本方程将图分为
Source_node
和Sink_node
。 成本方程就是被剪掉的所有边的权重之和。 在裁剪之后,所有连接到Source_node
的像素被认为是前景, 所有连接到Sink_node
的像素被认为是背景。继续这个过程直到分类收敛。
下图演示了这个过程(Image Courtesy: http://www.cs.ru.ac.za/research/g02m1682/):
演示
现在进入 OpenCV 中的 grabcut 算法。OpenCV 提供了函数:
cv2.grabCut()
。那么来先看看它的参数:
img
- 输入图像mask
-掩模图像,用来确定那些区域是背景,前景,可能是前景/背景等。 可以设置为:cv2.GC_BGD
,cv2.GC_FGD
,cv2.GC_PR_BGD
,cv2.GC_PR_FGD
,或者直接输入 0,1,2,3 也行。rect
- 包含前景的矩形,格式为 (x,y,w,h)bdgModel
,fgdModel
- 算法内部使用的数组. 只需要创建两个大小为 (1,65),数据类型为np.float64
的数组。iterCount
- 算法的迭代次数mode
可以设置为cv2.GC_INIT_WITH_RECT
或cv2.GC_INIT_WITH_MASK
,
也可以联合使用。这是用来确定进行修改的方式, 矩形模式或者掩模模式。
首先来看使用矩形模式。加载图片,创建掩模图像,
构建 bdgModel和 fgdModel。传入矩形参数。
都是这么直接。让算法迭代 5 次。
由于在使用矩形模式所以修改模式设置为 cv2.GC_INIT_WITH_RECT
。
运行grabcut。算法会修改掩模图像,在新的掩模图像中,
所有的像素被分为四类:
背景,前景,可能是背景/前景使用 4 个不同的标签标记(前面参数中提到过)。 然后来修改掩模图像,所有的 0 像素和 1 像素都被归为 0(例如背景), 所有的 1 像素和 3 像素都被归为 1(前景)。 最终的掩模图像就这样准备好了。用它和输入图像相乘就得到了分割好的图像。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('messimg.png')
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (50,50,450,290)
# 函数的返回值是更新的 mask, bgdModel, fgdModel
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
(<matplotlib.image.AxesImage at 0x7f2fb3b172c0>, <matplotlib.colorbar.Colorbar at 0x7f2fb3481250>, None)
哎呀,梅西的头发被弄没了!让我们来帮他找回头发。 所以要在那里画一笔(设置像素为 1,肯定是前景)。 同时还有一些并不需要的草地,需要去除它们, 再在这个区域画一笔(设置像素为 0,肯定是背景)。 现在可以象前面提到的那样来修改掩模图像了。
实际上是怎么做的呢?使用图像编辑软件打开输入图像,添加一个图层, 使用笔刷工具在需要的地方使用白色绘制(比如头发,鞋子,球等); 使用黑色笔刷在不需要的地方绘制(比如,logo,草地等)。 然后将其他地方用灰色填充,保存成新的掩码图像。 在 OpenCV 中导入这个掩模图像,根据新的掩码图像对原来的掩模图像进行编辑。代码如下:
# newmask is the mask image I manually labelled
newmask = cv2.imread('newmask.png',0)
# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[3], line 6 2 newmask = cv2.imread('newmask.png',0) 4 # whereever it is marked white (sure foreground), change mask=1 5 # whereever it is marked black (sure background), change mask=0 ----> 6 mask[newmask == 0] = 0 7 mask[newmask == 255] = 1 9 mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK) IndexError: boolean index did not match indexed array along axis 0; size of axis is 342 but size of corresponding boolean axis is 283
结果如下:
就是这样,也可以不使用矩形初始化,直接进入掩码图像模式。 使用 2像素和 3 像素(可能是背景/前景)对矩形区域的像素进行标记。 然后像在第二个例子中那样对肯定是前景的像素标记为 1 像素。 然后直接在掩模图像模式下使用 grabCut 函数。
练习
OpenCV 自带的示例中有一个使用 grabcut 算法的交互式工具
grabcut.py
,自己玩一下吧。可以创建一个交互是取样程序,可以绘制矩形,带有滑动条(调节笔刷的粗细)等。