什么是交互式前景提取
经典的前景提取技术主要使用纹理(颜色)信息,如魔术棒工具,或根据边缘(对比度)信息,如智能剪刀等完成。2004年,微软研究院的Rother等人在论文中提出了交互式前景提取技术。他们提出的算法,仅需要做很少的交互操作,就能够准确地提取出前景图像。
在开始提取前景时,先用一个矩形框指定前景区域所在的大致位置范围,然后不断迭代地分割,直到达到最好的效果。经过上述处理后,提取前景的效果可能并不理想,存在前景没有提取出来,或者将背景提取为前景的情况,此时需要用户干预提取过程。用户在原始图像的副本中(也可以是原始图像大小相等的任意一副图像),用白色标注要提取为前景的区域,用黑色标注要作为背景的区域,然后,将标注后的图像作为掩模,让算法继续迭代提取前景从而得到最终的结果。
比如上图这种人物图像,先用矩形框将要提取的前景框出来,在分别用白色和黑色对前景图像,背景图像进行标注。标注完成之后,使用交互式前景提取算法,就可以提取人物的完整前景了。
下面,博主来详细介绍交互式前景提取算法GrabCut算法的原理:
1.将前景所在的大致区域位置使用矩形框标注出来。值得注意的是,此时的矩形框框出的仅仅是前景的大致位置,其中即包含前景又包含背景,所以该区域实际上是未确定区域。但是该区域以外的区域被认为是“确定背景”
2.根据矩形框外部的“确定背景”数据来区分矩形框区域内的前景和背景。
3.用高斯混合模型(GMM)对前景和背景建模。GMM会根据用户的输入学习并创建新的像素分布。对未分类的像素(可能是背景也可能是前景),根据其与已知分类像素(前景和背景)的关系进行分类。
4.根据像素的分布情况生成一副图,图中的节点就是各个像素点。除了像素点之外,还有两个节点:前景节点和背景节点。所有的前景像素都和前景节点相连,所有的背景像素都和背景节点相连。每个像素连接到前景节点或背景节点的边的权重由像素是前景或背景的概率来决定
5.图中的每个像素除了与前景节点或背景节点相连外,彼此之间还存在着连接。两个像素连接的边的权重值由它们的相似性决定,两个像素值的颜色越接近,边的权重值越大。
6.完成节点连接后,需要解决的问题变成了一副连通的图。在该图上根据各自边的权重关系进行切割,将不同的点划分为前景节点和背景节点。
7.不断重复上述过程,直到分类收敛为止。
grabCut函数
在OpenCV中,它给我们提供了cv2.grabCut()函数来实现交互式前景提取,其完整定义如下:
def grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode=None):
img:输入图像,8位3通道
mask:掩模图像,8位单通道。该参数用于确定前景区域,背景区域和不确定区域,可以设置为4种形式:
取值 |
含义 |
cv2.GC_BGD |
表示确定背景,也可以用数值0表示 |
cv2.GC_FGD |
表示确定前景,也可以用数值1表示 |
cv2.GC_PR_BGD | 表示可能的背景,也可以用数值2表示 |
cv2.GC_PR_FGD | 表示可能的前景,也可以用数值3表示 |
在使用模板提取前景时,会将参数值0和2合并为背景(均当作0处理),将参数值1和3合前景(均当作1处理)。在通常情况下,我们可以使用白色笔刷和黑色笔刷在掩模图像上做标记,再通过转换将其中的白色像素设置为0,黑色像素设置为1。
rect:指包含前景对象的区域,该区域外的部分被认为是“确定背景”。因此,在选取时务必保证前景包含在rect指定的范围内;否则,rect外的前景部分时不会被提取出来的。只有当参数mode的值被设置为矩形模式cv2.GC_INIT_WITH_RECT时,参数rect才有意义。其格式为(x,y,w,h),分别表示区域的左上角像素的X轴和Y轴坐标以及区域的长宽。如果前景位于右下方,又不想判断原始图像的大小,对于w和h可以直接用一个很大的值。使用掩模模式时,将该值设置为none即可。
bgdModel:算法内部使用的数组,值需要创建大小为(1,65)的numpy.float64数组。
fgdModel:算法内部使用的数组,值需要创建大小为(1,65)的numpy.float64数组。
iterCount:迭代的次数
mode:迭代模式,具体取值如下表:
取值 |
含义 |
cv2.GC_INIT_WITH_RECT | 使用矩形模板 |
cv2.GC_INIT_WITH_MASK | 使用自定义模板。需要注意cv2.GC_INIT_WITH_RECT和cv2.GC_INIT_WITH_MASK能组合使用。所有ROI区域外(也就是不在模板或矩形指定范围内)的像素会自动被处理为背景 |
cv2.GC_EVAL | 修复模式 |
cv2.GC_EVAL_FREEZE_MODEL | 使用固定模式 |
提取图像前景
既然我们了解了原理,也知道了OpenCV提供的方法。下面,我们来试着提取上述图像的前景,代码如下:
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("4.jpg") plt.subplot(121) plt.imshow(img, cmap="gray") plt.axis('off') mask = np.zeros(img.shape[:2], dtype=np.uint8) bgdModel = np.zeros((1, 65), dtype=np.float64) fgdModel = np.zeros((1, 65), dtype=np.float64) mask[10:200, 95:220] = 3 cv2.grabCut(img, mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK) mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8') ogc = img * mask2[:, :, np.newaxis] plt.subplot(122) plt.imshow(ogc, cmap="gray") plt.axis('off') plt.show()
运行之后,我们可以完全分离出人脸与背景,效果如下:
至于mask取值为什么会是这样,我们先来看看如下代码:
import cv2 img = cv2.imread("4.jpg") rect=img[10:200,95:220] cv2.imshow("4",rect) cv2.waitKey() cv2.destroyAllWindows()
运行之后,效果如下:
可以看到,10:200, 95:220是坐标(10,95)宽高200,220的矩形框,也就是可能前景的矩形框。通过可能前景加上CrabCut算法,我们获取确定前景(这里是人物头部)。
使用模板提取图像前景
下面,我们直接使用cv2.GC_INIT_WITH_MASK来提取。代码如下:
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("4.jpg") mask = np.zeros(img.shape[:2], dtype=np.uint8) bgdModel = np.zeros((1, 65), dtype=np.float64) fgdModel = np.zeros((1, 65), dtype=np.float64) rect = (60, 10, 400, 500) cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT) mask2 = cv2.imread("37.jpg",0) plt.subplot(121) plt.imshow(mask2, cmap="gray") plt.axis('off') mask[mask2 == 0] = 0 mask[mask2 == 255] = 1 mask, bgd, fgd = cv2.grabCut(img, mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK) mask = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8') ogc = img * mask[:, :, np.newaxis] plt.subplot(122) plt.imshow(ogc, cmap="gray") plt.axis('off') plt.show()
运行之后,效果如下:
这里,我们构造了一个模板,并没有使用矩形框。通过模板,我们可以获取另一个图像中,大致的确定前景。这里,我们使用像素0标注确定背景,使用像素1标注确定前景。