图像分割—GrabCut算法

一、理论概述

Grabcut是基于图割(graph cut)实现的图像分割算法,它需要用户输入一个bounding box作为分割目标位置,实现对目标与背景的分离/分割,与KMeans与MeanShift等图像分割方法不同。
Grabcut分割速度快,效果好,支持交互操作,因此在很多APP图像分割/背景虚化的软件中可以看到其身影。
该算法主要基于以下知识:

  • k均值聚类
  • 高斯混合模型建模(GMM)
  • max flow/min cut

关于算法理论:https://blog.csdn.net/kyjl888/article/details/78253829
GrabCut算法的实现步骤:

  • 在图片中定义(一个或者多个)包含物体的矩形。矩形外的区域被自动认为是背景。
  • 对于用户定义的矩形区域,可用背景中的数据来区分它里面的前景和背景区域。
  • 用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或者背景。
  • 图像中的每一个像素都被看做通过虚拟边与周围像素相连接,而每条边都有一个属于前景或者背景的概率,这是基于它与周边像素颜色上的相似性。
  • 每一个像素(即算法中的节点)会与一个前景或背景节点连接。
    在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。下图能很好的说明该算法:
    在这里插入图片描述

二、OpenCV中grabCut算法函数:

grabCut(img,mask,rect,bgdModel,fgdModel,iterCount,mode)

参数说明:

img—待分割的源图像,是8位3通道
mask—掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:(若无标记GCD_BGD或GCD_FGD,则结果只有GCD_PR_BGD或GCD_PR_FGD;)

  • GCD_BGD(=0),背景;
  • GCD_FGD(=1),前景;
  • GCD_PR_BGD(=2),可能的背景;
  • GCD_PR_FGD(=3),可能的前景。

rect—限定要进行分割的图像范围
bgdModel—背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
fgdModel—前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
iterCount—迭代次数,必须大于0;
mode—grabCut函数操作方法,可选:

  • GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
  • GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
  • GC_EVAL(=2),执行分割。

输入:图像、被标记好的前景、背景。
输出:分割图像
其中输入的前景、背景指的是一种概率,如果你已经明确某一块区域是背景,那么它属于背景的概率为1;当然如果你觉得它有可能背景,但是没有百分百的肯定,这个时候你就要用到高斯模型,对其进行建模,然后估算概率。现在我以下图为例,用户通过交互输入框选区域,前景位于框选区域内,也就是说矩形区域外的全部属于背景,且概率为百分百。然后方框内可能属于前景,概率需要用高斯混合建模求解。

小案例:

import numpy as np
import cv2
     
#鼠标事件的回调函数
def on_mouse(event,x,y,flag,param):        
    global rect
    global leftButtonDowm
    global leftButtonUp
    
    #鼠标左键按下
    if event == cv2.EVENT_LBUTTONDOWN:
        rect[0] = x
        rect[2] = x
        rect[1] = y
        rect[3] = y
        leftButtonDowm = True
        leftButtonUp = False
        
    #移动鼠标事件
    if event == cv2.EVENT_MOUSEMOVE:
        if leftButtonDowm and  not leftButtonUp:
            rect[2] = x
            rect[3] = y        
  
    #鼠标左键松开
    if event == cv2.EVENT_LBUTTONUP:
        if leftButtonDowm and  not leftButtonUp:
            x_min = min(rect[0],rect[2])
            y_min = min(rect[1],rect[3])
            
            x_max = max(rect[0],rect[2])
            y_max = max(rect[1],rect[3])
            
            rect[0] = x_min
            rect[1] = y_min
            rect[2] = x_max
            rect[3] = y_max
            leftButtonDowm = False      
            leftButtonUp = True


img = cv2.imread(r'C:\Users\SongpingWang\Desktop\the_angry_birds_movie.jpg')
mask = np.zeros(img.shape[:2],np.uint8)


bgdModel = np.zeros((1,65),np.float64)    #背景模型
fgdModel = np.zeros((1,65),np.float64)    #前景模型
rect = [0,0,0,0]                          #设定需要分割的图像范围
    

leftButtonDowm = False                    #鼠标左键按下
leftButtonUp = True                       #鼠标左键松开

cv2.namedWindow('img')                    #指定窗口名来创建窗口
cv2.setMouseCallback('img',on_mouse)      #设置鼠标事件回调函数 来获取鼠标输入
cv2.imshow('img',img)                     #显示图片


while cv2.waitKey(2) == -1:
    #左键按下,画矩阵
    if leftButtonDowm and not leftButtonUp:  
        img_copy = img.copy()
        cv2.rectangle(img_copy,(rect[0],rect[1]),(rect[2],rect[3]),(0,255,0),2)  
        cv2.imshow('img',img_copy)
        
    #左键松开,矩形画好 
    elif not leftButtonDowm and leftButtonUp and rect[2] - rect[0] != 0 and rect[3] - rect[1] != 0:
        rect[2] = rect[2]-rect[0]
        rect[3] = rect[3]-rect[1]
        rect_copy = tuple(rect.copy())   
        rect = [0,0,0,0]
        #物体分割
        cv2.grabCut(img,mask,rect_copy,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
            
        mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
        img_show = img*mask2[:,:,np.newaxis]
        #显示图片分割后结果--显示原图
        cv2.imshow('grabcut',img_show)
        cv2.imshow('img',img)    

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述
我们可以对其背景进行替换
使用背景图像实现替换与背景融合,首先对生成的mask图像做高斯模型生成权重,根据权重对背景与前景对象实现重组生成一张新的图像,代码实现如下:

import cv2
import numpy as np


def readimg(src_path,background_path):
	src = cv2.imread(src_path);
	background = cv2.imread(background_path)
	h, w, ch = src.shape
	mask = np.zeros(src.shape[:2], dtype=np.uint8)
	rect = (53,12,w-100,h-12)
	bgdmodel = np.zeros((1,65),np.float64)
	fgdmodel = np.zeros((1,65),np.float64)

	cv2.grabCut(src,mask,rect,bgdmodel,fgdmodel,5,mode=cv2.GC_INIT_WITH_RECT)
	mask2 = np.where((mask==1) + (mask==3), 255, 0).astype('uint8')
	object = cv2.bitwise_and(src, src, mask=mask2)
	cv2.imshow("object", object)

	# 高斯模糊
	se = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
	cv2.dilate(mask2, se, mask2)
	mask2 = cv2.GaussianBlur(mask2, (5, 5), 0)
	cv2.imshow('mask',mask2)

	# 虚化背景
	background = cv2.GaussianBlur(background, (0, 0), 15)

	# blend image
	result = np.zeros((h, w, ch), dtype=np.uint8)
	for row in range(h):
		for col in range(w):
			w1 = mask2[row, col] / 255.0
			b, g, r = src[row, col]
			b1,g1,r1 = background[row, col]
			b = (1.0-w1) * b1 + b * w1
			g = (1.0-w1) * g1 + g * w1
			r = (1.0-w1) * r1 + r * w1
			result[row, col] = (b, g, r)
	return result

if __name__ == '__main__':
	src_path = './aaa.png'
	background_path = './bbb.png'
	readimg(src_path,background_path)
	cv2.imshow("result", result)
	cv2.waitKey(0)
	cv2.destroyAllWindows()
发布了386 篇原创文章 · 获赞 592 · 访问量 72万+

猜你喜欢

转载自blog.csdn.net/wsp_1138886114/article/details/104076134