OpenCV  4.10.0
开放源代码计算机视觉
加载...
搜索...
无匹配项
使用 GrabCut 算法进行交互式前景提取

目标

在本章中

  • 我们将了解 GrabCut 算法,该算法用于从图像中提取前景
  • 我们将为此创建一个交互式应用程序。

理论

GrabCut 算法由来自英国剑桥微软研究中心的研究员 Carsten Rother、Vladimir Kolmogorov 和 Andrew Blake 设计。在他们的论文 "GrabCut": 使用迭代图分割的交互式前景提取 中。为了用最少的用户交互完成前景提取,就需要一种算法,而 GrabCut 就是最终结果。

从用户的角度来看,它如何发挥作用?最初,用户在前景区域周围绘制一个矩形(前景区域应完全位于矩形内)。然后,算法以迭代方式对其进行分割,以获得最佳结果。完成。但在某些情况下,分割并不好,例如,它可能将某些前景区域标记为背景,反之亦然。在这种情况下,用户需要进行精细修饰。只需在图像上有错误结果的地方描几笔即可。轮廓基本上表示:“嘿,这个区域应该是前景,你将其标记为背景,在下次迭代时进行更正”或背景的反义词。然后在下次迭代中,你会得到更好的结果。

请参阅下图。首先,球员和足球被包含在一个蓝色矩形中。然后用白色轮廓(表示前景)和黑色轮廓(表示背景)进行一些最终修饰。而且我们得到了一个很好的结果。

图像

那么在后台发生了什么?

  • 用户输入矩形。这个矩形之外的所有内容将被视为背景(因此之前提到你的矩形应该包含所有对象是有原因的)。矩形内的所有内容都是未知的。类似地,任何指定前景和背景的用户输入都被视为硬标注,这意味着它们在此过程中不会发生变化。
  • 计算机根据我们提供的数据进行初步标注。它标注前景和背景像素(或硬标注)
  • 现在,使用高斯混合模型 (GMM) 来建模前景和背景。
  • 根据我们提供的数据,GMM 学习并创建新的像素分布。也就是说,根据其与其他硬标注像素在颜色统计方面的关系(这就像聚类),未知像素被标记为可能的前景或可能的背景。
  • 根据像素分布创建图像。图像中的节点为像素。添加两个额外的节点,源节点接收节点。每个前置像素连接至源节点,每个背景像素连接至接收节点。
  • 将像素连接至源节点/结束节点的边的权重通过像素成为前置/背景的概率定义。像素之间的权重通过边信息或像素相似性定义。如果像素颜色差异较大,则连接至它们之间的边的权重将较低。
  • 然后,使用最小割算法对图像进行分割。它使用最小成本函数将图像切成将源节点与接收节点分开的两块。成本函数为切断所有边的权重之和。切断后,连接至源节点的所有像素变成前置,连接至接收节点的所有像素变成背景。
  • 该过程持续,直到分类收敛。

如下图所示(图片礼让:http://www.cs.ru.ac.za/research/g02m1682/)

图像

演示

现在我们将使用 OpenCV 的 grabcut 算法。OpenCV 为此提供了函数,cv.grabCut()。我们先看一下它的参数

  • img - 输入图像
  • mask - 它为掩码图像,其中我们指定哪些区域是背景、前置或可能的背景/前置等。它通过以下标记完成,cv.GC_BGD, cv.GC_FGD, cv.GC_PR_BGD, cv.GC_PR_FGD,或者简单地将 0、1、2、3 传递到图像。
  • rect - 它为一个矩形的坐标,该矩形包括图像的 (x,y,w,h) 格式的前置对象
  • bdgModel, fgdModel - 它们为算法在内部使用的数组。您只需创建两个大小为 (1,65) 的 np.float64 类型零数组。
  • iterCount - 算法应当运行的迭代次数。
  • mode - 它应当为cv.GC_INIT_WITH_RECTcv.GC_INIT_WITH_MASK 或组合,这是确定我们绘制矩形还是最终修饰笔触。

首先让我们用矩形模式试试看。我们加载图片,创建一个类似的遮罩图片。我们创建了 `fgdModel` 和 `bgdModel`。我们给了矩形参数。这一切都很简单。让算法运行 5 次迭代吧。这里的模式应该是 `cv.GC_INIT_WITH_RECT`,因为我们使用的是矩形。然后运行 GrabCut。它将改变遮罩图像。在新的遮罩图像中,像素将标记为四种标记,表示背景/前景,如上所述。因此,我们修改遮罩,将所有 0 像素和 2 像素设置为 0(即背景),并将所有 1 像素和 3 像素设置为 1(即前景像素)。现在我们的最终遮罩已经准备好了。只要将它与输入图像相乘,就能得到分割后的图像。

导入 numpy 作为 np
导入 cv2 作为 cv
matplotlib 导入 pyplot 作为 plt
img = cv.imread('messi5.jpg')
断言 img 不是 , "文件无法读取,请检查 os.path.exists()"
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)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.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()
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载一个图像。
void grabCut(InputArray img, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, InputOutputArray fgdModel, int iterCount, int mode=GC_EVAL)
运行 GrabCut 算法。

下面是结果

图像

哦,梅西的头发不见了。谁喜欢没有头发的梅西?我们需要把它找回来。所以我们用 1 像素(肯定是前景)对它进行精修。与此同时,地面的一些部分出现了我们不想要的东西,还有一些标志。我们需要删除它们。我们在那儿进行了一些 0 像素的修饰(肯定是背景)。所以我们修改了上面提到的前一种情况下的结果遮罩。

我实际所做的是在绘图应用程序中打开输入图像并向图像添加另一个图层。在绘图中使用画笔工具,我用白色标记了错过的前景(头发、鞋子、球等),用黑色标记了此新图层中不需要的背景(如徽标、地面等)。然后用灰色填充剩余的背景。然后在 OpenCV 中加载该蒙板图像,使用新添加的蒙板图像中对应的值编辑我们获得的原始蒙板图像。查看以下代码

# newmask 是我手动标记的蒙板图像
newmask = cv.imread('newmask.png', cv.IMREAD_GRAYSCALE)
assert newmask is not None, "file could not be read, check with os.path.exists()"
# 标记为白色(确定前景)的任何地方,更改蒙板 = 1
# 标记为黑色(确定背景)的任何地方,更改蒙板 = 0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv.grabCut(img,mask,None,bgdModel,fgdModel,5,cv.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()

参见以下结果

图像

因此,就是这样。在这里,您无需在矩形模式中初始化,可以直接进入蒙板模式。直接用 2 像素或 3 像素(可能的背景/前景)在蒙板图像中标记矩形区域。然后,如我们在第二个示例中所做的那样,用 1 像素标记我们的确定前景。然后用蒙板模式直接应用 grabCut 函数。

其他资源

练习

  1. OpenCV 示例包含一个 grabcut.py 示例,它是一个使用 grabcut 的交互式工具。查看它。另外,观看此 YouTube 视频 以了解如何使用它。
  2. 在这里,您可以通过绘制矩形和使用鼠标绘制笔划,创建轨道条来调整笔划宽度等方式,将其制作成一个交互式示例。