• 【opencv450-samples】图像分割grabcut算法


    源图像:

     

    右键操作:

    处理后:

     

    左键操作:

    处理后:

     

     

    源码:

    1. #include "opencv2/imgcodecs.hpp"
    2. #include "opencv2/highgui.hpp"
    3. #include "opencv2/imgproc.hpp"
    4. #include <iostream>
    5. using namespace std;
    6. using namespace cv;
    7. static void help(char** argv)
    8. {
    9. cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
    10. "and then grabcut will attempt to segment it out.\n"
    11. "Call:\n"
    12. << argv[0] << " <image_name>\n"
    13. "\nSelect a rectangular area around the object you want to segment\n" <<
    14. "\nHot keys: \n"
    15. "\tESC - quit the program\n"
    16. "\tr - restore the original image\n"
    17. "\tn - next iteration\n"
    18. "\n"
    19. "\tleft mouse button - set rectangle\n"
    20. "\n"
    21. "\tCTRL+left mouse button - set GC_BGD pixels\n"
    22. "\tSHIFT+left mouse button - set GC_FGD pixels\n"
    23. "\n"
    24. "\tCTRL+right mouse button - set GC_PR_BGD pixels\n"
    25. "\tSHIFT+right mouse button - set GC_PR_FGD pixels\n" << endl;
    26. }
    27. //颜色//
    28. const Scalar RED = Scalar(0, 0, 255);
    29. const Scalar PINK = Scalar(230, 130, 255);
    30. const Scalar BLUE = Scalar(255, 0, 0);
    31. const Scalar LIGHTBLUE = Scalar(255, 255, 160);
    32. const Scalar GREEN = Scalar(0, 255, 0);
    33. //按键//
    34. const int BGD_KEY = EVENT_FLAG_CTRLKEY; //ctrl 键---背景//
    35. const int FGD_KEY = EVENT_FLAG_SHIFTKEY; //shift 键---前景//
    36. //获取二值掩码//
    37. static void getBinMask(const Mat& comMask, Mat& binMask)
    38. {
    39. if (comMask.empty() || comMask.type() != CV_8UC1)
    40. CV_Error(Error::StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)");
    41. if (binMask.empty() || binMask.rows != comMask.rows || binMask.cols != comMask.cols)
    42. binMask.create(comMask.size(), CV_8UC1);
    43. binMask = comMask & 1;//
    44. }
    45. //grab cut应用程序类//
    46. class GCApplication
    47. {
    48. public:
    49. enum { NOT_SET = 0, IN_PROCESS = 1, SET = 2 };
    50. static const int radius = 2;
    51. static const int thickness = -1;//实心//
    52. void reset();
    53. void setImageAndWinName(const Mat& _image, const string& _winName);
    54. void showImage() const;
    55. void mouseClick(int event, int x, int y, int flags, void* param);
    56. int nextIter();
    57. int getIterCount() const { return iterCount; }
    58. private:
    59. void setRectInMask();
    60. void setLblsInMask(int flags, Point p, bool isPr);
    61. const string* winName;
    62. const Mat* image;
    63. Mat mask;
    64. Mat bgdModel, fgdModel;
    65. uchar rectState, lblsState, prLblsState;
    66. bool isInitialized;
    67. Rect rect;
    68. vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;
    69. int iterCount;
    70. };
    71. //重置所有变量状态//
    72. void GCApplication::reset()
    73. {
    74. if (!mask.empty())
    75. mask.setTo(Scalar::all(GC_BGD));//黑色掩码
    76. bgdPxls.clear(); fgdPxls.clear();
    77. prBgdPxls.clear(); prFgdPxls.clear();
    78. isInitialized = false;
    79. rectState = NOT_SET;//矩形框状态未设置/
    80. lblsState = NOT_SET;//标签状态-左键按下时//
    81. prLblsState = NOT_SET;//右键按下-标签状态//
    82. iterCount = 0;
    83. }
    84. //设置图像和窗口名
    85. void GCApplication::setImageAndWinName(const Mat& _image, const string& _winName)
    86. {
    87. if (_image.empty() || _winName.empty())
    88. return;
    89. image = &_image;
    90. winName = &_winName;
    91. mask.create(image->size(), CV_8UC1);//创建掩码
    92. reset(); //重置图像
    93. }
    94. //显示图像
    95. void GCApplication::showImage() const
    96. {
    97. if (image->empty() || winName->empty())
    98. return;
    99. Mat res;
    100. Mat binMask;//二值掩码//
    101. if (!isInitialized)//未选定矩形
    102. image->copyTo(res);//
    103. else
    104. {
    105. getBinMask(mask, binMask);//
    106. image->copyTo(res, binMask);//res: 感兴趣区图像
    107. }
    108. vector<Point>::const_iterator it;
    109. for (it = bgdPxls.begin(); it != bgdPxls.end(); ++it)//背景像素点:蓝色小圆圈
    110. circle(res, *it, radius, BLUE, thickness);//
    111. for (it = fgdPxls.begin(); it != fgdPxls.end(); ++it)//前景像素点:红色小圆圈
    112. circle(res, *it, radius, RED, thickness);//
    113. for (it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it)//浅蓝色点:鼠标右键ctrl 背景点
    114. circle(res, *it, radius, LIGHTBLUE, thickness);//
    115. for (it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it)//粉色点
    116. circle(res, *it, radius, PINK, thickness);//
    117. if (rectState == IN_PROCESS || rectState == SET)
    118. rectangle(res, Point(rect.x, rect.y), Point(rect.x + rect.width, rect.y + rect.height), GREEN, 2);//绘制绿色矩形框//
    119. imshow(*winName, res);
    120. }
    121. //设置掩码里矩形框像素值
    122. void GCApplication::setRectInMask()
    123. {
    124. CV_Assert(!mask.empty());
    125. mask.setTo(GC_BGD);//黑色掩码
    126. rect.x = max(0, rect.x); //确保矩形左上角点的x>0
    127. rect.y = max(0, rect.y);//确保矩形左上角点的y>0
    128. rect.width = min(rect.width, image->cols - rect.x);//确保矩形右下角在image上
    129. rect.height = min(rect.height, image->rows - rect.y);
    130. (mask(rect)).setTo(Scalar(GC_PR_FGD));//掩码矩形区域设置为像素值3
    131. }
    132. //在掩码上绘制标记点
    133. void GCApplication::setLblsInMask(int flags, Point p, bool isPr)
    134. {
    135. vector<Point>* bpxls, * fpxls;//背景和前景像素点集 指针
    136. uchar bvalue, fvalue;//背景和前景像素点的值
    137. if (!isPr)// 鼠标左键模式
    138. {
    139. bpxls = &bgdPxls;
    140. fpxls = &fgdPxls;
    141. bvalue = GC_BGD;
    142. fvalue = GC_FGD;
    143. }
    144. else//鼠标右键模式
    145. {
    146. bpxls = &prBgdPxls;//获取点集合指针
    147. fpxls = &prFgdPxls;
    148. bvalue = GC_PR_BGD;//背景点目标像素值
    149. fvalue = GC_PR_FGD;//前景点集目标像素值
    150. }
    151. if (flags & BGD_KEY)//ctrl按下
    152. {
    153. bpxls->push_back(p);//背景点
    154. circle(mask, p, radius, bvalue, thickness);//绘制圆点
    155. }
    156. if (flags & FGD_KEY)//shift按下
    157. {
    158. fpxls->push_back(p);//前景点
    159. circle(mask, p, radius, fvalue, thickness);//绘制圆点
    160. }
    161. }
    162. //鼠标单击事件
    163. void GCApplication::mouseClick(int event, int x, int y, int flags, void*)
    164. {
    165. // TODO add bad args check
    166. switch (event)
    167. {
    168. case EVENT_LBUTTONDOWN: //左键按下 set rect or GC_BGD(GC_FGD) labels
    169. {
    170. bool isb = (flags & BGD_KEY) != 0,//
    171. isf = (flags & FGD_KEY) != 0;//
    172. if (rectState == NOT_SET && !isb && !isf)
    173. {
    174. rectState = IN_PROCESS;//矩形框模式--左键按下
    175. rect = Rect(x, y, 1, 1);//初始矩形宽1 高1
    176. }
    177. if ((isb || isf) && rectState == SET)//没按ctrl,没按shift, 矩形绘制完毕。
    178. lblsState = IN_PROCESS;//切换到标签模式
    179. }
    180. break;
    181. case EVENT_RBUTTONDOWN: //右键按下 set GC_PR_BGD(GC_PR_FGD) labels
    182. {
    183. bool isb = (flags & BGD_KEY) != 0,//没按ctrl
    184. isf = (flags & FGD_KEY) != 0;//没按shift
    185. if ((isb || isf) && rectState == SET)//矩形绘制完毕
    186. prLblsState = IN_PROCESS;//标签模式
    187. }
    188. break;
    189. case EVENT_LBUTTONUP://左键弹起
    190. if (rectState == IN_PROCESS)//矩形绘制模式
    191. {
    192. rect = Rect(Point(rect.x, rect.y), Point(x, y));
    193. rectState = SET;//矩形绘制完毕
    194. setRectInMask();//设置矩形掩码区域像素值为3
    195. CV_Assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());//断言:背景或者前景点集合为空
    196. showImage();
    197. }
    198. if (lblsState == IN_PROCESS)//标记点绘制模式
    199. {
    200. setLblsInMask(flags, Point(x, y), false);//绘制标记点:前景或者背景 ,左键或者右键(颜色不同)
    201. lblsState = SET;
    202. showImage();//更新显示
    203. }
    204. break;
    205. case EVENT_RBUTTONUP: //右键弹起
    206. if (prLblsState == IN_PROCESS)//右键标签点绘制模式
    207. {
    208. setLblsInMask(flags, Point(x, y), true); //true:右键模式
    209. prLblsState = SET;//右键标记点绘制完毕
    210. showImage();
    211. }
    212. break;
    213. case EVENT_MOUSEMOVE://鼠标移动
    214. if (rectState == IN_PROCESS)//绘制矩形模式
    215. {
    216. rect = Rect(Point(rect.x, rect.y), Point(x, y));//更新矩形区域
    217. CV_Assert(bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty());//断言:所有标记点集合为空
    218. showImage();//显示图像
    219. }
    220. else if (lblsState == IN_PROCESS)//标记点模式,左键模式
    221. {
    222. setLblsInMask(flags, Point(x, y), false);//绘制标记点 false:绘制中
    223. showImage();
    224. }
    225. else if (prLblsState == IN_PROCESS)//右键模式 标记点模式
    226. {
    227. setLblsInMask(flags, Point(x, y), true);//true: 右键模式 prLblsState标记点模式
    228. showImage();
    229. }
    230. break;
    231. }
    232. }
    233. /** @brief 运行 GrabCut 算法。
    234. 该函数实现了【GrabCut 图像分割算法】(http://en.wikipedia.org/wiki/GrabCut)。
    235. @param img 输入 8 位 3 通道图像。
    236. @param mask 输入/输出 8 位单通道掩码。当模式设置为#GC_INIT_WITH_RECT 时,函数会初始化掩码。它的元素可能具有#GrabCutClasses 之一。
    237. @param rect ROI 包含一个分段对象。 ROI 之外的像素被标记为“明显背景”。该参数仅在 mode==#GC_INIT_WITH_RECT 时使用。
    238. @param bgdModel 背景模型的临时数组。在处理同一图像时不要修改它。
    239. @param fgdModel 前景模型的临时数组。在处理同一图像时不要修改它。
    240. @param iterCount 算法在返回结果之前应该进行的迭代次数。请注意,可以通过 mode==#GC_INIT_WITH_MASK 或 mode==GC_EVAL 进一步调用来优化结果。
    241. @param mode 可能是#GrabCutModes 之一的操作模式
    242. */
    243. //图像分割
    244. int GCApplication::nextIter()
    245. {
    246. if (isInitialized)
    247. grabCut(*image, mask, rect, bgdModel, fgdModel, 1);//运行 GrabCut 算法
    248. else//初始化
    249. {
    250. if (rectState != SET)//矩形未选中
    251. return iterCount;//迭代次数
    252. if (lblsState == SET || prLblsState == SET)
    253. grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK);//初始化掩码
    254. else
    255. grabCut(*image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT);//初始化矩形
    256. isInitialized = true;//初始化完成
    257. }
    258. iterCount++;//迭代次数
    259. bgdPxls.clear(); fgdPxls.clear();
    260. prBgdPxls.clear(); prFgdPxls.clear();
    261. return iterCount;
    262. }
    263. GCApplication gcapp; //grab cut应用程序
    264. /** @brief 鼠标事件的回调函数。 见 cv::setMouseCallback
    265. @param 事件 cv::MouseEventTypes 常量之一。
    266. @param x 鼠标事件的 x 坐标。
    267. @param y 鼠标事件的 y 坐标。
    268. @param 标记 cv::MouseEventFlags 常量之一。
    269. @param userdata 可选参数。
    270. */
    271. /* cv::MouseEventFlags
    272. * EVENT_FLAG_ALTKEY = 32 摁住Alt
    273. EVENT_FLAG_CTRLKEY = 8 摁住Ctrl
    274. EVENT_FLAG_LBUTTON = 1 摁住左键
    275. EVENT_FLAG_MBUTTON = 4 摁住中键
    276. EVENT_FLAG_RBUTTON = 2 摁住右键
    277. EVENT_FLAG_SHIFTKEY = 16 摁住Shift
    278. EVENT_LBUTTONDBLCLK = 7 左键双击
    279. EVENT_LBUTTONDOWN = 1 左键击下
    280. EVENT_LBUTTONUP = 4 左键弹起
    281. EVENT_MBUTTONDBLCLK = 9 中键双击
    282. EVENT_MBUTTONDOWN = 3 中键击下
    283. EVENT_MBUTTONUP = 6 中键弹起
    284. EVENT_MOUSEHWHEEL = 11 滚动条向左,flags>0。向右,flags<0
    285. EVENT_MOUSEMOVE = 0 鼠标移动
    286. EVENT_MOUSEWHEEL = 10 滚动条向上,flags>0。向下,flags<0
    287. EVENT_RBUTTONDBLCLK = 8 中键双击
    288. EVENT_RBUTTONDOWN = 2 中键击下
    289. EVENT_RBUTTONUP = 5 中键弹起
    290. */
    291. static void on_mouse(int event, int x, int y, int flags, void* param)
    292. {
    293. gcapp.mouseClick(event, x, y, flags, param);
    294. }
    295. int main(int argc, char** argv)
    296. {
    297. cv::CommandLineParser parser(argc, argv, "{@input| messi5.jpg |}");//messi5.jpg
    298. help(argv);
    299. string filename = parser.get<string>("@input");
    300. if (filename.empty())
    301. {
    302. cout << "\nDurn, empty filename" << endl;
    303. return 1;
    304. }
    305. Mat image = imread(samples::findFile(filename), IMREAD_COLOR);//加载图片
    306. // Mat image = imread("01.jpg", IMREAD_COLOR);
    307. if (image.empty())
    308. {
    309. cout << "\n Durn, couldn't read image filename " << filename << endl;
    310. return 1;
    311. }
    312. //resize(image, image, Size(image.size() / 4));
    313. const string winName = "image";
    314. namedWindow(winName, WINDOW_AUTOSIZE);
    315. setMouseCallback(winName, on_mouse, 0);//设置窗口鼠标回调
    316. gcapp.setImageAndWinName(image, winName);//
    317. gcapp.showImage();
    318. for (;;)
    319. {
    320. char c = (char)waitKey(0);
    321. switch (c)
    322. {
    323. case '\x1b': // \x1B 表示 ASCII 中的第 1B(27号)字符 ESC(Escape)也就是转义字符
    324. cout << "Exiting ..." << endl;
    325. goto exit_main; //退出主程序
    326. case 'r':
    327. cout << endl;
    328. gcapp.reset();//重置图像
    329. gcapp.showImage();
    330. break;
    331. case 'n':
    332. int iterCount = gcapp.getIterCount();
    333. cout << "<" << iterCount << "... ";
    334. int newIterCount = gcapp.nextIter();
    335. if (newIterCount > iterCount)//确定好矩形
    336. {
    337. gcapp.showImage();
    338. cout << iterCount << ">" << endl;
    339. }
    340. else //矩形必须先确定
    341. cout << "rect must be determined>" << endl;
    342. break;
    343. }
    344. }
    345. exit_main:
    346. destroyWindow(winName);
    347. return 0;
    348. }

  • 相关阅读:
    学习 C++ 到底有什么好处?
    stm32-----定时中断基本结构
    编辑距离问题
    基于安卓android微信小程序的在线考试系统
    Kettle(二):连接SQL Server数据库
    单片机中使用操作系统RTOS的好处
    软件设计不是CRUD(2):降低模块间耦合性——需求场景
    JavaScript云LIS系统概述 前端框架JQuery+EasyUI+Bootstrap医院云HIS系统源码 开箱即用
    Python学习基础笔记七十五——Python调用其他程序
    【答读者问】把Go基础学完后,是学web方向还是区块链方向?
  • 原文地址:https://blog.csdn.net/cxyhjl/article/details/125440032