在图像处理的过程中,会经常碰到需要提取选定区域的前景,以下就使用grabCut算法进行图像前景的提取(我是用的opencv版本为opencv2.4.3.)。这是我的第一篇博客,有什么不足的地方欢迎大家指导。
关于grabCut算法的原理,我也是参照大牛写的文章,[这里]就是原文地址。(https://blog.csdn.net/zouxy09/article/details/8534954)
在进行图像前景的提取操作之前,我们需要先设置鼠标点击事件,这里我是用的是opencv的鼠标回调事件,首先定义回调函数,代码如下所示:void onMouse(int event,int x,int y,int flags,void* param);
回调函数定义完成之后需要将我们的回调函数设置在固定窗口之中,这里使用opencv的highgui模块中的setMouseCallback函数,代码如下所示:setMouseCallback(winTitle,onMouse); //设置鼠标回调函数
其中winTitle为窗口的名称,onMouse为之前定义的是鼠标回调函数。
将鼠标回调函数设置完毕之后,需要写回调函数中的具体内容了,废话不多说,直接上代码。
void onMouse(int event,int x,int y,int flags,void* param)
{
switch(event)
{
case EVENT_LBUTTONDOWN: //鼠标左键按下事件
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)
{
setROIMask(); //设置矩形区域为可能前景区域
showImage();
}
break;
default:
break;
}
}
在这个函数中,我仅仅是对鼠标左键进行设置,包括左键按下事件,鼠标移动事件以及左键松开事件。
做完以上步骤之后,可以运行一下,看看是否达到我们要求的效果,我的运行结果如下图所示:
完成了以上步骤之后,就可以设置图片的前景与后景了。在鼠标回调函数中,可以看到我使用了自定义的setROIMask函数,该函数的主要目的是将框选的区域设置为可能的前景,代码如下所示:
void setROIMask()
{
//GC_BGD=0----背景
//GC_FGD=1----前景
//GC_PR_BGD=2----可能为背景
//GC_PR_FGD=3----可能为前景
mask.setTo(GC_BGD); //全部设置为背景
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)); //将矩形区域设置为可能的前景
}
这里有必要对setTo中的参数进行说明,正如上面代码所说,GC_BGD表示设置为背景,GC_FGD表示设置为前景,GC_PR_BGD表示设置为疑似背景GC_PR_FGD表示设置为疑似前景。在鼠标左键松开事件中调用的setROIMask函数,就是将鼠标框选的矩形区域设置为疑似前景部分。
在完成对图像的前后景设置之后,就可以使用grabCut算法进行前景的分割提取了。
grabCut(src,mask,rect,bgModel,fgModel,1); //图割算法
以下是使用grabCut算法得到的图片前景,如果觉得效果不太理想,可以进行多次迭代,以达到更好的效果。
关于opencv的图像分割算法到这里就差不多了,好的,哪下面就放上完整代码,以供大家参考并指正。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int numRun=0;
Rect rect;
bool init=false;
Mat src,image;
Mat mask,bgModel,fgModel;
const char* winTitle="input image";
void onMouse(int event,int x,int y,int flags,void* param);
void setROIMask();
void showImage();
void runGrabCut();
int main()
{
src=imread("flower.jpg",1);
if(src.empty())
{
cout<<"could not load image..."<<endl;
return -1;
}
mask.create(src.size(),CV_8UC1);
mask.setTo(Scalar::all(GC_BGD)); //将整个图片设置为背景
namedWindow(winTitle,CV_WINDOW_AUTOSIZE); //声明窗口
setMouseCallback(winTitle,onMouse); //设置鼠标回调函数
imshow(winTitle,src); //展示原图
while(true)
{
char c=(char)waitKey(0); //获取用户 按下的按键
if(c=='n')
{
runGrabCut(); //开始图割算法,运行一次为迭代一次
numRun++; //迭代次数加1
showImage(); //显示图像
//imshow("背景",bgModel);
//imshow("前景",fgModel);
cout<<"迭代次数为:"<<numRun<<endl;
}
if((int)c==27)
{
break; //检测到按下esc按钮
}
}
waitKey(0);
return 0;
}
void showImage()
{
Mat result,binMask;
binMask.create(mask.size(),CV_8UC1);
binMask=mask & 1;
if(init)
{
src.copyTo(result,binMask);
}
else
{
src.copyTo(result);
}
rectangle(result,rect,Scalar(0,0,255),2,8); //画矩形
imshow(winTitle,result);
}
void setROIMask()
{
//GC_BGD=0----背景
//GC_FGD=1----前景
//GC_PR_BGD=2----可能为背景
//GC_PR_FGD=3----可能为前景
mask.setTo(GC_BGD); //全部设置为背景
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)); //将矩形区域设置为可能的前景
}
void onMouse(int event,int x,int y,int flags,void* param)
{
switch(event)
{
case EVENT_LBUTTONDOWN: //鼠标左键按下事件
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)
{
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(src,mask,rect,bgModel,fgModel,1,GC_INIT_WITH_RECT);
init=true;
}
}
关于图片和完整工程,大家可以去这里下载。