7.5. 基于GrabCut算法的交互式前景提取

7.5.1. 目标

在本章中
-我们将看到GrabCut算法来提取图像中的前景-我们将为此创建一个交互式应用程序。

7.5.2. 理论

GrabCut算法由英国剑桥微软研究院的Carsten Rother、Vladimir Kolmogorov和Andrew Blake设计。在他们的报纸上, “GrabCut”: interactive foreground extraction using iterated graph cuts . 在用户交互最少的情况下,提出了一种前景提取算法,并对结果进行了截取。

从用户的角度来看它是如何工作的?最初,用户在前景区域周围绘制一个矩形(前景区域应该完全位于矩形内部)。然后算法对其进行迭代分割,得到最佳结果。完成。但在某些情况下,分割效果不好,比如,它可能将某些前景区域标记为背景,反之亦然。在这种情况下,用户需要做精装修。只要在有错误结果的图像上画几笔就行了。中风基本上说 嘿,这个区域应该是前景色,您将它标记为背景,在下一次迭代中更正它 或者它的背景相反。然后在下一个迭代中,您会得到更好的结果。

见下图。第一个球员和足球被包围在一个蓝色的长方形里。然后用白色笔划(表示前景)和黑色笔划(表示背景)进行最后的润色。结果很好。

那么背景发生了什么?

  • 用户输入矩形。此矩形之外的所有内容都将作为确定的背景(这就是前面提到的矩形应该包含所有对象的原因)。矩形内的一切都是未知的。类似地,任何指定前景和背景的用户输入都被视为硬标签,这意味着它们在过程中不会改变。

  • 计算机根据我们提供的数据进行初始标记。它标记前景和背景像素(或硬标签)

  • 现在使用高斯混合模型(GMM)对前景和背景进行建模。

  • 根据我们提供的数据,GMM学习并创建新的像素分布。也就是说,根据未知像素与其他硬标记像素在颜色统计方面的关系,将其标记为可能的前景或可能的背景(就像聚类一样)。

  • 从这个像素分布建立一个图。图中的节点是像素。增加两个节点, 源节点汇节点 . 每个前景像素连接到源节点,每个背景像素连接到汇节点。

  • 将像素连接到源节点/结束节点的边的权重由像素成为前景/背景的概率来定义。像素之间的权重由边缘信息或像素相似度定义。如果像素颜色有很大的差异,它们之间的边缘将获得较低的权重。

  • 然后使用mincut算法对图进行分割。该算法以最小代价函数将图分为两个独立的源节点和汇节点。成本函数是被切割边缘的所有权重之和。剪切后,所有连接到源节点的像素变为前景,连接到汇节点的像素变为背景。

  • 这个过程一直持续到分类收敛为止。

如下图所示(图片来源:http://www.cs.ru.ac.za/research/g02m1682/

7.5.3. 演示

现在我们使用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)的前景对象

  • BDG模型FGD模型 -这些是算法内部使用的数组。只需创建两个大小为(1,65)的np.float64类型的零数组。

  • iterCount公司 -算法应该运行的迭代次数。

  • mode -应该是 cv2.GC_INIT_WITH_RECTcv2.GC_INIT_WITH_MASK 或组合,决定我们是绘制矩形还是最终的润色笔划。

首先让我们看看矩形模式。我们加载图像,创建一个类似的遮罩图像。我们创造 FGD模型BGD模型 . 我们给出矩形参数。一切都是直截了当的。让算法运行5次迭代。模式应该是 cv2.GC_INIT_WITH_RECT 因为我们用的是矩形。然后运行grabcut。它会修改遮罩图像。在新的遮罩图像中,像素将被标记为四个标志,如上文所述,表示背景/前景。因此,我们修改了遮罩,使得所有0像素和2像素都设置为0(即背景),所有1像素和3像素设置为1(即前景像素)。现在我们最后的面具准备好了。只需将其与输入图像相乘即可得到分割图像。

>>> import numpy as np
>>> import cv2
>>> from matplotlib import pyplot as plt
>>>
>>> img = cv2.imread('/cvdata/messi5.jpg')
>>> 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)
>>> 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()
<Figure size 640x480 with 2 Axes>
(<matplotlib.image.AxesImage at 0x7ff0f5d66f60>,
 <matplotlib.colorbar.Colorbar at 0x7ff0f5d206d8>,
 None)

结果如下:

哦,梅西的头发不见了。 谁喜欢梅西没有头发? 我们需要把它带回来。所以我们会给它一个1像素(肯定前景)的精细润色。同时,一些我们不想看到的地面,还有一些标志。我们需要把它们移走。在这里,我们给一些0像素的修补(当然背景)。所以我们在前面的例子中修改了我们的结果。

实际上,我在paint应用程序中打开了输入图像,并在图像中添加了另一层。使用画笔工具,我在这个新的图层上用白色和不需要的背景(如logo、ground等)标记丢失的前景(头发、鞋子、球等)。然后用灰色填充剩余的背景。然后将该掩模图像加载到OpenCV中,编辑得到的原始掩模图像,并在新添加的掩模图像中添加相应的值。检查以下代码:

>>> # newmask is the mask image I manually labelled
>>> newmask = cv2.imread('/cvdata/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()
../_images/sec05-grabcut_3_0.png
(<matplotlib.image.AxesImage at 0x7ff0f5cfb668>,
 <matplotlib.colorbar.Colorbar at 0x7ff0f5cab080>,
 None)

结果如下:

就这样。在这里,您可以直接进入掩码模式,而不是在rect模式下初始化。只需用2像素或3像素(可能的背景/前景)标记遮罩图像中的矩形区域。然后用1像素标记我们的sure_前景,就像我们在第二个例子中所做的那样。然后直接在掩模模式下应用grabCut功能。

7.5.4. 额外资源

7.5.5. 练习

  1. OpenCV示例包含一个示例 grabcut.py which is an interactive tool using grabcut. Check it. Also watch this youtube video 如何使用它。

  2. 在这里,你可以把它做成一个交互式的例子,用鼠标画矩形和笔划,创建轨迹栏来调整笔划宽度等等。