作业目标
算法简介
- 算法主要基于以下知识:
k均值聚类
高斯混合模型建模(GMM)
max flow/min cut
- GrabCut算法的实现步骤:
- 在图片中定义(一个或者多个)包含物体的矩形。 矩形外的区域被自动认为是背景。
- 对于用户定义的矩形区域,可用背景中的数据来区分它里面的前景和背景区域。
- 用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或者背景。
- 图像中的每一个像素都被看做通过虚拟边与周围像素相连接,而每条边都有一个属于前景或者背景的概率,这是基于它与周边像素颜色上的相似性。
- 每一个像素(即算法中的节点)会与一个前景或背景节点连接。
- 在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。
实现代码
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('messi5.jpg')
# 掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果
mask = np.zeros(img.shape[:2], np.uint8)
# 背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
bgdModel = np.zeros((1, 65), np.float64)
# fgdModel——前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
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()
# 在img图像上,绘制矩形 线条颜色为green 线宽为2
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()
实验结果
原始图片 | ![]() |
---|---|
勾选目标区域 | ![]() |
分割结果 | ![]() |
分割出来的效果不是很好,脚下的草可以通过写代码去掉特定的颜色,但是考虑到这样做没有普遍性,换一张图就完全行不通了,于是没有写。