源图像:
右键操作:
处理后:
左键操作:
处理后:
源码:
- #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;
- }