Python-OpenCV-基于GrabCut算法的交互式可选区域前景提取

翻译自:基于GrabCut算法的交互式前景提取

理论:

GrabCut算法由英国剑桥微软研究院的Carsten Rother,Vladimir Kolmogorov和Andrew Blake设计。

从用户的角度来看它是如何工作的?最初用户在前景区域周围绘制一个矩形(前景区域应该完全在矩形内)。然后算法迭代地对其进行分段以获得最佳结果,完成。但在某些情况下,分割将不会很好,例如,它可能已将某些前景区域标记为背景,反之亦然。在这种情况下,用户需要进行精细的修饰。只需对图像进行一些描述,其中存在一些错误结果。笔划基本上说*“嘿,这个区域应该是前景,你标记它的背景,在下一次迭代中纠正它”*或它的背景相反。然后在下一次迭代中,您将获得更好的结果。

见下图。第一名球员和足球被包围在一个蓝色矩形中。然后进行一些具有白色笔划(表示前景)和黑色笔划(表示背景)的最终修饰。我们得到了一个很好的结果。

那么背景会发生什么?

  • 用户输入矩形。这个矩形之外的所有东西都将被视为确定的背景(这就是之前提到的矩形应包括所有对象的原因)。矩形内的一切都是未知的。类似地,任何指定前景和背景的用户输入都被视为硬标签,这意味着它们不会在过程中发生变化。
  • 计算机根据我们提供的数据进行初始标记。它标记前景和背景像素(或硬标签)
  • 现在,高斯混合模型(GMM)用于模拟前景和背景。

  • 根据我们提供的数据,GMM学习并创建新的像素分布。也就是说,未知像素被标记为可能的前景或可能的背景,这取决于其在颜色统计方面与其他硬标记像素的关系(它就像聚类一样)。
  • 从该像素分布构建图形。图中的节点是像素。添加了另外两个节点,Source节点Sink节点。每个前景像素都连接到Source节点,每个背景像素都连接到Sink节点。
  • 将像素连接到源节点/端节点的边的权重由像素是前景/背景的概率来定义。像素之间的权重由边缘信息或像素相似性定义。如果像素颜色存在较大差异,则它们之间的边缘将获得较低的权重。
  • 然后使用mincut算法来分割图形。它将图形切割成两个分离源节点和汇聚节点,具有最小的成本函数。成本函数是被切割边缘的所有权重的总和。切割后,连接到Source节点的所有像素都变为前景,连接到Sink节点的像素变为背景。
  • 该过程一直持续到分类收敛为止。

现在我们使用OpenCV进行抓取算法。OpenCV具有此功能,cv.grabCut()。参数:

  • img - 输入图像
  • mask - 这是一个蒙版图像,我们指定哪些区域是背景,前景或可能的背景/前景等。它由以下标志cv.GC_BGD(背景:0),cv.GC_FGD(前景:1)cv.GC_PR_BGD(可能的背景:2)cv.GC_PR_FGD(可能的前景:3)组成,也可以输入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因为我们使用矩形。然后运行抓取。它修改了蒙版图像。在新的掩模图像中,像素将被标记为表示背景/前景的四个标记,如上所述。因此,我们修改掩模,使得所有0像素和2像素都被置为0(即背景),并且所有1像素和3像素被置为1(即前景像素)。现在我们的最后面具准备好了。只需将其与输入图像相乘即可得到分割后的图像。

注:np.where(condition, x, y):满足条件(condition),输出x,不满足输出y。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('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)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
#代码中将0和2合并为背景 1和3合并为前景
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()

哎呀,梅西的头发不见了。没有头发谁喜欢梅西?我们需要把它带回来。因此,我们将为其提供1像素(确定前景)的精细修饰。与此同时,有些地方已经出现了我们不想要的图片,还有一些标识。我们需要删除它们。在那里我们提供一些0像素的修饰(确定背景)。因此,正如我们现在所说的那样,我们在之前的案

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

# newmask是我手动标记的图像
newmask = cv.imread('newmask.png',0)
#标记为白色的地方(确定前景),更改掩码= 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()


代码改进:

由于rect很难确定正确的位置,我决定用OpenCV的鼠标事件来选取处理的背景区域。

鼠标左键点击确定起始坐标。

按住左键拖动鼠标来选取矩形区域。

鼠标左键放开确定结束坐标。

至此rect = (x,y,width,height)四个参数就由鼠标确定。

import cv2
import numpy as np

def mouseEvent(event, x, y, flags, param):
    
    global img, position1, position2 
    
    image = img.copy()   
    
    if event == cv2.EVENT_LBUTTONDOWN:                                          #按下左键
        position1 = (x,y)                                                       #获取鼠标的坐标(起始位置)

    elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:      #按住左键拖曳不放开
        cv2.rectangle(image, position1, (x,y), (0,255,0), 3)                    #画出矩形选定框
        cv2.imshow('image', image)
        
    elif event == cv2.EVENT_LBUTTONUP:                                          #放开左键
        position2 = (x,y)                                                       #获取鼠标的最终位置
        cv2.rectangle(image, position1, position2, (0,0,255), 3)                #画出最终的矩形 
        cv2.imshow('image', image)
        
        min_x = min(position1[0],position2[0])                                  #获得最小的坐标,因为可以由下往上拖动选定框     
        min_y = min(position1[1],position2[1])
        width = abs(position1[0] - position2[0])                                #为了适配rect的格式
        height = abs(position1[1] - position2[1])
        
        mask = np.zeros(img.shape[:2],np.uint8)                                 #初始化蒙版图像
        bgdModel = np.zeros((1,65),np.float64)                                  #内算法使用的零数组
        fgdModel = np.zeros((1,65),np.float64)
        rect = (min_x,min_y,width,height)                                       #选定的前景区域
        cv2.grabCut(image,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)  #函数返回值为mask,bgdModel,fgdModel
        mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')               #代码中将0和2合并为背景 1和3合并为前景
        
        img1 = image*mask2[:,:,np.newaxis]                                      #使用蒙板来获取前景区域
        cv2.imshow('GMM',img1)

def main():
    
    global img
    img = cv2.imread(r'C:\Users\x\Desktop\87.jpg',cv2.IMREAD_ANYCOLOR)
    cv2.namedWindow('image')
    cv2.setMouseCallback('image', mouseEvent)
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

 


结束!

  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值