【OpenCV】 GrabCut.cpp详解

grabCut的方法参数

从核心代码开始

grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK);


*image是读入的原图,mask是跟原图一样大小,用来标记原图的每个点,是前景、背景、可能的前景、可能的背景。看下图:这是个30*50的图片。经过赋值后,传入的mask值如下(至于如何给mask赋值,下面说)。我们就根据这个mask矩阵来判断,0的位置肯定是背景,而3的位置是可能的前景。接下来的很多处理都是再操作这个mask矩阵。我们还可以给3的位置继续标记。


下边是继续给mask标记后的结果:

可以看到,我们在可能的前景点里,标记出一块肯定是背景的区域。这个就是传入grabcut算法里的mask。



opencv里规定的四种状态:

背景:0

前景:1

可能的背景:2

可能的前景:3


继续看grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK);


第三个参数为rect,是一个矩形框,结构体为起始点和长宽。就是标记出来上边33333的外边框这个矩形。

bgdModel、fgdModel是中间变量,不用管。打印出来是一维的浮点数矩阵。看不懂意思。

最后一个参数GC_INIT_WITH_MASK。因为针对mask有很多标记方式(0,1,2,3,),这个和标记方式有关。我的项目只用到在全是3的举行里面继续标记0.所以设置的GC_INIT_WITH_MASK,根据mask来算。


流程

first

先读进来:Mat image = imread("c:/123.jpg", 1);


second

显示出来:进的setImageAndWinName这个方法里,这个方法里做了三件事:

1、显示出图片来,

2、把读入进来的Mat image赋值给了一个全局变量image,以后就操作后边这个image了。

3、创建和image一样大小的mask。这个mask也是一个Mat。

third

调用showImage()显示图片。(这个showImage()会被一直调,来保证图片上画的线可以实时更新显示),这个方法里是为了显示绿色矩形框和标记那四种状态。用鼠标动一下或者点击一下都会触发这个方法。把鼠标操作痕迹留在图上。如图:


这个图的意思是:绿色矩形框以外的肯定是背景(0),而绿色区域里面的蓝色区域也确定是背景(0)。其他的地方不确定(3)。到时候就都表示到mask里。


forth

就开始鼠标操作了,进mouseClick方法,跟安卓里的手势操作一样。

鼠标move时,进case CV_EVENT_MOUSEMOVE:

因为move时得判断外边矩形是不是画好了,因为要画里面的蓝色线必须先把外边矩形画好。

所以先画矩形:做两件事:

1、调用setRectInMask,把画好的矩形标记到mask里。

2、showImage()

然后判断是不是画蓝色:如果是蓝色,要做两件事:

1、把蓝色的点标记到mask上,进setLblsInMask方法,

核心的语句就是这个circle(mask, p, radius, bvalue, thickness);

bvalue已经在刚进这个方法的时候,赋值为0了,用的是它枚举的类型(GC_BGD)。

2、showImage()


最后调用grabcut()方法就可以完成这个流程了。



附代码和注释

我把我不需要用到的地方加了/**/了。


#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>

using namespace std;
using namespace cv;

static void help()
{
	cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
		"and then grabcut will attempt to segment it out.\n"
		"Call:\n"
		"./grabcut <image_name>\n"
		"\nSelect a rectangular area around the object you want to segment\n" <<
		"\nHot keys: \n"
		"\tESC - quit the program\n"
		"\tr - restore the original image\n"
		"\tn - next iteration\n"
		"\n"
		"\tleft mouse button - set rectangle\n"
		"\n"
		"\tCTRL+left mouse button - set GC_BGD pixels\n"
		"\tSHIFT+left mouse button - set GC_FGD pixels\n"
		"\n"
		"\tCTRL+right mouse button - set GC_PR_BGD pixels\n"
		"\tSHIFT+right mouse button - set GC_PR_FGD pixels\n" << endl;
}

const Scalar RED = Scalar(0, 0, 255);
const Scalar PINK = Scalar(230, 130, 255);
const Scalar BLUE = Scalar(255, 0, 0);
const Scalar LIGHTBLUE = Scalar(255, 255, 160);
const Scalar GREEN = Scalar(0, 255, 0);

const int BGD_KEY = CV_EVENT_FLAG_CTRLKEY;
const int FGD_KEY = CV_EVENT_FLAG_SHIFTKEY;

static void getBinMask(const Mat& comMask, Mat& binMask)
{
	cout << "getBinMask"<<endl;
	if (comMask.empty() || comMask.type() != CV_8UC1)
		CV_Error(CV_StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)");
	if (binMask.empty() || binMask.rows != comMask.rows || binMask.cols != comMask.cols)
		binMask.create(comMask.size(), CV_8UC1);
	binMask = comMask & 1;

}

class GCApplication
{
public:
	enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 };
	static const int radius = 2;
	static const int thickness = -1;

	void reset();
	void setImageAndWinName(const Mat& _image, const string& _winName);
	void showImage() const;
	void mouseClick(int event, int x, int y, int flags, void* param);
	int nextIter();
	int getIterCount() const { return iterCount; }
private:
	void setRectInMask();
	void setLblsInMask(int flags, Point p, bool isPr);

	const string* winName;
	const Mat* image;
	Mat mask;
	Mat bgdModel, fgdModel;

	uchar rectState, lblsState, prLblsState;
	bool isInitialized;

	Rect rect;
	vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;
	int iterCount;
};


//清空点集合
void GCApplication::reset()
{
	cout << "reset" << endl;
	if (!mask.empty())
		mask.setTo(Scalar::all(GC_BGD));
	bgdPxls.clear(); fgdPxls.clear();
	prBgdPxls.clear();  prFgdPxls.clear();

	isInitialized = false;
	rectState = NOT_SET;
	lblsState = NOT_SET;
	prLblsState = NOT_SET;
	iterCount = 0;
}
//初始化,把image赋给全局变量image
void GCApplication::setImageAndWinName(const Mat& _image, const string& _winName)
{
	cout << "setImageAndWinName" << endl;
	if (_image.empty() || _winName.empty())
		return;
	image = &_image;
	winName = &_winName;
	mask.create(image->size(), CV_8UC1);
	reset();
}
//负责显示矩形框和画的蓝色区域
void GCApplication::showImage() const
{
	cout << "showImage" << endl;
	if (image->empty() || winName->empty())
		return;

	Mat res;
	Mat binMask;
	if (!isInitialized){
		cout << "!isInitialized" << endl;
		image->copyTo(res);
	}
	else
	{
		cout << "isInitialized" << endl;
		getBinMask(mask, binMask);
		image->copyTo(res, binMask);
	}

	vector<Point>::const_iterator it;
	for (it = bgdPxls.begin(); it != bgdPxls.end(); ++it){
		//进的这里
		//cout << "bgdPxls" << bgdPxls << endl;
		//就是为了把以前的点也保存下来,所以不能直接打印
		//所以说 bgdPxls
		//这里就是为了显示,而真正的mask没在这里
		circle(res, *it, radius, BLUE, thickness);

	}
	for (it = fgdPxls.begin(); it != fgdPxls.end(); ++it){
		cout << "fgdPxls" << endl;
		circle(res, *it, radius, RED, thickness);
	}
	for (it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it){
		cout << "prBgdPxls" << endl;
		circle(res, *it, radius, LIGHTBLUE, thickness);
	}
	for (it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it){
		cout << "prFgdPxls" << endl;
		circle(res, *it, radius, PINK, thickness);
	}
	if (rectState == IN_PROCESS || rectState == SET){
		//意思是无论如何,都要显示出来绿色的矩形框
		cout << "IN_PROCESS || SET" << endl;
		rectangle(res, Point(rect.x, rect.y), Point(rect.x + rect.width, rect.y + rect.height), GREEN, 2);
	}
	//把res显示出来
	imshow(*winName, res);
}

void GCApplication::setRectInMask()
{
	cout << "setRectInMask" << endl;
	assert(!mask.empty());
	mask.setTo(GC_BGD);
	rect.x = max(0, rect.x);
	rect.y = max(0, rect.y);
	rect.width = min(rect.width, image->cols - rect.x);
	rect.height = min(rect.height, image->rows - rect.y);
	(mask(rect)).setTo(Scalar(GC_PR_FGD));
}

void GCApplication::setLblsInMask(int flags, Point p, bool isPr)
{
	cout << "setLblsInMask" << endl;
	vector<Point> *bpxls, *fpxls;
	uchar bvalue, fvalue;
	if (!isPr)
	{
		cout << "这里";
		bpxls = &bgdPxls;
		fpxls = &fgdPxls;
		bvalue = GC_BGD;
		fvalue = GC_FGD;
	}
	
	else
	{
		
		bpxls = &prBgdPxls;
		fpxls = &prFgdPxls;
		bvalue = GC_PR_BGD;
		fvalue = GC_PR_FGD;
	}
	
	if (flags & BGD_KEY)
	{
		
		//Add element at the end
		//bpxls相当于直接对bgdPxls操作
		bpxls->push_back(p);
		//cout << "bpxls" << *bpxls;
		//cout << "bgdPxls" << bgdPxls;
		circle(mask, p, radius, bvalue, thickness);

	}
	
	if (flags & FGD_KEY)
	{
	
		fpxls->push_back(p);
		circle(mask, p, radius, fvalue, thickness);
	}
	
}

//传入x,y的点坐标,然后直接赋值给Point (x,y)——》然后进setLblsInMask
void GCApplication::mouseClick(int event, int x, int y, int flags, void*)
{
	// TODO add bad args check
	switch (event)
	{
	case CV_EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels
	{
		
		bool isb = (flags & BGD_KEY) != 0,
			isf = (flags & FGD_KEY) != 0;
		if (rectState == NOT_SET && !isb && !isf)
		{
			//这里不是按住ctrl的
			rectState = IN_PROCESS;
			rect = Rect(x, y, 1, 1);
		}
		if ((isb || isf) && rectState == SET){
			//cout << "这里是按住ctrl之后的CV_EVENT_LBUTTONDOWN" << endl;
			lblsState = IN_PROCESS;
		}
	}
		break;
		/*
		case CV_EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels
		{
			cout << "CV_EVENT_RBUTTONDOWN"<<endl;
			bool isb = (flags & BGD_KEY) != 0,
				isf = (flags & FGD_KEY) != 0;
			if ((isb || isf) && rectState == SET)
				prLblsState = IN_PROCESS;
		}
			break;
		*/
	case CV_EVENT_LBUTTONUP:
		
		if (rectState == IN_PROCESS)
		{
			//这里不是按住ctrl的
			rect = Rect(Point(rect.x, rect.y), Point(x, y));
			rectState = SET;
			setRectInMask();
			assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());
			showImage();
		}
		if (lblsState == IN_PROCESS)
		{
			//cout << "这里是按住ctrl之后的CV_EVENT_LBUTTONUP" << endl;
			
			//把点给mask设置上去
			setLblsInMask(flags, Point(x, y), false);
			//修改更改状态
			lblsState = SET;
			//显示image
			showImage();
			//TODO 这里打印一下mask的值

			//cout << " mask" << mask;

		}
		break;
		/*
		case CV_EVENT_RBUTTONUP:
			cout << "CV_EVENT_RBUTTONUP" << endl;
			if (prLblsState == IN_PROCESS)
			{
				setLblsInMask(flags, Point(x, y), true);
				prLblsState = SET;
				showImage();
			}
		break;
		*/
	case CV_EVENT_MOUSEMOVE:
		//cout << "CV_EVENT_MOUSEMOVE" << endl;
		if (rectState == IN_PROCESS)
		{	
			//没有按住的
			//cout << "画矩形画的点" << endl;
			rect = Rect(Point(rect.x, rect.y), Point(x, y));
			assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());
			showImage();
		}
		else if (lblsState == IN_PROCESS)
		{
			//按住crtl之后移动的点
			cout << "crtl移动" << endl;
			setLblsInMask(flags, Point(x, y), false);
			showImage();
		}
		/*
		else if (prLblsState == IN_PROCESS)
		{
			
			setLblsInMask(flags, Point(x, y), true);
			showImage();
		}
		*/
		break;

	}
}

int GCApplication::nextIter()
{
	if (isInitialized){
		cout << "init";
		grabCut(*image, mask, rect, bgdModel, fgdModel, 1);
	}
	else
	{
		if (rectState != SET)
			return iterCount;

		if (lblsState == SET || prLblsState == SET)
		{
			cout << "MASK" <<"rect" << rect << endl;
			grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK);

		}
		else
		{
			cout << "RECT";
			grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT);
		}
		isInitialized = true;
	}
	iterCount++;

	bgdPxls.clear(); fgdPxls.clear();
	prBgdPxls.clear(); prFgdPxls.clear();

	return iterCount;
}

GCApplication gcapp;

static void on_mouse(int event, int x, int y, int flags, void* param)
{
	gcapp.mouseClick(event, x, y, flags, param);
}

int main(int argc, char** argv)
{

	string filename = "c:/123.jpg";

	Mat image = imread(filename, 1);
	if (image.empty())
	{
		cout << "\n Durn, couldn't read image filename " << filename << endl;
		return 1;
	}

	help();

	const string winName = "image";
	namedWindow(winName, WINDOW_AUTOSIZE);
	setMouseCallback(winName, on_mouse, 0);

	gcapp.setImageAndWinName(image, winName);
	gcapp.showImage();

	for (;;)
	{
		int c = waitKey(0);
		switch ((char)c)
		{
		case '\x1b':
			cout << "Exiting ..." << endl;
			goto exit_main;
		case 'r':
			cout << endl;
			gcapp.reset();
			gcapp.showImage();
			break;
		case 'n':
			int iterCount = gcapp.getIterCount();
			cout << "<" << iterCount << "... ";
			int newIterCount = gcapp.nextIter();
			if (newIterCount > iterCount)
			{
				gcapp.showImage();
				cout << iterCount << ">" << endl;
			}
			else
				cout << "rect must be determined>" << endl;
			break;
		}
	}

exit_main:
	destroyWindow(winName);
	return 0;
}



参考文章:

copyTo的使用

http://blog.csdn.net/jdpshq/article/details/9007627

Mat结构体

http://petersky0316.blog.163.com/blog/static/177063371201311714849746/

vector::push_back

http://blog.163.com/chen_dawn/blog/static/112506320109125221959/

GrabCut算法介绍

http://www.cnblogs.com/mikewolf2002/p/3330390.html

OpenCV的Java手册

http://docs.opencv.org/java/2.4.11/

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值