• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer faceoverlay plugin
2  * Copyright (C) 2011 Laura Lucas Alday <lauralucas@gmail.com>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  *
22  * Alternatively, the contents of this file may be used under the
23  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
24  * which case the following provisions apply instead of the ones
25  * mentioned above:
26  *
27  * This library is free software; you can redistribute it and/or
28  * modify it under the terms of the GNU Library General Public
29  * License as published by the Free Software Foundation; either
30  * version 2 of the License, or (at your option) any later version.
31  *
32  * This library is distributed in the hope that it will be useful,
33  * but WITHOUT ANY WARRANTY; without even the implied warranty of
34  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
35  * Library General Public License for more details.
36  *
37  * You should have received a copy of the GNU Library General Public
38  * License along with this library; if not, write to the
39  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
40  * Boston, MA 02110-1301, USA.
41  */
42 
43 /**
44  * SECTION:element-faceoverlay
45  *
46  * Overlays a SVG image over a detected face in a video stream.
47  * x, y, w, and h properties are optional, and change the image position and
48  * size relative to the detected face position and size.
49  *
50  * ## Example launch line
51  *
52  * |[
53  * gst-launch-1.0 autovideosrc ! videoconvert ! faceoverlay location=/path/to/gnome-video-effects/pixmaps/bow.svg x=0.5 y=0.5 w=0.7 h=0.7 ! videoconvert ! autovideosink
54  * ]|
55  */
56 
57 #ifdef HAVE_CONFIG_H
58 #  include <config.h>
59 #endif
60 
61 #include <gst/gst.h>
62 #include <gst/video/video.h>
63 #include <string.h>
64 
65 #include "gstfaceoverlay.h"
66 
67 GST_DEBUG_CATEGORY_STATIC (gst_face_overlay_debug);
68 #define GST_CAT_DEFAULT gst_face_overlay_debug
69 
70 enum
71 {
72   PROP_0,
73   PROP_LOCATION,
74   PROP_X,
75   PROP_Y,
76   PROP_W,
77   PROP_H
78 };
79 
80 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
81     GST_PAD_SINK,
82     GST_PAD_ALWAYS,
83     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{RGB}")));
84 
85 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
86     GST_PAD_SRC,
87     GST_PAD_ALWAYS,
88     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{BGRA}")));
89 
90 #define gst_face_overlay_parent_class parent_class
91 G_DEFINE_TYPE (GstFaceOverlay, gst_face_overlay, GST_TYPE_BIN);
92 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (faceoverlay, "faceoverlay",
93     GST_RANK_NONE, GST_TYPE_FACEOVERLAY,
94     GST_DEBUG_CATEGORY_INIT (gst_face_overlay_debug, "faceoverlay", 0,
95         "SVG Face Overlay");
96     );
97 
98 static void gst_face_overlay_set_property (GObject * object, guint prop_id,
99     const GValue * value, GParamSpec * pspec);
100 static void gst_face_overlay_get_property (GObject * object, guint prop_id,
101     GValue * value, GParamSpec * pspec);
102 static void gst_face_overlay_message_handler (GstBin * bin,
103     GstMessage * message);
104 static GstStateChangeReturn gst_face_overlay_change_state (GstElement * element,
105     GstStateChange transition);
106 static gboolean gst_face_overlay_create_children (GstFaceOverlay * filter);
107 
108 static gboolean
gst_face_overlay_create_children(GstFaceOverlay * filter)109 gst_face_overlay_create_children (GstFaceOverlay * filter)
110 {
111   GstElement *csp, *face_detect, *overlay;
112   GstPad *pad;
113 
114   csp = gst_element_factory_make ("videoconvert", NULL);
115   face_detect = gst_element_factory_make ("facedetect", NULL);
116   overlay = gst_element_factory_make ("rsvgoverlay", NULL);
117 
118   /* FIXME: post missing-plugin messages on NULL->READY if needed */
119   if (csp == NULL || face_detect == NULL || overlay == NULL)
120     goto missing_element;
121 
122   g_object_set (face_detect, "display", FALSE, NULL);
123 
124   gst_bin_add_many (GST_BIN (filter), face_detect, csp, overlay, NULL);
125   filter->svg_overlay = overlay;
126 
127   if (!gst_element_link_many (face_detect, csp, overlay, NULL))
128     GST_ERROR_OBJECT (filter, "couldn't link elements");
129 
130   pad = gst_element_get_static_pad (face_detect, "sink");
131   if (!gst_ghost_pad_set_target (GST_GHOST_PAD (filter->sinkpad), pad))
132     GST_ERROR_OBJECT (filter->sinkpad, "couldn't set sinkpad target");
133   gst_object_unref (pad);
134 
135   pad = gst_element_get_static_pad (overlay, "src");
136   if (!gst_ghost_pad_set_target (GST_GHOST_PAD (filter->srcpad), pad))
137     GST_ERROR_OBJECT (filter->srcpad, "couldn't set srcpad target");
138   gst_object_unref (pad);
139 
140   return TRUE;
141 
142 /* ERRORS */
143 missing_element:
144   {
145     /* clean up */
146     if (csp == NULL)
147       GST_ERROR_OBJECT (filter, "videoconvert element not found");
148     else
149       gst_object_unref (csp);
150 
151     if (face_detect == NULL)
152       GST_ERROR_OBJECT (filter, "facedetect element not found (opencv plugin)");
153     else
154       gst_object_unref (face_detect);
155 
156     if (overlay == NULL)
157       GST_ERROR_OBJECT (filter, "rsvgoverlay element not found (rsvg plugin)");
158     else
159       gst_object_unref (overlay);
160 
161     return FALSE;
162   }
163 }
164 
165 static GstStateChangeReturn
gst_face_overlay_change_state(GstElement * element,GstStateChange transition)166 gst_face_overlay_change_state (GstElement * element, GstStateChange transition)
167 {
168   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
169   GstFaceOverlay *filter = GST_FACEOVERLAY (element);
170 
171   switch (transition) {
172     case GST_STATE_CHANGE_NULL_TO_READY:
173       if (filter->svg_overlay == NULL) {
174         GST_ELEMENT_ERROR (filter, CORE, MISSING_PLUGIN, (NULL),
175             ("Some required plugins are missing, probably either the opencv "
176                 "facedetect element or rsvgoverlay"));
177         return GST_STATE_CHANGE_FAILURE;
178       }
179       filter->update_svg = TRUE;
180       break;
181     default:
182       break;
183   }
184 
185   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
186 
187   switch (transition) {
188     default:
189       break;
190   }
191 
192   return ret;
193 }
194 
195 static void
gst_face_overlay_handle_faces(GstFaceOverlay * filter,const GstStructure * s)196 gst_face_overlay_handle_faces (GstFaceOverlay * filter, const GstStructure * s)
197 {
198   guint x, y, width, height;
199   gint svg_x, svg_y, svg_width, svg_height;
200   const GstStructure *face;
201   const GValue *faces_list, *face_val;
202   gchar *new_location = NULL;
203   gint face_count;
204 
205 #if 0
206   /* optionally draw the image once every two messages for better performance */
207   filter->process_message = !filter->process_message;
208   if (!filter->process_message)
209     return;
210 #endif
211 
212   faces_list = gst_structure_get_value (s, "faces");
213   face_count = gst_value_list_get_size (faces_list);
214   GST_LOG_OBJECT (filter, "face count: %d", face_count);
215 
216   if (face_count == 0) {
217     GST_DEBUG_OBJECT (filter, "no face, clearing overlay");
218     g_object_set (filter->svg_overlay, "location", NULL, NULL);
219     GST_OBJECT_LOCK (filter);
220     filter->update_svg = TRUE;
221     GST_OBJECT_UNLOCK (filter);
222     return;
223   }
224 
225   /* The last face in the list seems to be the right one, objects mistakenly
226    * detected as faces for a couple of frames seem to be in the list
227    * beginning. TODO: needs confirmation. */
228   face_val = gst_value_list_get_value (faces_list, face_count - 1);
229   face = gst_value_get_structure (face_val);
230   gst_structure_get_uint (face, "x", &x);
231   gst_structure_get_uint (face, "y", &y);
232   gst_structure_get_uint (face, "width", &width);
233   gst_structure_get_uint (face, "height", &height);
234 
235   /* Apply x and y offsets relative to face position and size.
236    * Set image width and height as a fraction of face width and height.
237    * Cast to int since face position and size will never be bigger than
238    * G_MAX_INT and we may have negative values as svg_x or svg_y */
239 
240   GST_OBJECT_LOCK (filter);
241 
242   svg_x = (gint) x + (gint) (filter->x * width);
243   svg_y = (gint) y + (gint) (filter->y * height);
244 
245   svg_width = (gint) (filter->w * width);
246   svg_height = (gint) (filter->h * height);
247 
248   if (filter->update_svg) {
249     new_location = g_strdup (filter->location);
250     filter->update_svg = FALSE;
251   }
252   GST_OBJECT_UNLOCK (filter);
253 
254   if (new_location != NULL) {
255     GST_DEBUG_OBJECT (filter, "set rsvgoverlay location=%s", new_location);
256     g_object_set (filter->svg_overlay, "location", new_location, NULL);
257     g_free (new_location);
258   }
259 
260   GST_LOG_OBJECT (filter, "overlay dimensions: %d x %d @ %d,%d",
261       svg_width, svg_height, svg_x, svg_y);
262 
263   g_object_set (filter->svg_overlay,
264       "x", svg_x, "y", svg_y, "width", svg_width, "height", svg_height, NULL);
265 }
266 
267 static void
gst_face_overlay_message_handler(GstBin * bin,GstMessage * message)268 gst_face_overlay_message_handler (GstBin * bin, GstMessage * message)
269 {
270   if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) {
271     const GstStructure *s = gst_message_get_structure (message);
272 
273     if (gst_structure_has_name (s, "facedetect")) {
274       gst_face_overlay_handle_faces (GST_FACEOVERLAY (bin), s);
275     }
276   }
277 
278   GST_BIN_CLASS (parent_class)->handle_message (bin, message);
279 }
280 
281 static void
gst_face_overlay_class_init(GstFaceOverlayClass * klass)282 gst_face_overlay_class_init (GstFaceOverlayClass * klass)
283 {
284   GObjectClass *gobject_class;
285   GstBinClass *gstbin_class;
286   GstElementClass *gstelement_class;
287 
288   gobject_class = G_OBJECT_CLASS (klass);
289   gstbin_class = GST_BIN_CLASS (klass);
290   gstelement_class = GST_ELEMENT_CLASS (klass);
291 
292   gobject_class->set_property = gst_face_overlay_set_property;
293   gobject_class->get_property = gst_face_overlay_get_property;
294 
295   g_object_class_install_property (gobject_class, PROP_LOCATION,
296       g_param_spec_string ("location", "Location",
297           "Location of SVG file to use for face overlay",
298           "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
299   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X,
300       g_param_spec_float ("x", "face x offset",
301           "Specify image x relative to detected face x.", -G_MAXFLOAT,
302           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
303   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y,
304       g_param_spec_float ("y", "face y offset",
305           "Specify image y relative to detected face y.", -G_MAXFLOAT,
306           G_MAXFLOAT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
307   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_W,
308       g_param_spec_float ("w", "face width percent",
309           "Specify image width relative to face width.", 0, G_MAXFLOAT, 1,
310           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
311   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_H,
312       g_param_spec_float ("h", "face height percent",
313           "Specify image height relative to face height.", 0, G_MAXFLOAT, 1,
314           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
315 
316   gst_element_class_set_static_metadata (gstelement_class,
317       "faceoverlay",
318       "Filter/Editor/Video",
319       "Overlays SVG graphics over a detected face in a video stream",
320       "Laura Lucas Alday <lauralucas@gmail.com>");
321 
322   gst_element_class_add_pad_template (gstelement_class,
323       gst_static_pad_template_get (&src_factory));
324   gst_element_class_add_pad_template (gstelement_class,
325       gst_static_pad_template_get (&sink_factory));
326 
327   gstbin_class->handle_message =
328       GST_DEBUG_FUNCPTR (gst_face_overlay_message_handler);
329   gstelement_class->change_state =
330       GST_DEBUG_FUNCPTR (gst_face_overlay_change_state);
331 }
332 
333 static void
gst_face_overlay_init(GstFaceOverlay * filter)334 gst_face_overlay_init (GstFaceOverlay * filter)
335 {
336   GstPadTemplate *tmpl;
337 
338   filter->x = 0;
339   filter->y = 0;
340   filter->w = 1;
341   filter->h = 1;
342   filter->svg_overlay = NULL;
343   filter->location = NULL;
344   filter->process_message = TRUE;
345 
346   tmpl = gst_static_pad_template_get (&sink_factory);
347   filter->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", tmpl);
348   gst_object_unref (tmpl);
349   gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
350 
351   tmpl = gst_static_pad_template_get (&src_factory);
352   filter->srcpad = gst_ghost_pad_new_no_target_from_template ("src", tmpl);
353   gst_object_unref (tmpl);
354   gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
355 
356   gst_face_overlay_create_children (filter);
357 }
358 
359 static void
gst_face_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)360 gst_face_overlay_set_property (GObject * object, guint prop_id,
361     const GValue * value, GParamSpec * pspec)
362 {
363   GstFaceOverlay *filter = GST_FACEOVERLAY (object);
364 
365   switch (prop_id) {
366     case PROP_LOCATION:
367       GST_OBJECT_LOCK (filter);
368       g_free (filter->location);
369       filter->location = g_value_dup_string (value);
370       filter->update_svg = TRUE;
371       GST_OBJECT_UNLOCK (filter);
372       break;
373     case PROP_X:
374       GST_OBJECT_LOCK (filter);
375       filter->x = g_value_get_float (value);
376       GST_OBJECT_UNLOCK (filter);
377       break;
378     case PROP_Y:
379       GST_OBJECT_LOCK (filter);
380       filter->y = g_value_get_float (value);
381       GST_OBJECT_UNLOCK (filter);
382       break;
383     case PROP_W:
384       GST_OBJECT_LOCK (filter);
385       filter->w = g_value_get_float (value);
386       GST_OBJECT_UNLOCK (filter);
387       break;
388     case PROP_H:
389       GST_OBJECT_LOCK (filter);
390       filter->h = g_value_get_float (value);
391       GST_OBJECT_UNLOCK (filter);
392       break;
393     default:
394       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
395       break;
396   }
397 }
398 
399 static void
gst_face_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)400 gst_face_overlay_get_property (GObject * object, guint prop_id,
401     GValue * value, GParamSpec * pspec)
402 {
403   GstFaceOverlay *filter = GST_FACEOVERLAY (object);
404 
405   switch (prop_id) {
406     case PROP_LOCATION:
407       GST_OBJECT_LOCK (filter);
408       g_value_set_string (value, filter->location);
409       GST_OBJECT_UNLOCK (filter);
410       break;
411     case PROP_X:
412       GST_OBJECT_LOCK (filter);
413       g_value_set_float (value, filter->x);
414       GST_OBJECT_UNLOCK (filter);
415       break;
416     case PROP_Y:
417       GST_OBJECT_LOCK (filter);
418       g_value_set_float (value, filter->y);
419       GST_OBJECT_UNLOCK (filter);
420       break;
421     case PROP_W:
422       GST_OBJECT_LOCK (filter);
423       g_value_set_float (value, filter->w);
424       GST_OBJECT_UNLOCK (filter);
425       break;
426     case PROP_H:
427       GST_OBJECT_LOCK (filter);
428       g_value_set_float (value, filter->h);
429       GST_OBJECT_UNLOCK (filter);
430       break;
431     default:
432       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
433       break;
434   }
435 }
436 
437 static gboolean
faceoverlay_init(GstPlugin * plugin)438 faceoverlay_init (GstPlugin * plugin)
439 {
440   return GST_ELEMENT_REGISTER (faceoverlay, plugin);
441 }
442 
443 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
444     GST_VERSION_MINOR,
445     faceoverlay,
446     "SVG Face Overlay",
447     faceoverlay_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
448