Opencv 关于Grabcut算法

一.Grabcut原理的讲解
附上大佬的讲解,个人觉得不错
https://www.cnblogs.com/zyly/p/9392881.html
https://blog.csdn.net/zouxy09/article/details/8534954

二.GrabCut算法的实现步骤
1.在图片中定义(一个或者多个)包含物体的矩形。
2.矩形外的区域被自动认为是背景。
3.对于用户定义的矩形区域,可用背景中的数据来区分它里面的前景和背景区域。
4.用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或者背景。
5.图像中的每一个像素都被看做通过虚拟边与周围像素相连接,而每条边都有一个属于前景或者背景的概率,这是基于它与周边像素颜色上的相似性。
6.每一个像素(即算法中的节点)会与一个前景或背景节点连接。
7.在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。
下图能很好的说明该算法:
在这里插入图片描述

三.GrabCut的API介绍

void grabCut( InputArray img, InputOutputArray mask, Rect rect,
                           InputOutputArray bgdModel, InputOutputArray fgdModel,
                           int iterCount, int mode = GC_EVAL );

参数:
第一个参数:img:输入图像
第二个参数:mask:得到掩码矩阵,其值为以下四种
cv::GC_BGD == 0//表示是背景
cv::GC_FGD == 1//表示是前景
cv::GC_PR_BGD == 2//表示可能是背景
cv::GC_PR_FGD == 3//表示可能是前景
第三个参数:rect:指定的包含目标对象的矩阵
第四个参数:bdgModel:背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13*5
第五个参数:fgdModel:前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;(bgdModel , fgdModel可以在 cv::GC_INIT_WITH_MASK下使用,可以在以往迭代的基础上用它们保存的信息继续迭代)
第六个参数:iterCount:指定迭代次数
第七个参数:mode:有三个值可用
cv::GC_INIT_WITH_RECT //用矩阵初始化grabCut
cv::GC_INIT_WITH_MASK //用掩码初始化grabCut
cv::GC_EVAL//执行分割

四.关于Grabcut的实例
程序效果:在原图上,用鼠标框选出一块矩形区域,按下“n”键,会将目标从原图中抠选出来,并显示。同时控制台会打印Grabcut的运行次数
附上源码:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>  //因为用到了max()和min()

using namespace cv;
using namespace std;

int numRun = 0;  //定义运行的次数
Rect rect;    
bool init = false;  //定义初始的标志位
Mat src, image;
Mat mask, bgModel, fgModel;  //定义mask(掩膜,即0和1组成)、背景、前景

void onMouse(int event, int x, int y, int flags, void*); //鼠标操作函数
void setROIMask();  //声明设置鼠标框选感兴趣区域的操作
void showImage();   //声明图像显示的操作函数
void runGrabCut();  //声明Grabcut算法的运行函数

int main(int argc, char** argv) 
{
	src = imread("E:\\图像处理图片\\30.jpg", 1);
 	if (src.empty()) 
 	{
 		printf("could not load image...\n");
  		return -1;
 	}
 	mask.create(src.size(), CV_8UC1);//mask与原图像类型一致,且为单通道八位
 	mask.setTo(Scalar::all(GC_BGD));//这行代码的作用是在不知道mask属于什么的情况下,直接全部设为背景
 	//GC_BGD表示:GC是Grabcut的缩写,BGD表示背景background的缩写

	namedWindow("效果", WINDOW_AUTOSIZE);
 	setMouseCallback("效果", onMouse, 0);
 	imshow("效果", src);

	while (true) 
	{
		char c = (char)waitKey(0);
  		if (c == 'n') //按下“n”键
  		{
  			runGrabCut();
   			numRun++;
   			showImage();
   			printf("current iteative times : %d\n", numRun);
  		}
  		if ((int)c == 27) //按下 ESC键
  		{
  			break;
  		}
	}
	waitKey(0);
 	return 0;
}

void showImage() 
{
	Mat result, binMask;
 	binMask.create(mask.size(), CV_8UC1);
 	binMask = mask & 1;  //这句不懂什么意思。如果去掉这行代码,之后框选矩形区域之后按'n',不显示效果图
 	if (init) 
 	{
 		src.copyTo(result, binMask);//按照最低位是0还是1来复制,只保留跟前景有关的图像,比如说可能的前景,可能的背景
 	}
 	else
 	{
 		src.copyTo(result);
 	}
 	rectangle(result, rect, Scalar(0, 0, 255), 2, 8); //设置用于鼠标框选的矩形方框的格式
 	imshow("效果", result);
}

void setROIMask()  //设置鼠标框选感兴趣区域的操作
{
	// GC_FGD = 1    表示是前景
 	// GC_BGD =0;    表示是背景
 	// GC_PR_FGD = 3 表示可能是前景
 	// GC_PR_BGD = 2 表示可能是背景
 	
 	mask.setTo(GC_BGD);  //虽然在主函数中已经将mask全部设为背景了,但这里以防万一,再进行一次操作
 	rect.x = max(0, rect.x);  //防止你鼠标左键选择的矩形框超过图片的边界
 	rect.y = max(0, rect.y);  //返回两个数中的较大数
 	rect.width = min(rect.width, src.cols - rect.x);  
 	rect.height = min(rect.height, src.rows - rect.y);
 	mask(rect).setTo(Scalar(GC_PR_FGD));  //将mask中的矩形区域都设为前景
}

void onMouse(int event, int x, int y, int flags, void*) 
{
	switch (event)
	{
		case EVENT_LBUTTONDOWN:   //按下左键
			//定义矩形区域的参数:左上角坐标、宽度、高度。
  			//初始标志位为false
  			//程序运行的次数初始化为0
  			rect.x = x;   
  			rect.y = y;
  			rect.width = 1;
  			rect.height = 1;
  			init = false;
  			numRun = 0;
  			break;
  		case EVENT_MOUSEMOVE:  // 鼠标移动
  			if (flags & EVENT_FLAG_LBUTTON) //如果左键拖拽(即按下左键拖动)
  			{
  				rect = Rect(Point(rect.x, rect.y), Point(x, y)); //矩形左上角、右下角
   				showImage();
  			}
  			break;
  		case EVENT_LBUTTONUP:  //松开左键
  			if (rect.width > 1 && rect.height > 1) //如果鼠标框选的矩形区域大于一个像素,就开始Grabcut操作
  			{
  				setROIMask();
   				showImage();
  			}
  			break;
  		default:
  			break;
	}
}

void runGrabCut() 
{
	if (rect.width < 2 || rect.height < 2)  //框选区域太小不会执行
	{
		return;
	}
	if (init) 
	{
		grabCut(src, mask, rect, bgModel, fgModel, 1);//用掩码初始化grabCut
	}
	{
		grabCut(src, mask, rect, bgModel, fgModel, 1, GC_INIT_WITH_RECT);//用矩阵初始化grabCut
  		init = true;
	}
}

输出:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

发布了25 篇原创文章 · 获赞 0 · 访问量 465

猜你喜欢

转载自blog.csdn.net/qq_45445740/article/details/103245214