敞开成长之旅!这是我参加「日新计划 2 月更文应战」的第 31 天,点击查看活动详情

目标

在这一章当中

  • 看到 GrabCut算法来提取图画中的远景
  • 为此创立一个交互式使用程序

理论

GrabCut 算法由英国剑桥微软研究院的 Carsten Rother、Vladimir Kolmogorov 和 Andrew Blake 设计。在他们的论文“GrabCut”中:运用迭代图切开的交互式远景提取。需求一种算法以最少的用户交互进行远景提取,成果是 GrabCut。

从用户的视点来看它是如何作业的?开始用户在远景区域周围制作一个矩形(远景区域应该完全在矩形内)。然后算法对其进行迭代切割以取得最佳成果。完毕。但是在某些情况下,切割不会很好。例如,它或许将某些远景区域符号为布景,反之亦然。在这种情况下,用户需求进行精密的润饰。只需在存在一些错误成果的图画上进行一些描边即可。Strokes 基本上是说 “嘿,这个区域应该是远景,你把它符号为布景,鄙人一次迭代中纠正它” 或许它的相反布景。然后鄙人一次迭代中,将取得更好的成果。

见下图。第一位球员和足球被封闭在一个蓝色矩形中。然后用 白色笔触(表明远景)和黑色笔触(表明布景) 进行一些终究润饰,终究得到了一个不错的成果。

OpenCV 29:  使用Grabcut算法的交互式前景提取

这背后会发生什么?

  • 用户输入矩形。此矩形之外的一切内容都将被视为确定的布景(这便是之前提到的矩形应包括一切对象的原因)。矩形内的一切都是不知道的。类似地,任何指定远景和布景的用户输入都被视为硬标签,这意味着它们不会在过程中发生变化。
  • 计算机依据所供给的数据进行初始符号。符号远景和布景像素(或硬符号)
  • 现在运用高斯混合模型(GMM)对远景和布景进行建模
  • 依据供给的数据,GMM 学习并创立新的像素散布。即,不知道像素被符号为或许的远景或或许的布景,这取决于它与其他硬符号像素在色彩统计方面的关系(就像聚类一样)。
  • 图形是依据此像素散布构建的。图中的节点是像素。增加了别的两个节点**,Source node和Sink node**。每个远景像素都连接到 Source 节点,每个布景像素都连接到 Sink 节点。
  • 将像素连接到源节点/完毕节点的边的权重由像素为远景/布景的概率定义。像素之间的权重由边际信息或像素相似度定义。如果像素色彩存在较大差异,则它们之间的边际将取得较低的权重。
  • 然后运用mincut算法对图进行切割。它以最小的价值函数将图切开成两个别离的源节点和汇节点。成本函数是被切开的边的一切权重的总和。剪切后,一切连接到源节点的像素成为远景,连接到接收节点的像素成为布景。
  • 该过程一向继续到分类收敛。 示意图如下所示(图片供给:[http : //www.cs.ru.ac.za/research/g0… : //www.cs.ru.ac.za/research/g02m1682/))

OpenCV 29:  使用Grabcut算法的交互式前景提取

演示

现在运用 OpenCV 实现grabcut算法。OpenCV 有函数cv2.grabCut() 。咱们将首先看到它的参数:

mask, bgdModel, fgdModel = cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount[, mode] )

  • img – 输入图画
  • mask – 遮罩图画,指定哪些区域是布景、远景或或许的布景/远景等。由以下标志完结,cv2.GC_BGDcv2.GC_FGDcv2.GC_PR_BGDcv2.GC_PR_FGD,或简略地经过0,1,2,3
  • rect – 它是包括格局为 (x,y,w,h) 的远景对象的矩形的坐标
  • bdgModel , fgdModel – 这些是算法内部运用的数组。只需创立两个大小为 (1,65) 的 np.float64 类型零数组
  • iterCount – 算法应该运转的迭代次数
  • mode 应该是cv2.GC_INIT_WITH_RECTcv2.GC_INIT_WITH_MASK或组合,这决议了是制作矩形还是终究的润饰笔触。

首先,看看矩形形式( rectangular mode)。加载图画,然后创立一个类似的蒙版图画。创立fgdModelbgdModel。并给出矩形参数。这一切都是开门见山的。让算法迭代运转 5 次。形式应该是cv2.GC_INIT_WITH_RECT,这是由于运用的是矩形。然后运转grabcut。它修改蒙版图画。在新的蒙版图画中,像素将被符号为四个标志,表明上面指定的布景/远景。所以修改掩码,使得一切 0 像素和 2 像素都置为 0(即布景),一切 1 像素和 3 像素均置为 1(即远景像素)。现在终究的mask准备好了。只需将其与输入图画相乘即可得到切割图画。

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('messi2.jpg')
mask = np.zeros(img.shape[:2], np.uint8)
cv2.imwrite('dd.jpg', mask)
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()

查看以下成果:

OpenCV 29:  使用Grabcut算法的交互式前景提取

哎呀,梅西的头发不见了。谁喜爱没有头发的梅西?咱们需求把它弄回来。因此,将运用 1 像素(当然是远景) 进行精密润饰。一起,一些地面呈现了咱们不想要的图片,还有一些标志, 也需求移除它们。在那里,供给了一些 0 像素的润饰(当然是布景)。因此,正如现在所说的那样,修改了之前案例中的成果掩码。

实践做的是,在绘画使用程序中打开输入图画并为图画增加了另一个图层。在油漆中运用画笔东西,在这个新图层上用白色符号错过的远景(头发、鞋子、球等)和用黑色符号不需求的布景(如标志、地面等)。然后用灰色填充剩余的布景。 然后在 OpenCV 中加载该蒙版图画,修改咱们取得的原始蒙版图画,并在新增加的蒙版图画中运用相应的值。查看下面的代码:

# newmask is the mask image by manually labelled
newmask = cv2.imread('messi-new-mask.jpg', 0)
# wherever it is marked white (sure foreground), change mask=1
# wherever 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.subplot(121)
plt.imshow(mask)
plt.subplot(122)
plt.imshow(img)
plt.colorbar()
plt.show()

OpenCV 29:  使用Grabcut算法的交互式前景提取

在这里,可以直接进入掩码形式,而不是在 rect 形式下初始化。只需用 2 像素或 3 像素(或许的布景/远景)符号蒙版图画中的矩形区域。然后像咱们在第二个示例中所做的那样用 1 像素符号咱们的 sure_foreground。然后直接使用带有mask形式的grabCut函数。

附加资源

  • docs.opencv.org/4.1.2/d8/d8…
  • dl.acm.org/citation.cf…
  • docs.opencv.org/4.1.2/d7/d1…