opencv: grabcut: Ported to OpenCV version 3.1
[platform/upstream/gstreamer.git] / ext / opencv / gstgrabcut.cpp
1 /*
2  * GStreamer
3  * Copyright (C) 2013 Miguel Casas-Sanchez <miguelecasassanchez@gmail.com>
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Alternatively, the contents of this file may be used under the
24  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
25  * which case the following provisions apply instead of the ones
26  * mentioned above:
27  *
28  * This library is free software; you can redistribute it and/or
29  * modify it under the terms of the GNU Library General Public
30  * License as published by the Free Software Foundation; either
31  * version 2 of the License, or (at your option) any later version.
32  *
33  * This library is distributed in the hope that it will be useful,
34  * but WITHOUT ANY WARRANTY; without even the implied warranty of
35  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
36  * Library General Public License for more details.
37  *
38  * You should have received a copy of the GNU Library General Public
39  * License along with this library; if not, write to the
40  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
41  * Boston, MA 02110-1301, USA.
42  */
43
44 /**
45  * SECTION:element-grabcut
46  *
47  *
48  * This element is a wrapper around OpenCV grabcut implementation. GrabCut is an
49  * image segmentation method based on graph cuts technique. It can be seen as a
50  * way of fine-grain segmenting the image from some FG and BG "seed" areas. The
51  * OpenCV implementation follows the article [1].
52  * The "seed" areas are taken in this element from either an input bounding box
53  * coming from a face detection, or from alpha channel values. The input box is
54  * taken from a "face" event such as the one generated from the 'facedetect'
55  * element. The Alpha channel values should be one of the following (cv.hpp):
56  * enum{
57  *  GC_BGD    = 0,  //!< background
58  *  GC_FGD    = 1,  //!< foreground
59  *  GC_PR_BGD = 2,  //!< most probably background
60  *  GC_PR_FGD = 3   //!< most probably foreground
61  * };
62  * with values over GC_PR_FGD interpreted as GC_PR_FGD. IN CASE OF no alpha mask
63  * input (all 0's or all 1's), the 'GstOpenCvFaceDetect-face' downstream event
64  * is used to create a bbox of PR_FG elements. If both foreground alpha
65  * is not specified and there is no face detection, nothing is done.
66  *
67  * [1] C. Rother, V. Kolmogorov, and A. Blake, "GrabCut: Interactive foreground
68  * extraction using iterated graph cuts, ACM Trans. Graph., vol. 23, pp. 309–314,
69  * 2004.
70  *
71  * <refsect2>
72  * <title>Example launch line</title>
73  * |[
74  * gst-launch-1.0 --gst-debug=grabcut=4  v4l2src device=/dev/video0 ! videoconvert ! grabcut ! videoconvert ! video/x-raw,width=320,height=240 ! ximagesink
75  * ]|
76  * Another example launch line
77  * |[
78  * gst-launch-1.0 --gst-debug=grabcut=4  v4l2src device=/dev/video0 ! videoconvert ! facedetect display=0 ! videoconvert ! grabcut test-mode=true ! videoconvert ! video/x-raw,width=320,height=240 ! ximagesink
79  * ]|
80  * </refsect2>
81  */
82
83 #ifdef HAVE_CONFIG_H
84 #include <config.h>
85 #endif
86
87 #include "gstgrabcut.h"
88 extern "C"
89 {
90 #include <opencv2/imgproc/imgproc_c.h>
91 }
92 #include <opencv2/imgproc/imgproc.hpp>
93 GST_DEBUG_CATEGORY_STATIC (gst_grabcut_debug);
94 #define GST_CAT_DEFAULT gst_grabcut_debug
95
96 using namespace cv;
97 /* Filter signals and args */
98 enum
99 {
100   /* FILL ME */
101   LAST_SIGNAL
102 };
103
104 enum
105 {
106   PROP_0,
107   PROP_TEST_MODE,
108   PROP_SCALE
109 };
110
111 #define DEFAULT_TEST_MODE FALSE
112 #define DEFAULT_SCALE 1.6
113
114 G_DEFINE_TYPE (GstGrabcut, gst_grabcut, GST_TYPE_VIDEO_FILTER);
115 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
116     GST_PAD_SINK,
117     GST_PAD_ALWAYS,
118     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGBA")));
119
120 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
121     GST_PAD_SRC,
122     GST_PAD_ALWAYS,
123     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGBA")));
124
125
126 static void gst_grabcut_set_property (GObject * object, guint prop_id,
127     const GValue * value, GParamSpec * pspec);
128 static void gst_grabcut_get_property (GObject * object, guint prop_id,
129     GValue * value, GParamSpec * pspec);
130
131 static GstFlowReturn gst_grabcut_transform_ip (GstVideoFilter * btrans,
132     GstVideoFrame * frame);
133 static gboolean gst_grabcut_set_info (GstVideoFilter * filter,
134     GstCaps * incaps, GstVideoInfo * in_info,
135     GstCaps * outcaps, GstVideoInfo * out_info);
136
137 static void gst_grabcut_release_all_pointers (GstGrabcut * filter);
138
139 static gboolean gst_grabcut_stop (GstBaseTransform * basesrc);
140 static void compose_matrix_from_image (CvMat * output, IplImage * input);
141
142 static int initialise_grabcut (struct grabcut_params *GC, IplImage * image_c,
143     CvMat * mask_c);
144 static int run_grabcut_iteration (struct grabcut_params *GC,
145     IplImage * image_c, CvMat * mask_c, CvRect * bbox);
146 static int run_grabcut_iteration2 (struct grabcut_params *GC,
147     IplImage * image_c, CvMat * mask_c, CvRect * bbox);
148 static int finalise_grabcut (struct grabcut_params *GC);
149
150 /* initialize the grabcut's class */
151 static void
152 gst_grabcut_class_init (GstGrabcutClass * klass)
153 {
154   GObjectClass *gobject_class = (GObjectClass *) klass;
155   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
156   GstBaseTransformClass *btrans_class = (GstBaseTransformClass *) klass;
157   GstVideoFilterClass *video_class = (GstVideoFilterClass *) klass;
158
159   gobject_class->set_property = gst_grabcut_set_property;
160   gobject_class->get_property = gst_grabcut_get_property;
161
162   btrans_class->stop = gst_grabcut_stop;
163   btrans_class->passthrough_on_same_caps = TRUE;
164
165   video_class->transform_frame_ip = gst_grabcut_transform_ip;
166   video_class->set_info = gst_grabcut_set_info;
167
168   g_object_class_install_property (gobject_class, PROP_TEST_MODE,
169       g_param_spec_boolean ("test-mode", "test-mode",
170           "If true, the output RGB is overwritten with the segmented foreground. Alpha channel same as normal case ",
171           DEFAULT_TEST_MODE, (GParamFlags)
172           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
173
174   g_object_class_install_property (gobject_class, PROP_SCALE,
175       g_param_spec_float ("scale", "scale",
176           "Grow factor for the face bounding box, if present", 1.0,
177           4.0, DEFAULT_SCALE,
178           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
179
180   gst_element_class_set_static_metadata (element_class,
181       "Grabcut-based image FG/BG segmentation", "Filter/Effect/Video",
182       "Runs Grabcut algorithm on input alpha. Values: BG=0, FG=1, PR_BG=2, PR_FGD=3; \
183 NOTE: larger values of alpha (notably 255) are interpreted as PR_FGD too. \n\
184 IN CASE OF no alpha mask input (all 0's or all 1's), the 'face' \
185 downstream event is used to create a bbox of PR_FG elements.\n\
186 IF nothing is present, then nothing is done.", "Miguel Casas-Sanchez <miguelecasassanchez@gmail.com>");
187
188   gst_element_class_add_static_pad_template (element_class, &src_factory);
189   gst_element_class_add_static_pad_template (element_class, &sink_factory);
190 }
191
192
193 /* initialize the new element
194  * instantiate pads and add them to element
195  * set pad calback functions
196  * initialize instance structure
197  */
198 static void
199 gst_grabcut_init (GstGrabcut * filter)
200 {
201   filter->test_mode = DEFAULT_TEST_MODE;
202   filter->scale = DEFAULT_SCALE;
203   gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), FALSE);
204 }
205
206
207 static void
208 gst_grabcut_set_property (GObject * object, guint prop_id,
209     const GValue * value, GParamSpec * pspec)
210 {
211   GstGrabcut *grabcut = GST_GRABCUT (object);
212
213   switch (prop_id) {
214     case PROP_TEST_MODE:
215       grabcut->test_mode = g_value_get_boolean (value);
216       break;
217     case PROP_SCALE:
218       grabcut->scale = g_value_get_float (value);
219       break;
220     default:
221       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
222       break;
223   }
224 }
225
226 static void
227 gst_grabcut_get_property (GObject * object, guint prop_id,
228     GValue * value, GParamSpec * pspec)
229 {
230   GstGrabcut *filter = GST_GRABCUT (object);
231
232   switch (prop_id) {
233     case PROP_TEST_MODE:
234       g_value_set_boolean (value, filter->test_mode);
235       break;
236     case PROP_SCALE:
237       g_value_set_float (value, filter->scale);
238       break;
239     default:
240       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
241       break;
242   }
243 }
244
245 /* GstElement vmethod implementations */
246 /* this function handles the link with other elements */
247 static gboolean
248 gst_grabcut_set_info (GstVideoFilter * filter,
249     GstCaps * incaps, GstVideoInfo * in_info,
250     GstCaps * outcaps, GstVideoInfo * out_info)
251 {
252   GstGrabcut *grabcut = GST_GRABCUT (filter);
253   CvSize size;
254
255   size = cvSize (in_info->width, in_info->height);
256   /* If cvRGBA is already allocated, it means there's a cap modification,
257      so release first all the images.                                      */
258   if (NULL != grabcut->cvRGBAin)
259     gst_grabcut_release_all_pointers (grabcut);
260
261   grabcut->cvRGBAin = cvCreateImageHeader (size, IPL_DEPTH_8U, 4);
262   grabcut->cvRGBin = cvCreateImage (size, IPL_DEPTH_8U, 3);
263
264   grabcut->cvA = cvCreateImage (size, IPL_DEPTH_8U, 1);
265   grabcut->cvB = cvCreateImage (size, IPL_DEPTH_8U, 1);
266   grabcut->cvC = cvCreateImage (size, IPL_DEPTH_8U, 1);
267   grabcut->cvD = cvCreateImage (size, IPL_DEPTH_8U, 1);
268
269   grabcut->grabcut_mask = cvCreateMat (size.height, size.width, CV_8UC1);
270   cvZero (grabcut->grabcut_mask);
271   initialise_grabcut (&(grabcut->GC), grabcut->cvRGBin, grabcut->grabcut_mask);
272
273   return TRUE;
274 }
275
276 /* Clean up */
277 static gboolean
278 gst_grabcut_stop (GstBaseTransform * basesrc)
279 {
280   GstGrabcut *filter = GST_GRABCUT (basesrc);
281
282   if (filter->cvRGBAin != NULL)
283     gst_grabcut_release_all_pointers (filter);
284
285   return TRUE;
286 }
287
288 static void
289 gst_grabcut_release_all_pointers (GstGrabcut * filter)
290 {
291   cvReleaseImage (&filter->cvRGBAin);
292   cvReleaseImage (&filter->cvRGBin);
293
294   cvReleaseImage (&filter->cvA);
295   cvReleaseImage (&filter->cvB);
296   cvReleaseImage (&filter->cvC);
297   cvReleaseImage (&filter->cvD);
298
299   finalise_grabcut (&(filter->GC));
300 }
301
302 static GstFlowReturn
303 gst_grabcut_transform_ip (GstVideoFilter * btrans, GstVideoFrame * frame)
304 {
305   GstGrabcut *gc = GST_GRABCUT (btrans);
306   gint alphapixels;
307
308   GstVideoRegionOfInterestMeta *meta;
309   meta = gst_buffer_get_video_region_of_interest_meta (frame->buffer);
310   if (meta) {
311     gc->facepos.x = (meta->x) - ((gc->scale - 1) * meta->w / 2);
312     gc->facepos.y = (meta->y) - ((gc->scale - 1) * meta->h / 2);
313     gc->facepos.width = meta->w * gc->scale * 0.9;
314     gc->facepos.height = meta->h * gc->scale * 1.1;
315   } else {
316     memset (&(gc->facepos), 0, sizeof (gc->facepos));
317   }
318
319   gc->cvRGBAin->imageData = (char *) GST_VIDEO_FRAME_COMP_DATA (frame, 0);
320
321   /*  normally input should be RGBA */
322   cvSplit (gc->cvRGBAin, gc->cvA, gc->cvB, gc->cvC, gc->cvD);
323   cvCvtColor (gc->cvRGBAin, gc->cvRGBin, CV_BGRA2BGR);
324   compose_matrix_from_image (gc->grabcut_mask, gc->cvD);
325
326   /*  Pass cvD to grabcut_mask for the graphcut stuff but that only if
327      really there is something in the mask! otherwise -->input bbox is
328      what we use */
329   alphapixels = cvCountNonZero (gc->cvD);
330   if ((0 < alphapixels) && (alphapixels < (gc->width * gc->height))) {
331     GST_INFO ("running on mask");
332     run_grabcut_iteration (&(gc->GC), gc->cvRGBin, gc->grabcut_mask, NULL);
333   } else {
334
335     if ((abs (gc->facepos.width) > 2) && (abs (gc->facepos.height) > 2)) {
336       GST_INFO ("running on bbox (%d,%d),(%d,%d)", gc->facepos.x, gc->facepos.y,
337           gc->facepos.width, gc->facepos.height);
338       run_grabcut_iteration2 (&(gc->GC), gc->cvRGBin, gc->grabcut_mask,
339           &(gc->facepos));
340     } else {
341       GST_WARNING ("No face info present, skipping frame.");
342       return GST_FLOW_OK;
343     }
344   }
345
346   /*  if we want to display, just overwrite the output */
347   if (gc->test_mode) {
348     /*  get only FG, PR_FG */
349     cvAndS (gc->grabcut_mask, cvRealScalar (1), gc->grabcut_mask, NULL);
350     /*  (saturated) FG, PR_FG --> 255 */
351     cvConvertScale (gc->grabcut_mask, gc->grabcut_mask, 255.0, 0.0);
352
353     cvAnd (gc->grabcut_mask, gc->cvA, gc->cvA, NULL);
354     cvAnd (gc->grabcut_mask, gc->cvB, gc->cvB, NULL);
355     cvAnd (gc->grabcut_mask, gc->cvC, gc->cvC, NULL);
356   }
357
358   cvMerge (gc->cvA, gc->cvB, gc->cvC, gc->cvD, gc->cvRGBAin);
359
360   if (gc->test_mode) {
361     cvRectangle (gc->cvRGBAin,
362         cvPoint (gc->facepos.x, gc->facepos.y),
363         cvPoint (gc->facepos.x + gc->facepos.width,
364             gc->facepos.y + gc->facepos.height), CV_RGB (255, 0, 255), 1, 8, 0);
365   }
366
367   return GST_FLOW_OK;
368 }
369
370 /* entry point to initialize the plug-in
371  * initialize the plug-in itself
372  * register the element factories and other features
373  */
374 gboolean
375 gst_grabcut_plugin_init (GstPlugin * plugin)
376 {
377   /* debug category for fltering log messages
378    *
379    */
380   GST_DEBUG_CATEGORY_INIT (gst_grabcut_debug, "grabcut",
381       0,
382       "Grabcut image segmentation on either input alpha or input bounding box");
383
384   return gst_element_register (plugin, "grabcut", GST_RANK_NONE,
385       GST_TYPE_GRABCUT);
386 }
387
388 void
389 compose_matrix_from_image (CvMat * output, IplImage * input)
390 {
391
392   int x, y;
393   for (x = 0; x < output->cols; x++) {
394     for (y = 0; y < output->rows; y++) {
395       CV_MAT_ELEM (*output, uchar, y, x) =
396           (cvGetReal2D (input, y, x) <= GC_PR_FGD) ? cvGetReal2D (input, y,
397           x) : GC_PR_FGD;
398     }
399   }
400 }
401
402
403 int
404 initialise_grabcut (struct grabcut_params *GC, IplImage * image_c,
405     CvMat * mask_c)
406 {
407   GC->image = (void *) new Mat (cvarrToMat (image_c, false));    /*  "true" refers to copydata */
408   GC->mask = (void *) new Mat (cvarrToMat (mask_c, false));
409   GC->bgdModel = (void *) new Mat ();       /*  "true" refers to copydata */
410   GC->fgdModel = (void *) new Mat ();
411
412   return (0);
413 }
414
415 int
416 run_grabcut_iteration (struct grabcut_params *GC, IplImage * image_c,
417     CvMat * mask_c, CvRect * bbox)
418 {
419   ((Mat *) GC->image)->data = (uchar *) image_c->imageData;
420   ((Mat *) GC->mask)->data = mask_c->data.ptr;
421
422   if (cvCountNonZero (mask_c))
423     grabCut (*((Mat *) GC->image), *((Mat *) GC->mask), Rect (),
424         *((Mat *) GC->bgdModel), *((Mat *) GC->fgdModel), 1,
425         GC_INIT_WITH_MASK);
426
427   return (0);
428 }
429
430 int
431 run_grabcut_iteration2 (struct grabcut_params *GC, IplImage * image_c,
432     CvMat * mask_c, CvRect * bbox)
433 {
434   ((Mat *) GC->image)->data = (uchar *) image_c->imageData;
435   ((Mat *) GC->mask)->data = mask_c->data.ptr;
436   grabCut (*((Mat *) GC->image), *((Mat *) GC->mask), *(bbox),
437       *((Mat *) GC->bgdModel), *((Mat *) GC->fgdModel), 1,
438       GC_INIT_WITH_RECT);
439
440   return (0);
441 }
442
443 int
444 finalise_grabcut (struct grabcut_params *GC)
445 {
446   delete ((Mat *) GC->image);
447   delete ((Mat *) GC->mask);
448   delete ((Mat *) GC->bgdModel);
449   delete ((Mat *) GC->fgdModel);
450
451   return (0);
452 }