9.6grabCut进行分割

基本概念

grabCut 算法是 OpenCV 中一个非常强大的图像分割方法,主要用于前景与背景的分割。它基于图割(Graph Cut)算法,并通过迭代的方式优化能量函数,以达到更好的分割效果。在 OpenCV 中,grabCut 函数通常用于处理包含复杂背景的图片,以分离出感兴趣的前景对象。

grabCut() 是 OpenCV 提供的一个高级图像分割算法,它可以用来自动地分割图像中的对象。GrabCut 算法最初由 Carsten Rother 等人在 2004 年提出,它结合了图割技术与机器学习方法,可以在用户提供少量的指导信息后,自动地完成复杂的分割任务。

grabCut 函数详解

grabCut 函数的原型通常如下(基于 OpenCV 4.x):

void cv::grabCut(InputArray img,   
                 InputOutputArray mask,   
                 Rect rect,   
                 InputOutputArray bgdModel,   
                 InputOutputArray fgdModel,   
                 int iterCount,   
                 int mode=GC_INIT_WITH_RECT);
参数详解
img:输入图像,通常应为 8 位 3 通道图像(例如,CV_8UC3 类型的图像)。
mask:操作掩码,用于指定每个像素的状态(可能是背景、前景、可能是背景、可能是前景)。掩码在调用函数前需要初始化,通常是将感兴趣的区域(ROI)内的像素标记为 GC_PR_FGD(可能是前景),ROI 外的像素标记为 GC_BGD(背景),ROI 边界上的像素标记为 GC_PR_BGD(可能是背景)或 GC_PR_FGD(可能是前景)。
rect:包含感兴趣区域的矩形。这个矩形应该尽可能小但完全包含目标对象。
bgdModel 和 fgdModel:分别用于存储背景和前景的模型。这些通常是浮点数数组,但在 OpenCV 中通常作为 Mat 对象处理。它们的大小依赖于迭代次数和图像大小,但通常不需要手动设置。
iterCount:算法执行的最大迭代次数。迭代次数越多,分割效果通常越好,但计算时间也越长。
mode:算法的工作模式。GC_INIT_WITH_RECT 表示使用矩形初始化;GC_INIT_WITH_MASK 表示使用掩码初始化。

使用示例1

以下是一个简单的使用 grabCut 的示例:

#include <opencv2/opencv.hpp>  
#include <iostream>  
  
int main() {  
    cv::Mat img = cv::imread("path_to_image.jpg");  
    if (img.empty()) {  
        std::cerr << "Could not read the image." << std::endl;  
        return 1;  
    }  
  
    cv::Rect rect(50, 50, 450, 290); // 假设的感兴趣区域  
    cv::Mat mask = cv::Mat::zeros(img.size(), CV_8UC1);  
    cv::Rect rect_bgd(0, 0, img.cols, img.rows);  
    mask(rect_bgd).setTo(cv::GC_BGD);  
    mask(rect).setTo(cv::Scalar::all(cv::GC_PR_FGD));  
  
    cv::Mat bgdModel, fgdModel;  
    int iterCount = 5; // 迭代次数  
    cv::grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, cv::GC_INIT_WITH_RECT);  
  
    // 创建一个结果图像  
    cv::Mat result;  
    img.copyTo(result, mask == cv::GC_FGD);  
  
    cv::imshow("Result", result);  
    cv::waitKey(0);  
    return 0;  
}

运行结果1

下面是如何使用 OpenCV 的 grabCut() 函数进行图像分割的详细步骤:

1.导入必要的头文件:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

2.加载图像:
Mat src = imread("path/to/your/image.jpg");
if (src.empty()) {
    cout << "Could not open or find the image" << endl;
    return -1;
}

3.创建掩码:
grabCut() 需要一个掩码来指示哪些区域是确定前景、确定背景或不确定的。我们可以初始化一个与图像大小相同的矩阵,然后手动填充一些区域作为提示。
Mat mask(src.size(), CV_8UC1, Scalar(0)); // 初始化为全背景
Rect rect(x, y, w, h); // 指定一个矩形框选区域作为提示
int cvtRect[] = {rect.x, rect.y, rect.width, rect.height};
grabCut(src, mask, Rect(cvtRect[0], cvtRect[1], cvtRect[2], cvtRect[3]), Mat(), Mat(), 5, GC_INIT_WITH_RECT);

4.细化掩码:
grabCut() 执行后,掩码会被更新。你可以根据需要进一步细化掩码。
Mat bgModel, fgModel; // 这两个模型用于存储背景和前景的概率模型
grabCut(src, mask, Rect(), bgModel, fgModel, 5, GC_INIT_WITH_MASK);

5.应用掩码:
应用掩码到原图上,得到分割后的图像。
Mat dst;
src.copyTo(dst, mask == 1); // 只复制确定为前景的部分

6.显示结果:
显示原始图像和分割
namedWindow("Original Image", WINDOW_AUTOSIZE);
imshow("Original Image", src);

namedWindow("Segmented Image", WINDOW_AUTOSIZE);
imshow("Segmented Image", dst);

7.等待按键退出:
waitKey(0); // Wait for a keystroke in the window

8.清理:
不要忘记释放所有使用的内存资源。
destroyAllWindows();

注意事项
 grabCut() 函数需要用户提供的提示来区分前景和背景。这些提示可以通过矩形框或者手动绘制的掩码来提供。
 grabCut() 可以通过迭代来逐步改进分割结果。参数 iterations 控制迭代次数。
 GC_INIT_WITH_RECT 表示使用矩形框作为初始提示;GC_INIT_WITH_MASK 则表示使用掩码作为初始提示。
分割结果储存在掩码中,其中:
   GC_BGD (0) 表示确定的背景像素。
   GC_FGD (1) 表示确定的前景像素。
   GC_PR_BGD (2) 表示可能是背景的像素。
   GC_PR_FGD (3) 表示可能是前景的像素。
这个例子演示了如何使用 grabCut() 函数来分割图像中的对象。实际应用中,你可能需要根据具体需求调整矩形框的位置和大小,或者使用交互式的方式让用户手动标注部分区域来提高分割精度

完整代码2


#include "pch.h"
//1.导入必要的头文件:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

int main()
{


	//2.加载图像:
	Mat src = imread("88.jpeg");
	if (src.empty()) 
	{
		cout << "Could not open or find the image" << endl;
		return -1;
	}

	//3.创建掩码:
	//	grabCut() 需要一个掩码来指示哪些区域是确定前景、确定背景或不确定的。我们可以初始化一个与图像大小相同的矩阵,然后手动填充一些区域作为提示。
	Mat mask(src.size(), CV_8UC1, Scalar(0)); // 初始化为全背景
	Rect rect(120, 120, 500, 1120); // 指定一个矩形框选区域作为提示
	int cvtRect[] = { rect.x, rect.y, rect.width, rect.height };
	grabCut(src, mask, Rect(cvtRect[0], cvtRect[1], cvtRect[2], cvtRect[3]), Mat(), Mat(), 5, GC_INIT_WITH_RECT);

	//4.细化掩码:
	//	grabCut() 执行后,掩码会被更新。你可以根据需要进一步细化掩码。
		Mat bgModel, fgModel; // 这两个模型用于存储背景和前景的概率模型
	grabCut(src, mask, Rect(), bgModel, fgModel, 5, GC_INIT_WITH_MASK);

	//5.应用掩码:
	//应用掩码到原图上,得到分割后的图像。
		Mat dst;
	src.copyTo(dst, mask == 1); // 只复制确定为前景的部分

	//6.显示结果:
		//显示原始图像和分割
		namedWindow("Original Image", WINDOW_NORMAL);
	imshow("Original Image", src);

	namedWindow("Segmented Image", WINDOW_NORMAL);
	imshow("Segmented Image", dst);

	//7.等待按键退出:
		waitKey(0); // Wait for a keystroke in the window

	//8.清理:
		//不要忘记释放所有使用的内存资源。
		destroyAllWindows();
	return 0;

}
//注意事项
//grabCut() 函数需要用户提供的提示来区分前景和背景。这些提示可以通过矩形框或者手动绘制的掩码来提供。
//grabCut() 可以通过迭代来逐步改进分割结果。参数 iterations 控制迭代次数。
//GC_INIT_WITH_RECT 表示使用矩形框作为初始提示;GC_INIT_WITH_MASK 则表示使用掩码作为初始提示。
//分割结果储存在掩码中,其中:
//GC_BGD(0) 表示确定的背景像素。
//GC_FGD(1) 表示确定的前景像素。
//GC_PR_BGD(2) 表示可能是背景的像素。
//GC_PR_FGD(3) 表示可能是前景的像素。
//这个例子演示了如何使用 grabCut() 函数来分割图像中的对象。实际应用中,你可能需要根据具体需求调整矩形框的位置和大小,或者使用交互式的方式让用户手动标注部分区域来提高分割精度

运行结果2

注意

1.grabCut 需要良好的初始估计,特别是 ROI 的选择对最终结果有很大影响。

2.迭代次数是一个重要的参数,需要根据实际情况调整以达到最佳效果。

3.grabCut 在处理复杂背景或前景对象与背景颜色相近时可能效果不佳。

4.结果图像 result 是通过将原图 img 复制到新图 result 中,但只复制那些被标记为前景的像素来得到的。

实验代码3


#include "pch.h"
//#pragma comment(lib, "opencv_world450d.lib")   
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#include <iostream>
using namespace std;
using namespace cv;
 
void main()
{
	Mat src = imread("02.png");
	Rect rect(84, 84, 406, 318);//左上坐标(X,Y)和长宽
	Mat result, bg, fg;

	grabCut(src, result, rect, bg, fg, 1, GC_INIT_WITH_RECT);
	imshow("grab", result);
	/*threshold(result, result, 2, 255, CV_THRESH_BINARY);
	imshow("threshold", result);*/

	compare(result, GC_PR_FGD, result, CMP_EQ);//result和GC_PR_FGD对应像素相等时,目标图像该像素值置为255
	imshow("result", result);
	Mat foreground(src.size(), CV_8UC3, Scalar(255, 255, 255));
	src.copyTo(foreground, result);//copyTo有两种形式,此形式表示result为mask

	namedWindow("foreground", WINDOW_NORMAL);
	imshow("foreground", foreground);
	waitKey(0);
}

运行结果3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值