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 #include <opencv2/imgproc.hpp>
89 
90 GST_DEBUG_CATEGORY_STATIC (gst_grabcut_debug);
91 #define GST_CAT_DEFAULT gst_grabcut_debug
92 
93 using namespace cv;
94 /* Filter signals and args */
95 enum
96 {
97   /* FILL ME */
98   LAST_SIGNAL
99 };
100 
101 enum
102 {
103   PROP_0,
104   PROP_TEST_MODE,
105   PROP_SCALE
106 };
107 
108 #define DEFAULT_TEST_MODE FALSE
109 #define DEFAULT_SCALE 1.6
110 
111 G_DEFINE_TYPE (GstGrabcut, gst_grabcut, GST_TYPE_OPENCV_VIDEO_FILTER);
112 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
113     GST_PAD_SINK,
114     GST_PAD_ALWAYS,
115     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGBA")));
116 
117 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
118     GST_PAD_SRC,
119     GST_PAD_ALWAYS,
120     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGBA")));
121 
122 
123 static void gst_grabcut_set_property (GObject * object, guint prop_id,
124     const GValue * value, GParamSpec * pspec);
125 static void gst_grabcut_get_property (GObject * object, guint prop_id,
126     GValue * value, GParamSpec * pspec);
127 
128 static GstFlowReturn gst_grabcut_transform_ip (GstOpencvVideoFilter * filter,
129     GstBuffer * buf, Mat img);
130 static gboolean gst_grabcut_set_caps (GstOpencvVideoFilter * filter,
131     gint in_width, gint in_height, int in_cv_type,
132     gint out_width, gint out_height, int out_cv_type);
133 
134 //static void gst_grabcut_release_all_pointers (GstGrabcut * filter);
135 
136 static void compose_matrix_from_image (Mat output, Mat input);
137 
138 static int run_grabcut_iteration (Mat image_c, Mat mask_c, Mat bgdModel,
139     Mat fgdModel);
140 static int run_grabcut_iteration2 (Mat image_c, Mat mask_c, Mat bgdModel,
141     Mat fgdModel, Rect bbox);
142 
143 /* Clean up */
144 static void
gst_grabcut_finalize(GObject * obj)145 gst_grabcut_finalize (GObject * obj)
146 {
147   GstGrabcut *filter = GST_GRABCUT (obj);
148 
149   filter->cvRGBin.release ();
150   filter->cvA.release ();
151   filter->cvB.release ();
152   filter->cvC.release ();
153   filter->cvD.release ();
154   filter->grabcut_mask.release ();
155   filter->bgdModel.release ();
156   filter->fgdModel.release ();
157 
158   G_OBJECT_CLASS (gst_grabcut_parent_class)->finalize (obj);
159 }
160 
161 /* initialize the grabcut's class */
162 static void
gst_grabcut_class_init(GstGrabcutClass * klass)163 gst_grabcut_class_init (GstGrabcutClass * klass)
164 {
165   GObjectClass *gobject_class = (GObjectClass *) klass;
166   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
167   GstOpencvVideoFilterClass *cvbasefilter_class =
168       (GstOpencvVideoFilterClass *) klass;
169   GstBaseTransformClass *btrans_class = (GstBaseTransformClass *) klass;
170 
171   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_grabcut_finalize);
172   gobject_class->set_property = gst_grabcut_set_property;
173   gobject_class->get_property = gst_grabcut_get_property;
174 
175   btrans_class->passthrough_on_same_caps = TRUE;
176 
177   cvbasefilter_class->cv_trans_ip_func = gst_grabcut_transform_ip;
178   cvbasefilter_class->cv_set_caps = gst_grabcut_set_caps;
179 
180   g_object_class_install_property (gobject_class, PROP_TEST_MODE,
181       g_param_spec_boolean ("test-mode", "test-mode",
182           "If true, the output RGB is overwritten with the segmented foreground. Alpha channel same as normal case ",
183           DEFAULT_TEST_MODE, (GParamFlags)
184           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
185 
186   g_object_class_install_property (gobject_class, PROP_SCALE,
187       g_param_spec_float ("scale", "scale",
188           "Grow factor for the face bounding box, if present", 1.0,
189           4.0, DEFAULT_SCALE,
190           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
191 
192   gst_element_class_set_static_metadata (element_class,
193       "Grabcut-based image FG/BG segmentation", "Filter/Effect/Video",
194       "Runs Grabcut algorithm on input alpha. Values: BG=0, FG=1, PR_BG=2, PR_FGD=3; \
195 NOTE: larger values of alpha (notably 255) are interpreted as PR_FGD too. \n\
196 IN CASE OF no alpha mask input (all 0's or all 1's), the 'face' \
197 downstream event is used to create a bbox of PR_FG elements.\n\
198 IF nothing is present, then nothing is done.", "Miguel Casas-Sanchez <miguelecasassanchez@gmail.com>");
199 
200   gst_element_class_add_static_pad_template (element_class, &src_factory);
201   gst_element_class_add_static_pad_template (element_class, &sink_factory);
202 }
203 
204 
205 /* initialize the new element
206  * instantiate pads and add them to element
207  * set pad calback functions
208  * initialize instance structure
209  */
210 static void
gst_grabcut_init(GstGrabcut * filter)211 gst_grabcut_init (GstGrabcut * filter)
212 {
213   filter->test_mode = DEFAULT_TEST_MODE;
214   filter->scale = DEFAULT_SCALE;
215   gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER (filter), TRUE);
216 }
217 
218 
219 static void
gst_grabcut_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)220 gst_grabcut_set_property (GObject * object, guint prop_id,
221     const GValue * value, GParamSpec * pspec)
222 {
223   GstGrabcut *grabcut = GST_GRABCUT (object);
224 
225   switch (prop_id) {
226     case PROP_TEST_MODE:
227       grabcut->test_mode = g_value_get_boolean (value);
228       break;
229     case PROP_SCALE:
230       grabcut->scale = g_value_get_float (value);
231       break;
232     default:
233       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
234       break;
235   }
236 }
237 
238 static void
gst_grabcut_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)239 gst_grabcut_get_property (GObject * object, guint prop_id,
240     GValue * value, GParamSpec * pspec)
241 {
242   GstGrabcut *filter = GST_GRABCUT (object);
243 
244   switch (prop_id) {
245     case PROP_TEST_MODE:
246       g_value_set_boolean (value, filter->test_mode);
247       break;
248     case PROP_SCALE:
249       g_value_set_float (value, filter->scale);
250       break;
251     default:
252       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
253       break;
254   }
255 }
256 
257 /* GstElement vmethod implementations */
258 /* this function handles the link with other elements */
259 static gboolean
gst_grabcut_set_caps(GstOpencvVideoFilter * filter,gint in_width,gint in_height,int in_cv_type,gint out_width,gint out_height,int out_cv_type)260 gst_grabcut_set_caps (GstOpencvVideoFilter * filter, gint in_width,
261     gint in_height, int in_cv_type, gint out_width,
262     gint out_height, int out_cv_type)
263 {
264   GstGrabcut *grabcut = GST_GRABCUT (filter);
265   Size size;
266 
267   size = Size (in_width, in_height);
268 
269 
270   grabcut->cvRGBin.create (size, CV_8UC3);
271 
272   grabcut->cvA.create (size, CV_8UC1);
273   grabcut->cvB.create (size, CV_8UC1);
274   grabcut->cvC.create (size, CV_8UC1);
275   grabcut->cvD.create (size, CV_8UC1);
276 
277   grabcut->grabcut_mask = Mat::zeros (size, CV_8UC1);
278   grabcut->bgdModel = Mat ();
279   grabcut->fgdModel = Mat ();
280   //initialise_grabcut (&(grabcut->GC), grabcut->cvRGBin, grabcut->grabcut_mask);
281 
282   return TRUE;
283 }
284 
285 static GstFlowReturn
gst_grabcut_transform_ip(GstOpencvVideoFilter * filter,GstBuffer * buffer,Mat img)286 gst_grabcut_transform_ip (GstOpencvVideoFilter * filter, GstBuffer * buffer,
287     Mat img)
288 {
289   GstGrabcut *gc = GST_GRABCUT (filter);
290   gint alphapixels;
291   std::vector < Mat > channels (4);
292 
293   GstVideoRegionOfInterestMeta *meta;
294   meta = gst_buffer_get_video_region_of_interest_meta (buffer);
295   if (meta) {
296     gc->facepos.x = (meta->x) - ((gc->scale - 1) * meta->w / 2);
297     gc->facepos.y = (meta->y) - ((gc->scale - 1) * meta->h / 2);
298     gc->facepos.width = meta->w * gc->scale * 0.9;
299     gc->facepos.height = meta->h * gc->scale * 1.1;
300   } else {
301     memset (static_cast < void *>(&(gc->facepos)), 0, sizeof (gc->facepos));
302   }
303 
304   /*  normally input should be RGBA */
305   split (img, channels);
306   gc->cvA = channels.at (0);
307   gc->cvB = channels.at (1);
308   gc->cvC = channels.at (2);
309   gc->cvD = channels.at (3);
310   cvtColor (img, gc->cvRGBin, COLOR_BGRA2BGR);
311   compose_matrix_from_image (gc->grabcut_mask, gc->cvD);
312 
313   /*  Pass cvD to grabcut_mask for the graphcut stuff but that only if
314      really there is something in the mask! otherwise -->input bbox is
315      what we use */
316   alphapixels = countNonZero (gc->cvD);
317   if ((0 < alphapixels) && (alphapixels < (gc->width * gc->height))) {
318     GST_INFO ("running on mask");
319     run_grabcut_iteration (gc->cvRGBin, gc->grabcut_mask, gc->bgdModel,
320         gc->fgdModel);
321   } else {
322     if ((abs (gc->facepos.width) > 2) && (abs (gc->facepos.height) > 2)) {
323       GST_INFO ("running on bbox (%d,%d),(%d,%d)", gc->facepos.x, gc->facepos.y,
324           gc->facepos.width, gc->facepos.height);
325       run_grabcut_iteration2 (gc->cvRGBin, gc->grabcut_mask, gc->bgdModel,
326           gc->fgdModel, gc->facepos);
327     } else {
328       GST_WARNING ("No face info present, skipping frame.");
329       return GST_FLOW_OK;
330     }
331   }
332 
333   /*  if we want to display, just overwrite the output */
334   if (gc->test_mode) {
335     /*  get only FG, PR_FG */
336     bitwise_and (gc->grabcut_mask, Scalar (1), gc->grabcut_mask);
337     /*  (saturated) FG, PR_FG --> 255 */
338     gc->grabcut_mask.convertTo (gc->grabcut_mask, -1, 255.0, 0.0);
339 
340     bitwise_and (gc->grabcut_mask, gc->cvA, gc->cvA);
341     bitwise_and (gc->grabcut_mask, gc->cvB, gc->cvB);
342     bitwise_and (gc->grabcut_mask, gc->cvC, gc->cvC);
343   }
344 
345   merge (channels, img);
346 
347   if (gc->test_mode) {
348     rectangle (img,
349         Point (gc->facepos.x, gc->facepos.y),
350         Point (gc->facepos.x + gc->facepos.width,
351             gc->facepos.y + gc->facepos.height), CV_RGB (255, 0, 255), 1, 8, 0);
352   }
353 
354   return GST_FLOW_OK;
355 }
356 
357 /* entry point to initialize the plug-in
358  * initialize the plug-in itself
359  * register the element factories and other features
360  */
361 gboolean
gst_grabcut_plugin_init(GstPlugin * plugin)362 gst_grabcut_plugin_init (GstPlugin * plugin)
363 {
364   /* debug category for fltering log messages
365    *
366    */
367   GST_DEBUG_CATEGORY_INIT (gst_grabcut_debug, "grabcut",
368       0,
369       "Grabcut image segmentation on either input alpha or input bounding box");
370 
371   return gst_element_register (plugin, "grabcut", GST_RANK_NONE,
372       GST_TYPE_GRABCUT);
373 }
374 
375 void
compose_matrix_from_image(Mat output,Mat input)376 compose_matrix_from_image (Mat output, Mat input)
377 {
378 
379   int x, y;
380   for (x = 0; x < output.cols; x++) {
381     for (y = 0; y < output.rows; y++) {
382       output.data[output.step[0] * y + x] =
383           (input.data[input.step[0] * y + x] <=
384           GC_PR_FGD) ? input.data[input.step[0] * y + x] : GC_PR_FGD;
385     }
386   }
387 }
388 
389 int
run_grabcut_iteration(Mat image_c,Mat mask_c,Mat bgdModel,Mat fgdModel)390 run_grabcut_iteration (Mat image_c, Mat mask_c, Mat bgdModel, Mat fgdModel)
391 {
392   if (countNonZero (mask_c))
393     grabCut (image_c, mask_c, Rect (),
394         bgdModel, bgdModel, 1, GC_INIT_WITH_MASK);
395 
396   return (0);
397 }
398 
399 int
run_grabcut_iteration2(Mat image_c,Mat mask_c,Mat bgdModel,Mat fgdModel,Rect bbox)400 run_grabcut_iteration2 (Mat image_c, Mat mask_c, Mat bgdModel, Mat fgdModel,
401     Rect bbox)
402 {
403   grabCut (image_c, mask_c, bbox, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT);
404 
405   return (0);
406 }
407