samples/cpp/grabcut.cpp#
An example using the GrabCut algorithm
\
1#include "opencv2/imgcodecs.hpp"
2#include "opencv2/highgui.hpp"
3#include "opencv2/imgproc.hpp"
4
5#include <iostream>
6
7using namespace std;
8using namespace cv;
9
10static void help(char** argv)
11{
12 cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
13 "and then grabcut will attempt to segment it out.\n"
14 "Call:\n"
15 << argv[0] << " <image_name>\n"
16 "\nSelect a rectangular area around the object you want to segment\n" <<
17 "\nHot keys: \n"
18 "\tESC - quit the program\n"
19 "\tr - restore the original image\n"
20 "\tn - next iteration\n"
21 "\n"
22 "\tleft mouse button - set rectangle\n"
23 "\n"
24 "\tCTRL+left mouse button - set GC_BGD pixels\n"
25 "\tSHIFT+left mouse button - set GC_FGD pixels\n"
26 "\n"
27 "\tCTRL+right mouse button - set GC_PR_BGD pixels\n"
28 "\tSHIFT+right mouse button - set GC_PR_FGD pixels\n" << endl;
29}
30
31const Scalar RED = Scalar(0,0,255);
32const Scalar PINK = Scalar(230,130,255);
33const Scalar BLUE = Scalar(255,0,0);
34const Scalar LIGHTBLUE = Scalar(255,255,160);
35const Scalar GREEN = Scalar(0,255,0);
36
37const int BGD_KEY = EVENT_FLAG_CTRLKEY;
38const int FGD_KEY = EVENT_FLAG_SHIFTKEY;
39
40static void getBinMask( const Mat& comMask, Mat& binMask )
41{
42 if( comMask.empty() || comMask.type()!=CV_8UC1 )
43 CV_Error( Error::StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" );
44 if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols )
45 binMask.create( comMask.size(), CV_8UC1 );
46 binMask = comMask & 1;
47}
48
49class GCApplication
50{
51public:
52 enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 };
53 static const int radius = 2;
54 static const int thickness = -1;
55
56 void reset();
57 void setImageAndWinName( const Mat& _image, const string& _winName );
58 void showImage() const;
59 void mouseClick( int event, int x, int y, int flags, void* param );
60 int nextIter();
61 int getIterCount() const { return iterCount; }
62private:
63 void setRectInMask();
64 void setLblsInMask( int flags, Point p, bool isPr );
65
66 const string* winName;
67 const Mat* image;
68 Mat mask;
69 Mat bgdModel, fgdModel;
70
71 uchar rectState, lblsState, prLblsState;
72 bool isInitialized;
73
74 Rect rect;
75 vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;
76 int iterCount;
77};
78
79void GCApplication::reset()
80{
81 if( !mask.empty() )
82 mask.setTo(Scalar::all(GC_BGD));
83 bgdPxls.clear(); fgdPxls.clear();
84 prBgdPxls.clear(); prFgdPxls.clear();
85
86 isInitialized = false;
87 rectState = NOT_SET;
88 lblsState = NOT_SET;
89 prLblsState = NOT_SET;
90 iterCount = 0;
91}
92
93void GCApplication::setImageAndWinName( const Mat& _image, const string& _winName )
94{
95 if( _image.empty() || _winName.empty() )
96 return;
97 image = &_image;
98 winName = &_winName;
99 mask.create( image->size(), CV_8UC1);
100 reset();
101}
102
103void GCApplication::showImage() const
104{
105 if( image->empty() || winName->empty() )
106 return;
107
108 Mat res;
109 Mat binMask;
110 image->copyTo( res );
111 if( isInitialized ){
112 getBinMask( mask, binMask);
113
114 Mat black (binMask.rows, binMask.cols, CV_8UC3, cv::Scalar(0,0,0));
115 black.setTo(Scalar::all(255), binMask);
116
117 addWeighted(black, 0.5, res, 0.5, 0.0, res);
118 }
119
120 vector<Point>::const_iterator it;
121 for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it )
122 circle( res, *it, radius, BLUE, thickness );
123 for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it )
124 circle( res, *it, radius, RED, thickness );
125 for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it )
126 circle( res, *it, radius, LIGHTBLUE, thickness );
127 for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it )
128 circle( res, *it, radius, PINK, thickness );
129
130 if( rectState == IN_PROCESS || rectState == SET )
131 rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), GREEN, 2);
132
133 imshow( *winName, res );
134}
135
136void GCApplication::setRectInMask()
137{
138 CV_Assert( !mask.empty() );
139 mask.setTo( GC_BGD );
140 rect.x = max(0, rect.x);
141 rect.y = max(0, rect.y);
142 rect.width = min(rect.width, image->cols-rect.x);
143 rect.height = min(rect.height, image->rows-rect.y);
144 (mask(rect)).setTo( Scalar(GC_PR_FGD) );
145}
146
147void GCApplication::setLblsInMask( int flags, Point p, bool isPr )
148{
149 vector<Point> *bpxls, *fpxls;
150 uchar bvalue, fvalue;
151 if( !isPr )
152 {
153 bpxls = &bgdPxls;
154 fpxls = &fgdPxls;
155 bvalue = GC_BGD;
156 fvalue = GC_FGD;
157 }
158 else
159 {
160 bpxls = &prBgdPxls;
161 fpxls = &prFgdPxls;
162 bvalue = GC_PR_BGD;
163 fvalue = GC_PR_FGD;
164 }
165 if( flags & BGD_KEY )
166 {
167 bpxls->push_back(p);
168 circle( mask, p, radius, bvalue, thickness );
169 }
170 if( flags & FGD_KEY )
171 {
172 fpxls->push_back(p);
173 circle( mask, p, radius, fvalue, thickness );
174 }
175}
176
177void GCApplication::mouseClick( int event, int x, int y, int flags, void* )
178{
179 // TODO add bad args check
180 switch( event )
181 {
182 case EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels
183 {
184 bool isb = (flags & BGD_KEY) != 0,
185 isf = (flags & FGD_KEY) != 0;
186 if( rectState == NOT_SET && !isb && !isf )
187 {
188 rectState = IN_PROCESS;
189 rect = Rect( x, y, 1, 1 );
190 }
191 if ( (isb || isf) && rectState == SET )
192 lblsState = IN_PROCESS;
193 }
194 break;
195 case EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels
196 {
197 bool isb = (flags & BGD_KEY) != 0,
198 isf = (flags & FGD_KEY) != 0;
199 if ( (isb || isf) && rectState == SET )
200 prLblsState = IN_PROCESS;
201 }
202 break;
203 case EVENT_LBUTTONUP:
204 if( rectState == IN_PROCESS )
205 {
206 if(rect.x == x || rect.y == y){
207 rectState = NOT_SET;
208 }
209 else{
210 rect = Rect( Point(rect.x, rect.y), Point(x,y) );
211 rectState = SET;
212 setRectInMask();
213 CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
214 }
215 showImage();
216 }
217 if( lblsState == IN_PROCESS )
218 {
219 setLblsInMask(flags, Point(x,y), false);
220 lblsState = SET;
221 nextIter();
222 showImage();
223 }
224 else{
225 if(rectState == SET){
226 nextIter();
227 showImage();
228 }
229 }
230 break;
231 case EVENT_RBUTTONUP:
232 if( prLblsState == IN_PROCESS )
233 {
234 setLblsInMask(flags, Point(x,y), true);
235 prLblsState = SET;
236 }
237 if(rectState == SET){
238 nextIter();
239 showImage();
240 }
241 break;
242 case EVENT_MOUSEMOVE:
243 if( rectState == IN_PROCESS )
244 {
245 rect = Rect( Point(rect.x, rect.y), Point(x,y) );
246 CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
247 showImage();
248 }
249 else if( lblsState == IN_PROCESS )
250 {
251 setLblsInMask(flags, Point(x,y), false);
252 showImage();
253 }
254 else if( prLblsState == IN_PROCESS )
255 {
256 setLblsInMask(flags, Point(x,y), true);
257 showImage();
258 }
259 break;
260 }
261}
262
263int GCApplication::nextIter()
264{
265 if( isInitialized )
266 grabCut( *image, mask, rect, bgdModel, fgdModel, 1 );
267 else
268 {
269 if( rectState != SET )
270 return iterCount;
271
272 if( lblsState == SET || prLblsState == SET )
273 grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK );
274 else
275 grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT );
276
277 isInitialized = true;
278 }
279 iterCount++;
280
281 bgdPxls.clear(); fgdPxls.clear();
282 prBgdPxls.clear(); prFgdPxls.clear();
283
284 return iterCount;
285}
286
287GCApplication gcapp;
288
289static void on_mouse( int event, int x, int y, int flags, void* param )
290{
291 gcapp.mouseClick( event, x, y, flags, param );
292}
293
294int main( int argc, char** argv )
295{
296 cv::CommandLineParser parser(argc, argv, "{@input| messi5.jpg |}");
297 help(argv);
298
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 if( image.empty() )
307 {
308 cout << "\n Durn, couldn't read image filename " << filename << endl;
309 return 1;
310 }
311
312 const string winName = "image";
313 namedWindow( winName, WINDOW_AUTOSIZE );
314 setMouseCallback( winName, on_mouse, 0 );
315
316 gcapp.setImageAndWinName( image, winName );
317 gcapp.showImage();
318
319 for(;;)
320 {
321 char c = (char)waitKey(0);
322 switch( c )
323 {
324 case '\x1b':
325 cout << "Exiting ..." << endl;
326 goto exit_main;
327 case 'r':
328 cout << endl;
329 gcapp.reset();
330 gcapp.showImage();
331 break;
332 case 'n':
333 int iterCount = gcapp.getIterCount();
334 cout << "<" << iterCount << "... ";
335 int newIterCount = gcapp.nextIter();
336 if( newIterCount > iterCount )
337 {
338 gcapp.showImage();
339 cout << iterCount << ">" << endl;
340 }
341 else
342 cout << "rect must be determined>" << endl;
343 break;
344 }
345 }
346
347exit_main:
348 destroyWindow( winName );
349 return 0;
350}