图像分割—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算法函数:
-
void grabCut(
-
InputArray img,
InputOutputArray mask,
Rect rect,
InputOutputArray bgdModel,
InputOutputArray fgdModel,
int iterCount,
int mode = GC_EVAL
); 参数说明:
-
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()
更多推荐








所有评论(0)