目标
本章内容
- 我们将学习使用GrabCut算法提取图像中的前景。
- 我们将为此创建一个交互式应用程序。
理论
GrabCut算法由来自英国剑桥微软研究院的Carsten Rother、Vladimir Kolmogorov和Andrew Blake在其论文“GrabCut”: interactive foreground extraction using iterated graph cuts中提出。该算法旨在以最少的用户交互实现前景提取,GrabCut正是其成果。
从用户的角度来看,它是如何工作的呢?最初,用户会在前景区域周围绘制一个矩形(前景区域应完全位于矩形内)。然后,算法迭代地分割图像以获得最佳结果。完成。但在某些情况下,分割效果可能不佳,例如,它可能将某些前景区域标记为背景,反之亦然。在这种情况下,用户需要进行精细的修饰。只需在图像上一些结果错误的区域进行涂抹即可。涂抹操作基本上表示“嘿,这个区域应该是前景,你把它标记为背景了,在下次迭代中更正它”或其背景的相反操作。然后在下一次迭代中,你将获得更好的结果。
请参见下图。首先,球员和足球被包含在一个蓝色矩形中。然后用白色笔触(表示前景)和黑色笔触(表示背景)进行了一些最终的修饰。我们得到了一个不错的结果。
图像
那么后台发生了什么呢?
- 用户输入矩形。矩形外部的所有内容都将被视为确定的背景(这就是前面提到你的矩形应该包含所有对象的理由)。矩形内部的所有内容都是未知的。同样,任何指定前景和背景的用户输入都被视为硬标记,这意味着它们在处理过程中不会改变。
- 计算机根据我们提供的数据进行初始标记。它标记前景和背景像素(或硬标记)。
- 现在,使用高斯混合模型 (GMM) 对前景和背景进行建模。
- 根据我们提供的数据,GMM 学习并创建新的像素分布。也就是说,未知像素根据其与其他硬标记像素在颜色统计方面的关系,被标记为可能的前景或可能的后景(这就像聚类一样)。
- 根据此像素分布构建一个图。图中的节点是像素。还会添加两个额外的节点,**源节点**和**汇节点**。每个前景像素都连接到源节点,每个背景像素都连接到汇节点。
- 连接像素到源节点/汇节点的边的权重由像素属于前景/背景的概率定义。像素之间的权重由边缘信息或像素相似性定义。如果像素颜色差异很大,则它们之间的边将获得较低的权重。
- 然后,使用最小割算法对图进行分割。它以最小成本函数将图切割成两个分离源节点和汇节点。成本函数是所有被切割边的权重之和。切割后,所有连接到源节点的像素都变成前景,所有连接到汇节点的像素都变成背景。
- 该过程持续进行,直到分类收敛。
下图对此进行了说明(图片由http://www.cs.ru.ac.za/research/g02m1682/提供)。
图像
演示
现在我们使用OpenCV进行GrabCut算法。OpenCV具有此函数,**cv.grabCut()**。我们首先来看看它的参数
首先让我们看看矩形模式。我们加载图像,创建一个类似的掩码图像。我们创建fgdModel和bgdModel。我们给出矩形参数。一切都很直接。让算法运行 5 次迭代。模式应为cv.GC_INIT_WITH_RECT,因为我们使用的是矩形。然后运行grabcut。它修改掩码图像。在新掩码图像中,像素将用四个标志标记,表示如上所述的背景/前景。因此,我们将掩码修改为所有 0 像素和 2 像素都设置为 0(即背景),所有 1 像素和 3 像素都设置为 1(即前景像素)。现在我们的最终掩码已准备就绪。只需将其与输入图像相乘即可获得分割图像。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
assert img is not None, "文件无法读取,请使用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_BGR)
从文件中加载图像。
void grabCut(InputArray img, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, InputOutputArray fgdModel, int iterCount, int mode=GC_EVAL)
运行GrabCut算法。
请参见下面的结果
图像
糟糕,梅西的头发不见了。谁喜欢没头发的梅西?我们需要把它找回来。因此,我们将使用 1 像素(确定前景)在那里进行精细的修饰。同时,一些我们不想要的地面部分也进入了画面,还有一些标志。我们需要移除它们。在那里,我们进行一些 0 像素的修饰(确定背景)。因此,我们将修改前面案例中的结果掩码,就像我们现在所说的那样。
我的实际操作是:在画图应用程序中打开输入图像,并向图像添加另一层。使用画图工具中的画笔工具,我在这一新图层上用白色标记缺失的前景(头发、鞋子、球等),用黑色标记不需要的背景(如徽标、地面等)。然后用灰色填充剩余的背景。然后在OpenCV中加载该蒙版图像,并使用新添加的蒙版图像中的对应值编辑我们获得的原始蒙版图像。请查看下面的代码
newmask =
cv.imread(
'newmask.png', cv.IMREAD_GRAYSCALE)
assert newmask is not None, "文件无法读取,请使用os.path.exists()检查"
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像素标记我们的sure_foreground。然后直接使用蒙版模式应用grabCut函数。
练习
- OpenCV示例包含一个示例grabcut.py,这是一个使用grabcut的交互式工具。请查看它。还可以观看此YouTube视频,了解如何使用它。
- 在这里,您可以将其制作成一个交互式示例,使用鼠标绘制矩形和笔触,创建轨迹条来调整笔触宽度等。