OpenCV-Python(28):基于GrabCut 算法交互式前景提取

发布时间:2024年01月05日

目标

  • 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 np

img = 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_RECT

mask, 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_over

    if event == cv2.EVENT_LBUTTONDOWN:
        if not rectangle:
            rectangle = True
            rect = (x, y, 1, 1)
        else:
            rectangle = False
            rect_over = True
    elif 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) & 0xFF
    if k == ord('r'):
        rect = (0, 0, 1, 1)
        rectangle = False
        rect_over = False
    elif k == ord('c'):
        mode = cv2.GC_INIT_WITH_RECT
        print('Draw a rectangle around the object to be segmented')
    elif k == ord('f'):
        mode = cv2.GC_INIT_WITH_MASK
        print('Mark the areas in the image where the object and background are')
    elif k == 13: # Enter键
        break

cv2.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 np

drawing = False  # 是否正在绘制
mode = True  # True表示绘制矩形,False表示绘制圆形
ix, iy = -1, -1  # 绘制起点坐标

# 鼠标回调函数
def draw_shape(event, x, y, flags, param):
    global ix, iy, drawing, mode

    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix, iy = x, y
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        if 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) & 0xFF
    if k == ord('m'):
        mode = not mode
    elif 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’键时,我们退出循环。然后,我们获取滑动条的值,并根据值更新笔刷的粗细。最后,我们销毁窗口并退出程序。

文章来源:https://blog.csdn.net/mzl_18353516147/article/details/135409906
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。