源图像:
右键操作:
处理后:
左键操作:
处理后:
源码:
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
static void help(char** argv)
{
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"
<< argv[0] << " <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 = EVENT_FLAG_CTRLKEY; //ctrl 键---背景//
const int FGD_KEY = EVENT_FLAG_SHIFTKEY; //shift 键---前景//
//获取二值掩码//
static void getBinMask(const Mat& comMask, Mat& binMask)
{
if (comMask.empty() || comMask.type() != CV_8UC1)
CV_Error(Error::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;//
}
//grab cut应用程序类//
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()
{
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;
}
//设置图像和窗口名
void GCApplication::setImageAndWinName(const Mat& _image, const string& _winName)
{
if (_image.empty() || _winName.empty())
return;
image = &_image;
winName = &_winName;
mask.create(image->size(), CV_8UC1);//创建掩码
reset(); //重置图像
}
//显示图像
void GCApplication::showImage() const
{
if (image->empty() || winName->empty())
return;
Mat res;
Mat binMask;//二值掩码//
if (!isInitialized)//未选定矩形
image->copyTo(res);//
else
{
getBinMask(mask, binMask);//
image->copyTo(res, binMask);//res: 感兴趣区图像
}
vector<Point>::const_iterator it;
for (it = bgdPxls.begin(); it != bgdPxls.end(); ++it)//背景像素点:蓝色小圆圈
circle(res, *it, radius, BLUE, thickness);//
for (it = fgdPxls.begin(); it != fgdPxls.end(); ++it)//前景像素点:红色小圆圈
circle(res, *it, radius, RED, thickness);//
for (it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it)//浅蓝色点:鼠标右键ctrl 背景点
circle(res, *it, radius, LIGHTBLUE, thickness);//
for (it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it)//粉色点
circle(res, *it, radius, PINK, thickness);//
if (rectState == IN_PROCESS || rectState == SET)
rectangle(res, Point(rect.x, rect.y), Point(rect.x + rect.width, rect.y + rect.height), GREEN, 2);//绘制绿色矩形框//
imshow(*winName, res);
}
//设置掩码里矩形框像素值
void GCApplication::setRectInMask()
{
CV_Assert(!mask.empty());
mask.setTo(GC_BGD);//黑色掩码
rect.x = max(0, rect.x); //确保矩形左上角点的x>0
rect.y = max(0, rect.y);//确保矩形左上角点的y>0
rect.width = min(rect.width, image->cols - rect.x);//确保矩形右下角在image上
rect.height = min(rect.height, image->rows - rect.y);
(mask(rect)).setTo(Scalar(GC_PR_FGD));//掩码矩形区域设置为像素值3
}
//在掩码上绘制标记点
void GCApplication::setLblsInMask(int flags, Point p, bool isPr)
{
vector<Point>* bpxls, * fpxls;//背景和前景像素点集 指针
uchar bvalue, fvalue;//背景和前景像素点的值
if (!isPr)// 鼠标左键模式
{
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)//ctrl按下
{
bpxls->push_back(p);//背景点
circle(mask, p, radius, bvalue, thickness);//绘制圆点
}
if (flags & FGD_KEY)//shift按下
{
fpxls->push_back(p);//前景点
circle(mask, p, radius, fvalue, thickness);//绘制圆点
}
}
//鼠标单击事件
void GCApplication::mouseClick(int event, int x, int y, int flags, void*)
{
// TODO add bad args check
switch (event)
{
case 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)
{
rectState = IN_PROCESS;//矩形框模式--左键按下
rect = Rect(x, y, 1, 1);//初始矩形宽1 高1
}
if ((isb || isf) && rectState == SET)//没按ctrl,没按shift, 矩形绘制完毕。
lblsState = IN_PROCESS;//切换到标签模式
}
break;
case EVENT_RBUTTONDOWN: //右键按下 set GC_PR_BGD(GC_PR_FGD) labels
{
bool isb = (flags & BGD_KEY) != 0,//没按ctrl
isf = (flags & FGD_KEY) != 0;//没按shift
if ((isb || isf) && rectState == SET)//矩形绘制完毕
prLblsState = IN_PROCESS;//标签模式
}
break;
case EVENT_LBUTTONUP://左键弹起
if (rectState == IN_PROCESS)//矩形绘制模式
{
rect = Rect(Point(rect.x, rect.y), Point(x, y));
rectState = SET;//矩形绘制完毕
setRectInMask();//设置矩形掩码区域像素值为3
CV_Assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());//断言:背景或者前景点集合为空
showImage();
}
if (lblsState == IN_PROCESS)//标记点绘制模式
{
setLblsInMask(flags, Point(x, y), false);//绘制标记点:前景或者背景 ,左键或者右键(颜色不同)
lblsState = SET;
showImage();//更新显示
}
break;
case EVENT_RBUTTONUP: //右键弹起
if (prLblsState == IN_PROCESS)//右键标签点绘制模式
{
setLblsInMask(flags, Point(x, y), true); //true:右键模式
prLblsState = SET;//右键标记点绘制完毕
showImage();
}
break;
case EVENT_MOUSEMOVE://鼠标移动
if (rectState == IN_PROCESS)//绘制矩形模式
{
rect = Rect(Point(rect.x, rect.y), Point(x, y));//更新矩形区域
CV_Assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());//断言:所有标记点集合为空
showImage();//显示图像
}
else if (lblsState == IN_PROCESS)//标记点模式,左键模式
{
setLblsInMask(flags, Point(x, y), false);//绘制标记点 false:绘制中
showImage();
}
else if (prLblsState == IN_PROCESS)//右键模式 标记点模式
{
setLblsInMask(flags, Point(x, y), true);//true: 右键模式 prLblsState标记点模式
showImage();
}
break;
}
}
/** @brief 运行 GrabCut 算法。
该函数实现了【GrabCut 图像分割算法】(http://en.wikipedia.org/wiki/GrabCut)。
@param img 输入 8 位 3 通道图像。
@param mask 输入/输出 8 位单通道掩码。当模式设置为#GC_INIT_WITH_RECT 时,函数会初始化掩码。它的元素可能具有#GrabCutClasses 之一。
@param rect ROI 包含一个分段对象。 ROI 之外的像素被标记为“明显背景”。该参数仅在 mode==#GC_INIT_WITH_RECT 时使用。
@param bgdModel 背景模型的临时数组。在处理同一图像时不要修改它。
@param fgdModel 前景模型的临时数组。在处理同一图像时不要修改它。
@param iterCount 算法在返回结果之前应该进行的迭代次数。请注意,可以通过 mode==#GC_INIT_WITH_MASK 或 mode==GC_EVAL 进一步调用来优化结果。
@param mode 可能是#GrabCutModes 之一的操作模式
*/
//图像分割
int GCApplication::nextIter()
{
if (isInitialized)
grabCut(*image, mask, rect, bgdModel, fgdModel, 1);//运行 GrabCut 算法
else//初始化
{
if (rectState != SET)//矩形未选中
return iterCount;//迭代次数
if (lblsState == SET || prLblsState == SET)
grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK);//初始化掩码
else
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; //grab cut应用程序
/** @brief 鼠标事件的回调函数。 见 cv::setMouseCallback
@param 事件 cv::MouseEventTypes 常量之一。
@param x 鼠标事件的 x 坐标。
@param y 鼠标事件的 y 坐标。
@param 标记 cv::MouseEventFlags 常量之一。
@param userdata 可选参数。
*/
/* cv::MouseEventFlags
* EVENT_FLAG_ALTKEY = 32 摁住Alt
EVENT_FLAG_CTRLKEY = 8 摁住Ctrl
EVENT_FLAG_LBUTTON = 1 摁住左键
EVENT_FLAG_MBUTTON = 4 摁住中键
EVENT_FLAG_RBUTTON = 2 摁住右键
EVENT_FLAG_SHIFTKEY = 16 摁住Shift
EVENT_LBUTTONDBLCLK = 7 左键双击
EVENT_LBUTTONDOWN = 1 左键击下
EVENT_LBUTTONUP = 4 左键弹起
EVENT_MBUTTONDBLCLK = 9 中键双击
EVENT_MBUTTONDOWN = 3 中键击下
EVENT_MBUTTONUP = 6 中键弹起
EVENT_MOUSEHWHEEL = 11 滚动条向左,flags>0。向右,flags<0
EVENT_MOUSEMOVE = 0 鼠标移动
EVENT_MOUSEWHEEL = 10 滚动条向上,flags>0。向下,flags<0
EVENT_RBUTTONDBLCLK = 8 中键双击
EVENT_RBUTTONDOWN = 2 中键击下
EVENT_RBUTTONUP = 5 中键弹起
*/
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)
{
cv::CommandLineParser parser(argc, argv, "{@input| messi5.jpg |}");//messi5.jpg
help(argv);
string filename = parser.get<string>("@input");
if (filename.empty())
{
cout << "\nDurn, empty filename" << endl;
return 1;
}
Mat image = imread(samples::findFile(filename), IMREAD_COLOR);//加载图片
// Mat image = imread("01.jpg", IMREAD_COLOR);
if (image.empty())
{
cout << "\n Durn, couldn't read image filename " << filename << endl;
return 1;
}
//resize(image, image, Size(image.size() / 4));
const string winName = "image";
namedWindow(winName, WINDOW_AUTOSIZE);
setMouseCallback(winName, on_mouse, 0);//设置窗口鼠标回调
gcapp.setImageAndWinName(image, winName);//
gcapp.showImage();
for (;;)
{
char c = (char)waitKey(0);
switch (c)
{
case '\x1b': // \x1B 表示 ASCII 中的第 1B(27号)字符 ESC(Escape)也就是转义字符
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;
}