当前位置: 首页 > news >正文

OpenCV实战——基于GrabCut算法的图像分割

OpenCV实战——基于GrabCut算法的图像分割

    • 1. GrabCut 算法
    • 2. 图像分割实战
    • 3. 完整代码
    • 相关链接

1. GrabCut 算法

在 OpenCV 策略设计模式一节中,我们已经了解了颜色信息如何用于将图像分割成与场景特定元素相对应的区域。每类对象通常具有独特的颜色,通常可以通过识别相似颜色的区域来提取。OpenCV 提供了一种流行的图像分割算法—— GrabCut 算法的实现。GrabCut 是一种复杂且计算量大的算法,但它通常会得到非常准确的结果。该算法特别适合提取图像中的前景对象,例如,将目标对象从一张图片剪切并粘贴到另一张图片中。

2. 图像分割实战

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

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

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

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

定义矩形框

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

(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_FGDcv::GC_FGD 被定义为值 13,而 cv::GC_BGDcv::GC_PR_BGD 被定义为 02

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

图像分割结果
在以上代码中,GrabCut 算法能够通过指定包含感兴趣对象的矩形来提取前景对象。或者,也可以将值 cv::GC_BGDcv::GC_FGD 分配给输入图像的某些特定像素,这些像素通过使用蒙版图像作为 cv::grabCut 函数的第二个参数提供,然后指定 GC_INIT_WITH_MASK 作为输入模式标志。例如,可以通过要求用户以交互方式标记图像中的一些元素来获得这些输入标签。也可以组合这两种输入模式。
使用以上输入信息,GrabCut 算法按以下步骤创建背景/前景分割。首先,将前景标签 (cv::GC_PR_FGD) 暂时分配给所有未标记的像素。算法根据当前的分类,将像素分为相似颜色的簇(即背景为 K 个簇,前景为 K 个簇);接下来,通过在前景和背景像素之间引入边界来确定背景/前景分割,这是通过优化过程完成的,该过程尝试将像素与相似标签进行连接,并在强度相对均匀的区域中放置边界施加惩罚,这一优化问题可以使用 Graph Cuts 算法解决,此算法方法通过将问题表示为应用切割的连通图来找到问题的最佳解,以获取最佳分割,获得的分割为像素分配新的标签。
然后重复聚类过程,并再次找到新的最佳分割,因此,GrabCut 算法是一个逐步改进分割结果的迭代过程。根据场景的复杂程度,可以在更多或更少的迭代次数中得到一个较好的解。
因此,用户可以指定要应用的迭代次数作为函数的参数。由算法维护的两个内部模型作为函数的参数传递(并返回),因此,如果希望通过执行额外的迭代来改进分割结果,可以再次使用上次运行的模型调用该函数。

3. 完整代码

完整代码 (extractObject.cpp) 如下所示:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int main() {
    // 读取输入图像
    cv::Mat image = cv::imread("1.png");
    if (!image.data) return 0;
    cv::namedWindow("Original Image");
    cv::imshow("Original Image", image);
    // 定义边界框
    cv::Rect rectangle(290, 180, 170, 215);
    cv::Mat bgModel, fgModel;
    // 分割结果
    cv::Mat result;
    // GrabCut 分割
    cv::grabCut(
        image,      // 输入图像
        result,     // 分割结果
        rectangle,  // 包含前景的矩形
        bgModel, fgModel, // 模型
        5,          // 迭代次数
        cv::GC_INIT_WITH_RECT   // 使用矩形
    );
    // 获取标记为可能前景的像素
    cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);
    // 或者
    // result = result & 1;
    // 创建白色图像
    cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
    image.copyTo(foreground, result);
    // 在原图中绘制矩形框
    cv::rectangle(image, rectangle, cv::Scalar(255, 255, 255), 1);
    cv::namedWindow("Image with rectangle");
    cv::imshow("Image with rectangle", image);
    // 显示结果
    cv::namedWindow("Foreground object");
    cv::imshow("Foreground object", foreground);
    cv::waitKey();
    return 0;
}

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式

相关文章:

  • 书法网站优化关键词/宁海关键词优化怎么优化
  • 网店运营招聘要求/深圳seo优化推广公司
  • 网站建设在哪个软件下做/百度竞价排名收费标准
  • 织梦 做网站 知乎/重庆百度推广排名优化
  • 襄阳做网站哪家好/长沙网站推广有哪些啊
  • 日本有个做二十四节气照片的网站/广东云浮疫情最新情况
  • React Native 初次安裝踩坑
  • ADI demo PL工程的编译-以adrv9371x_zc706为例子之使用Cygwin
  • 【Go基础】结构体
  • 2022年个人年终总结(一)
  • 如何使用OpenDRIVE
  • 邂逅Vue.js开发
  • linux基本功系列之pwd命令实战
  • 注意力机制
  • aardio - 升级bindConfig函数,支持多属性和多子组件
  • 《Unity Shader 入门精要》第2章 渲染流水线
  • 【ROS】dynamic_reconfigure配置详细说明
  • 【JavaSE】Java反射机制详解