目标
- GrabCut 算法原理,使用GrabCut 算法提取图像的前景
- 创建一个交互是程序完成前景提取
介绍
GrabCut算法是一种基于图像分割的算法,用于将图像中的前景物体从背景中准确地分离出来。它是由Carsten Rother等人于2004年提出的。
GrabCut算法的基本思想是通过迭代的方式将图像分成前景和背景两部分。它首先需要用户提供一个包含前景物体的矩形框,然后通过迭代的方式对图像进行分割。在每一次迭代中,算法会根据像素的颜色和纹理信息,以及前景和背景的模型,对像素进行分类。然后,算法会根据分类结果更新前景和背景的模型,并进行下一次迭代。直到算法收敛为止,得到最终的前景和背景分割结果。
步骤
1.用户输入一个包含前景物体的矩形框。
2.使用高斯混合模型(GMM)对前景和背景进行建模。
3.根据像素的颜色和纹理信息,对像素进行分类(前景或背景)。
4.根据分类结果,更新前景和背景的模型。
5.重复步骤3和步骤4,直到算法收敛。
6.根据最终的分类结果,将图像分割成前景和背景两部分。
实现
在OpenCV中,可以使用cv2.grabCut()
函数来实现GrabCut算法。函数原型如下:
mask, bgdModel, fgdModel = cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode=None)
参数说明:
img
:输入图像。mask
:与输入图像大小相同的掩码图像,用于指定前景、背景和未知区域。rect
:包含前景物体的矩形框。bgdModel
:背景模型。fgdModel
:前景模型。iterCount
:迭代次数。mode
:可选参数,用于指定操作模式(例如,cv2.GC_INIT_WITH_RECT
表示使用矩形框初始化模型)。
函数返回值:
mask
:更新后的掩码图像。bgdModel
:更新后的背景模型。fgdModel
:更新后的前景模型。
使用GrabCut算法的示例代码如下:
import cv2
import numpy as npimg = cv2.imread('image.jpg')
mask = np.zeros(img.shape[:2], dtype=np.uint8)rect = (50, 50, 200, 200)
bgdModel = np.zeros((1,65), dtype=np.float64)
fgdModel = np.zeros((1,65), dtype=np.float64)iterCount = 5
mode = cv2.GC_INIT_WITH_RECTmask, bgdModel, fgdModel = cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode)mask = np.where((mask==2)|(mask==0), 0, 1).astype('uint8')
img = img * mask[:, :, np.newaxis]cv2.imshow('Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上面的代码中,我们首先读取一张图像,并创建一个与图像大小相同的掩码图像。然后,我们定义一个包含前景物体的矩形框,并初始化背景模型和前景模型。接下来,我们调用cv2.grabCut()
函数来进行图像分割。最后,根据最终的分类结果,将图像中的前景提取出来并显示出来。
练习
1.OpenCV 自带的示例中有一个使用grabcut 算法的交互式工具grabcut.py。
下面是OpenCV自带的grabcut.py
示例代码的实现:
import numpy as np
import cv2# 定义状态
rect = (0, 0, 1, 1)
drawing = False
rectangle = False
rect_over = False
iterCount = 5
mode = cv2.GC_INIT_WITH_RECT# 鼠标回调函数
def onmouse(event, x, y, flags, param):global rect, drawing, rectangle, rect_overif event == cv2.EVENT_LBUTTONDOWN:if not rectangle:rectangle = Truerect = (x, y, 1, 1)else:rectangle = Falserect_over = Trueelif event == cv2.EVENT_LBUTTONUP:if rectangle:rect = (min(rect[0], x), min(rect[1], y), abs(rect[0] - x), abs(rect[1] - y))drawing = False# 读取图像
img = cv2.imread('image.jpg')# 创建窗口并设置鼠标回调函数
cv2.namedWindow('image')
cv2.setMouseCallback('image', onmouse)while True:# 显示图像if not rectangle:cv2.imshow('image', img)else:temp = img.copy()cv2.rectangle(temp, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), (0, 255, 0), 2)cv2.imshow('image', temp)# 处理键盘输入k = cv2.waitKey(1) & 0xFFif k == ord('r'):rect = (0, 0, 1, 1)rectangle = Falserect_over = Falseelif k == ord('c'):mode = cv2.GC_INIT_WITH_RECTprint('Draw a rectangle around the object to be segmented')elif k == ord('f'):mode = cv2.GC_INIT_WITH_MASKprint('Mark the areas in the image where the object and background are')elif k == 13: # Enter键breakcv2.destroyAllWindows()# 进行图像分割
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)if rect_over:cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode)mask = np.where((mask==2)|(mask==0), 0, 1).astype('uint8')
img = img * mask[:, :, np.newaxis]cv2.imshow('Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上面的代码中,我们首先定义了一些状态变量,包括矩形框的坐标、绘制状态、矩形框绘制状态和迭代次数等。然后,我们定义了一个鼠标回调函数onmouse()
,用于处理鼠标事件。接下来,我们读取图像,并创建一个窗口并设置鼠标回调函数。然后,我们进入一个循环,在循环中显示图像,并处理键盘输入。当用户按下键盘上的特定键时,我们会根据不同的情况进行相应的操作,例如重置矩形框、选择操作模式等。当用户按下回车键时,我们退出循环。然后,我们根据用户选择的操作模式,调用cv2.grabCut()
函数进行图像分割。最后,根据最终的分类结果,将图像中的前景提取出来并显示出来。
2.创建一个交互式取样程序,可以绘制矩形,带有滑动条,可以调节笔刷 的粗细等功能的python程式。
下面是一个交互式取样程序的示例代码,它可以绘制矩形,带有滑动条,可以调节笔刷粗细等功能:
import cv2
import numpy as npdrawing = False # 是否正在绘制
mode = True # True表示绘制矩形,False表示绘制圆形
ix, iy = -1, -1 # 绘制起点坐标# 鼠标回调函数
def draw_shape(event, x, y, flags, param):global ix, iy, drawing, modeif event == cv2.EVENT_LBUTTONDOWN:drawing = Trueix, iy = x, yelif event == cv2.EVENT_LBUTTONUP:drawing = Falseif mode:cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1)else:cv2.circle(img, (x, y), 5, (0, 0, 255), -1)# 创建图像
img = np.zeros((512, 512, 3), np.uint8)# 创建窗口并设置鼠标回调函数
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_shape)# 创建滑动条
cv2.createTrackbar('Size', 'image', 1, 10, lambda x: None)while True:cv2.imshow('image', img)# 处理键盘输入k = cv2.waitKey(1) & 0xFFif k == ord('m'):mode = not modeelif k == ord('q'):break# 获取滑动条的值size = cv2.getTrackbarPos('Size', 'image')# 更新笔刷的粗细if size > 0:cv2.setMouseCallback('image', draw_shape, param=size)cv2.destroyAllWindows()
在上面的代码中,我们首先定义了一些状态变量,包括是否正在绘制、绘制模式、绘制起点的坐标等。然后,我们定义了一个鼠标回调函数draw_shape()
,用于处理鼠标事件。在鼠标按下和抬起的事件中,我们根据绘制模式选择绘制矩形或圆形,并将绘制的形状添加到图像中。接下来,我们创建了一个空白图像,并创建了一个窗口并设置鼠标回调函数。然后,我们创建了一个滑动条,用于调节笔刷的粗细。接下来,我们进入一个循环,在循环中显示图像,并处理键盘输入。当用户按下’m’键时,我们切换绘制模式。当用户按下’q’键时,我们退出循环。然后,我们获取滑动条的值,并根据值更新笔刷的粗细。最后,我们销毁窗口并退出程序。