Grabcut
原理部分请大家自行百度学习,本人不擅长。
- 基于交互式界面由用户选择前景区域;
- 定义一个单通道的输出掩码,0为背景,1为前景,2为可能的背景,3为可能的前景;
- grabCut抠图;将输出结果与可能的前景作比较得到可能的前景;
- 定义三通道的结果图像;
- 从原图中拷贝可能的前景到结果图像;
相关api
函数api
grabCut( InputArray img,
InputOutputArray mask,
Rect rect,
InputOutputArray bgdModel,
InputOutputArray fgdModel,
int iterCount,
int mode = GC_EVAL );
参数介绍
- img:输入原图像;
- mask:8位单通道掩码。如果函数设置
mode= GC_INIT_WITH_RECT
,则该掩码由函数来初始化。它的元素值是GrabCutClasses
中的一个。; - rect:包含待分割目标的ROI区域。在该区域之外的像素被标记为"obvious background(背景)"。这个参数仅在
mode==GC_INIT_WITH_RECT
的时候使用; - bgModel:输出背景图像;
- fgModel:输出前景图像;
- iterCount:迭代次数;
- mode: 默认GC_EVAL:必须是枚举类型
GrabCutModes
中的一种
GC_INIT_WITH_RECT | 该函数使用提供的矩形初始化状态和掩码。之后,它将运行算法的iterCount 迭代 |
---|---|
GC_INIT_WITH_MASK | 该函数使用提供的掩码初始化状态。 请注意,可以合并GC_INIT_WITH_RECT和GC_INIT_WITH_MASK。 然后,使用GC_BGD自动初始化ROI之外的所有像素。 |
GC_EVAL | 该值表示算法应恢复 |
GC_EVAL_FREEZE_MODEL | 该值表示该算法应仅使用固定模型运行grabCut 算法(单次迭代) |
代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "flower.jpeg"
Rect rect; //roi区域
bool init =false; //扣图 显示标志位
Mat src;
Mat mask,bgModel,fgModel; //掩码矩阵
void on_mouse_callback(int event,int x,int y,int flags,void *param);
void show_img(void);
void set_roi(void);
string win_name = "src pic";
int numRun =0;
void run_grabcut(void);
int main(void)
{
//获取完整的图片路径及名称
string pic = string(PIC_PATH)+string(PIC_NAME);
//打印图片路径
cout << "pic path is :"<<pic<<endl;
//读取图片
src = imread(pic);
//判断图片是否存在
if(src.empty())
{
cout<<"pic is not exist!!!!"<<endl;
return -1;
}
//显示图片
namedWindow(win_name,WINDOW_AUTOSIZE);
imshow(win_name,src);
//初始化mask
mask.create(src.size(),CV_8UC1); //创建mask 大小与原图一致 单通道8位
mask.setTo(Scalar::all(GC_BGD)); //先全部设置为背景
//鼠标事件回调函数
setMouseCallback(win_name,on_mouse_callback);
while(true)
{
//检测键值
char c = (char)waitKey(0);
if(c == 'n') //检测到n进行一次扣图 可以根据按键次数迭代扣图次数
{
run_grabcut(); //扣图算法
numRun ++;
show_img(); //显示图像
printf("current ite: %d\n",numRun); //显示当前迭代次数
}
if(c == 27) //检测到esc键 退出程序运行
{
break;
}
}
destroyAllWindows();
return 0;
}
void show_img(void)
{
Mat result,bin_mask;
bin_mask.create(mask.size(),CV_8UC1);
bin_mask = mask & 1;
if(init)
{
src.copyTo(result,bin_mask); //获取掩码覆盖区域
}else
{
src.copyTo(result);
}
rectangle(result,rect,Scalar(255,0,0),2);
imshow(win_name,result);
}
void set_roi(void)
{
/*
GC_FGD = 1 前景
GC_BGD = 0 背景
GC_PF_FGD = 3 可能前景
GC_PF_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));
}
void on_mouse_callback(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));
show_img();
}
break;
case EVENT_LBUTTONUP:
if(rect.width>1 && rect.height>1)
{
set_roi(); //将选定的区域设置为感兴趣区域
show_img();
init = true;
}
break;
default:
break;
}
}
void run_grabcut(void)
{
//检测范围太小的话 直接退出
if(rect.width<2 || rect.height <2)
{
return ;
}
if(init)
{
//获取mask
grabCut(src,mask,rect,bgModel,fgModel,1);
}else
{
//只初始化 不获取
grabCut(src,mask,rect,bgModel,fgModel,1,GC_INIT_WITH_RECT);
}
}
效果
原图
扣取对象