OpenCV使用 GrabCut 算法分割图像

615 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

前言

OpenCV 提供了一种流行的图像分割算法——GrabCut 算法的实现。 GrabCut 是一种复杂且计算量大的算法,但它通常会得到非常准确的结果。该算法特别适合提取图像中的前景对象,例如,将目标对象从一张图片剪切并粘贴到另一张图片。

使用 GrabCut 算法分割图像

cv::grabCut 函数的使用方法非常简单,只需要输入一个图像并将其中的一些像素标记为属于背景或前景。基于这些标记,算法可以分割图像的前景/背景。

1. 为输入图像指定部分前景/背景标签的一种方法是定义一个矩形,其中包含前景对象:

// 定义边界框
cv::Rect rectangle(290, 180, 170, 215);

以上代码定义了图像中的以下区域:

F2.png

在输入图像中,该矩形之外的所有像素都将被标记为背景。

2. 调用 cv::grabCut 函数需要定义两个矩阵,其中包含算法构建的模型:

// 分割结果
cv::Mat result;
// GrabCut 分割
cv::grabCut(
    image,      // 输入图像
    result,     // 分割结果
    rectangle,  // 包含前景的矩形
    bgModel, fgModel, // 模型
    5,          // 迭代次数
    cv::GC_INIT_WITH_RECT   // 使用矩形
);

我们使用 cv::GC_INIT_WITH_RECT 标志作为函数的最后一个参数,用于指定边界矩形模式。

3. 输入/输出分割图像中的像素值可能属于以下四个值之一:

  • cv::GC_BGD:肯定属于背景的像素的值(例如,上图中矩形外的像素)
  • cv::GC_FGD:肯定属于前景的像素的值
  • cv::GC_PR_BGD:可能属于背景的像素值
  • cv::GC_PR_FGD:可能属于前景的像素的值(例如,上图中矩形内像素的初始值)

4. 我们通过提取像素值等于 cv::GC_PR_FGD 的像素来获得分割的二值图像:

// 获取标记为可能前景的像素
cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);
// 创建白色图像
cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
image.copyTo(foreground, result);

5. 要提取所有前景像素,即值等于 cv::GC_PR_FGDcv::GC_FGD,可以使用位运算获取:

result = result & 1;

常量 cv::GC_PR_FGD 和 cv::GC_FGD 被定义为值 13,而 cv::GC_BGDcv::GC_PR_BGD 被定义为 02

执行以上程序,可以得到下图:

F3.png

在以上代码中,GrabCut 算法能够通过指定包含感兴趣对象的矩形来提取前景对象。或者,也可以将值 cv::GC_BGDcv::GC_FGD 分配给输入图像的某些特定像素,这些像素通过使用蒙版图像作为 cv::grabCut 函数的第二个参数提供,然后指定 GC_INIT_WITH_MASK 作为输入模式标志。例如,可以通过要求用户以交互方式标记图像中的一些元素来获得这些输入标签。也可以组合这两种输入模式。

使用以上输入信息,GrabCut 算法按以下步骤创建背景/前景分割。首先,将前景标签 (cv::GC_PR_FGD) 暂时分配给所有未标记的像素。算法根据当前的分类,将像素分为相似颜色的簇(即背景为 K 个簇,前景为 K 个簇);接下来,通过在前景和背景像素之间引入边界来确定背景/前景分割,这是通过优化过程完成的,该过程尝试将像素与相似标签进行连接,并在强度相对均匀的区域中放置边界施加惩罚,这一优化问题可以使用 Graph Cuts 算法解决,此算法方法通过将问题表示为应用切割的连通图来找到问题的最佳解,以获取最佳分割,获得的分割为像素分配新的标签。