1 /*
2  * GStreamer
3  * Copyright (C) 2005 Thomas Vander Stichele <thomas@apestaart.org>
4  * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
5  * Copyright (C) 2008 Michael Sheldon <mike@mikeasoft.com>
6  * Copyright (C) 2011 Stefan Sauer <ensonic@users.sf.net>
7  * Copyright (C) 2014 Robert Jobbagy <jobbagy.robert@gmail.com>
8  * Copyright (C) 2018 Nicola Murino <nicola.murino@gmail.com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  *
28  * Alternatively, the contents of this file may be used under the
29  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
30  * which case the following provisions apply instead of the ones
31  * mentioned above:
32  *
33  * This library is free software; you can redistribute it and/or
34  * modify it under the terms of the GNU Library General Public
35  * License as published by the Free Software Foundation; either
36  * version 2 of the License, or (at your option) any later version.
37  *
38  * This library is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
41  * Library General Public License for more details.
42  *
43  * You should have received a copy of the GNU Library General Public
44  * License along with this library; if not, write to the
45  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
46  * Boston, MA 02110-1301, USA.
47  */
48 
49 /**
50  * SECTION:element-facedetect
51  *
52  * Performs face detection on videos and images.
53  * If you have high cpu load you need to use videoscale with capsfilter and reduce the video resolution.
54  *
55  * The image is scaled down multiple times using the GstFaceDetect::scale-factor
56  * until the size is &lt;= GstFaceDetect::min-size-width or
57  * GstFaceDetect::min-size-height.
58  *
59  * <refsect2>
60  * <title>Example launch line</title>
61  * |[
62  * gst-launch-1.0 autovideosrc ! decodebin ! colorspace ! facedetect ! videoconvert ! xvimagesink
63  * ]| Detect and show faces
64  * |[
65  * gst-launch-1.0 autovideosrc ! video/x-raw,width=320,height=240 ! videoconvert ! facedetect min-size-width=60 min-size-height=60 ! colorspace ! xvimagesink
66  * ]| Detect large faces on a smaller image
67  *
68  * </refsect2>
69  */
70 
71 /* FIXME: development version of OpenCV has CV_HAAR_FIND_BIGGEST_OBJECT which
72  * we might want to use if available
73  * see https://code.ros.org/svn/opencv/trunk/opencv/modules/objdetect/src/haar.cpp
74  */
75 
76 #ifdef HAVE_CONFIG_H
77 #  include <config.h>
78 #endif
79 
80 #include <vector>
81 
82 using namespace std;
83 
84 #include "gstfacedetect.h"
85 #include <opencv2/imgproc.hpp>
86 
87 GST_DEBUG_CATEGORY_STATIC (gst_face_detect_debug);
88 #define GST_CAT_DEFAULT gst_face_detect_debug
89 
90 #define HAAR_CASCADES_DIR OPENCV_PREFIX G_DIR_SEPARATOR_S "share" \
91     G_DIR_SEPARATOR_S OPENCV_PATH_NAME G_DIR_SEPARATOR_S "haarcascades" \
92     G_DIR_SEPARATOR_S
93 #define DEFAULT_FACE_PROFILE HAAR_CASCADES_DIR "haarcascade_frontalface_default.xml"
94 #define DEFAULT_NOSE_PROFILE HAAR_CASCADES_DIR "haarcascade_mcs_nose.xml"
95 #define DEFAULT_MOUTH_PROFILE HAAR_CASCADES_DIR "haarcascade_mcs_mouth.xml"
96 #define DEFAULT_EYES_PROFILE HAAR_CASCADES_DIR "haarcascade_mcs_eyepair_small.xml"
97 #define DEFAULT_SCALE_FACTOR 1.25
98 #if (CV_MAJOR_VERSION >= 4)
99 #define DEFAULT_FLAGS CASCADE_DO_CANNY_PRUNING
100 #else
101 #define DEFAULT_FLAGS CV_HAAR_DO_CANNY_PRUNING
102 #endif
103 #define DEFAULT_MIN_NEIGHBORS 3
104 #define DEFAULT_MIN_SIZE_WIDTH 30
105 #define DEFAULT_MIN_SIZE_HEIGHT 30
106 #define DEFAULT_MIN_STDDEV 0
107 
108 using namespace cv;
109 /* Filter signals and args */
110 enum
111 {
112   /* FILL ME */
113   LAST_SIGNAL
114 };
115 
116 enum
117 {
118   PROP_0,
119   PROP_DISPLAY,
120   PROP_FACE_PROFILE,
121   PROP_NOSE_PROFILE,
122   PROP_MOUTH_PROFILE,
123   PROP_EYES_PROFILE,
124   PROP_SCALE_FACTOR,
125   PROP_MIN_NEIGHBORS,
126   PROP_FLAGS,
127   PROP_MIN_SIZE_WIDTH,
128   PROP_MIN_SIZE_HEIGHT,
129   PROP_UPDATES,
130   PROP_MIN_STDDEV
131 };
132 
133 
134 /*
135  * GstOpencvFaceDetectFlags:
136  *
137  * Flags parameter to OpenCV's cvHaarDetectObjects function.
138  */
139 typedef enum
140 {
141   GST_OPENCV_FACE_DETECT_HAAR_DO_CANNY_PRUNING = (1 << 0)
142 } GstOpencvFaceDetectFlags;
143 
144 #define GST_TYPE_OPENCV_FACE_DETECT_FLAGS (gst_opencv_face_detect_flags_get_type())
145 
146 inline void
structure_and_message(const vector<Rect> & rectangles,const gchar * name,guint rx,guint ry,GstFaceDetect * filter,GstStructure * s)147 structure_and_message (const vector < Rect > &rectangles, const gchar * name,
148     guint rx, guint ry, GstFaceDetect * filter, GstStructure * s)
149 {
150   Rect sr = rectangles[0];
151   gchar *nx = g_strconcat (name, "->x", NULL);
152   gchar *ny = g_strconcat (name, "->y", NULL);
153   gchar *nw = g_strconcat (name, "->width", NULL);
154   gchar *nh = g_strconcat (name, "->height", NULL);
155 
156   GST_LOG_OBJECT (filter,
157       "%s/%" G_GSIZE_FORMAT ": x,y = %4u,%4u: w.h = %4u,%4u",
158       name, rectangles.size (), rx + sr.x, ry + sr.y, sr.width, sr.height);
159   gst_structure_set (s, nx, G_TYPE_UINT, rx + sr.x, ny, G_TYPE_UINT, ry + sr.y,
160       nw, G_TYPE_UINT, sr.width, nh, G_TYPE_UINT, sr.height, NULL);
161 
162   g_free (nx);
163   g_free (ny);
164   g_free (nw);
165   g_free (nh);
166 }
167 
168 static void
register_gst_opencv_face_detect_flags(GType * id)169 register_gst_opencv_face_detect_flags (GType * id)
170 {
171   static const GFlagsValue values[] = {
172     {(guint) GST_OPENCV_FACE_DETECT_HAAR_DO_CANNY_PRUNING,
173         "Do Canny edge detection to discard some regions", "do-canny-pruning"},
174     {0, NULL, NULL}
175   };
176   *id = g_flags_register_static ("GstOpencvFaceDetectFlags", values);
177 }
178 
179 static GType
gst_opencv_face_detect_flags_get_type(void)180 gst_opencv_face_detect_flags_get_type (void)
181 {
182   static GType id;
183   static GOnce once = G_ONCE_INIT;
184 
185   g_once (&once, (GThreadFunc) register_gst_opencv_face_detect_flags, &id);
186   return id;
187 }
188 
189 #define GST_TYPE_FACE_DETECT_UPDATES (facedetect_update_get_type ())
190 
191 static GType
facedetect_update_get_type(void)192 facedetect_update_get_type (void)
193 {
194   static GType facedetect_update_type = 0;
195   static const GEnumValue facedetect_update[] = {
196     {GST_FACEDETECT_UPDATES_EVERY_FRAME, "Send update messages on every frame",
197         "every_frame"},
198     {GST_FACEDETECT_UPDATES_ON_CHANGE,
199           "Send messages when a new face is detected or one is not anymore detected",
200         "on_change"},
201     {GST_FACEDETECT_UPDATES_ON_FACE,
202           "Send messages whenever a face is detected",
203         "on_face"},
204     {GST_FACEDETECT_UPDATES_NONE, "Send no messages update", "none"},
205     {0, NULL, NULL},
206   };
207 
208   if (!facedetect_update_type) {
209     facedetect_update_type =
210         g_enum_register_static ("GstFaceDetectUpdates", facedetect_update);
211   }
212   return facedetect_update_type;
213 }
214 
215 /* the capabilities of the inputs and outputs.
216  */
217 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
218     GST_PAD_SINK,
219     GST_PAD_ALWAYS,
220     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB"))
221     );
222 
223 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
224     GST_PAD_SRC,
225     GST_PAD_ALWAYS,
226     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB"))
227     );
228 
229 G_DEFINE_TYPE (GstFaceDetect, gst_face_detect, GST_TYPE_OPENCV_VIDEO_FILTER);
230 
231 static void gst_face_detect_set_property (GObject * object, guint prop_id,
232     const GValue * value, GParamSpec * pspec);
233 static void gst_face_detect_get_property (GObject * object, guint prop_id,
234     GValue * value, GParamSpec * pspec);
235 
236 static gboolean gst_face_detect_set_caps (GstOpencvVideoFilter * transform,
237     gint in_width, gint in_height, int in_cv_type,
238     gint out_width, gint out_height, int out_cv_type);
239 static GstFlowReturn gst_face_detect_transform_ip (GstOpencvVideoFilter * base,
240     GstBuffer * buf, Mat img);
241 
242 static CascadeClassifier *gst_face_detect_load_profile (GstFaceDetect *
243     filter, gchar * profile);
244 
245 /* Clean up */
246 static void
gst_face_detect_finalize(GObject * obj)247 gst_face_detect_finalize (GObject * obj)
248 {
249   GstFaceDetect *filter = GST_FACE_DETECT (obj);
250 
251   filter->cvGray.release ();
252 
253   g_free (filter->face_profile);
254   g_free (filter->nose_profile);
255   g_free (filter->mouth_profile);
256   g_free (filter->eyes_profile);
257 
258   if (filter->cvFaceDetect)
259     delete (filter->cvFaceDetect);
260   if (filter->cvNoseDetect)
261     delete (filter->cvNoseDetect);
262   if (filter->cvMouthDetect)
263     delete (filter->cvMouthDetect);
264   if (filter->cvEyesDetect)
265     delete (filter->cvEyesDetect);
266 
267   G_OBJECT_CLASS (gst_face_detect_parent_class)->finalize (obj);
268 }
269 
270 /* initialize the facedetect's class */
271 static void
gst_face_detect_class_init(GstFaceDetectClass * klass)272 gst_face_detect_class_init (GstFaceDetectClass * klass)
273 {
274   GObjectClass *gobject_class;
275   GstOpencvVideoFilterClass *gstopencvbasefilter_class;
276 
277   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
278   gobject_class = (GObjectClass *) klass;
279   gstopencvbasefilter_class = (GstOpencvVideoFilterClass *) klass;
280 
281   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_face_detect_finalize);
282   gobject_class->set_property = gst_face_detect_set_property;
283   gobject_class->get_property = gst_face_detect_get_property;
284 
285   gstopencvbasefilter_class->cv_trans_ip_func = gst_face_detect_transform_ip;
286   gstopencvbasefilter_class->cv_set_caps = gst_face_detect_set_caps;
287 
288   g_object_class_install_property (gobject_class, PROP_DISPLAY,
289       g_param_spec_boolean ("display", "Display",
290           "Sets whether the detected faces should be highlighted in the output",
291           TRUE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
292 
293   g_object_class_install_property (gobject_class, PROP_FACE_PROFILE,
294       g_param_spec_string ("profile", "Face profile",
295           "Location of Haar cascade file to use for face detection",
296           DEFAULT_FACE_PROFILE,
297           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
298   g_object_class_install_property (gobject_class, PROP_NOSE_PROFILE,
299       g_param_spec_string ("nose-profile", "Nose profile",
300           "Location of Haar cascade file to use for nose detection",
301           DEFAULT_NOSE_PROFILE,
302           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
303   g_object_class_install_property (gobject_class, PROP_MOUTH_PROFILE,
304       g_param_spec_string ("mouth-profile", "Mouth profile",
305           "Location of Haar cascade file to use for mouth detection",
306           DEFAULT_MOUTH_PROFILE,
307           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
308   g_object_class_install_property (gobject_class, PROP_EYES_PROFILE,
309       g_param_spec_string ("eyes-profile", "Eyes profile",
310           "Location of Haar cascade file to use for eye-pair detection",
311           DEFAULT_EYES_PROFILE,
312           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
313 
314   g_object_class_install_property (gobject_class, PROP_FLAGS,
315       g_param_spec_flags ("flags", "Flags", "Flags to cvHaarDetectObjects",
316           GST_TYPE_OPENCV_FACE_DETECT_FLAGS, DEFAULT_FLAGS,
317           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
318   g_object_class_install_property (gobject_class, PROP_SCALE_FACTOR,
319       g_param_spec_double ("scale-factor", "Scale factor",
320           "Factor by which the frame is scaled after each object scan",
321           1.1, 10.0, DEFAULT_SCALE_FACTOR,
322           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
323   g_object_class_install_property (gobject_class, PROP_MIN_NEIGHBORS,
324       g_param_spec_int ("min-neighbors", "Mininum neighbors",
325           "Minimum number (minus 1) of neighbor rectangles that makes up "
326           "an object", 0, G_MAXINT, DEFAULT_MIN_NEIGHBORS,
327           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
328   g_object_class_install_property (gobject_class, PROP_MIN_SIZE_WIDTH,
329       g_param_spec_int ("min-size-width", "Minimum face width",
330           "Minimum area width to be recognized as a face", 0, G_MAXINT,
331           DEFAULT_MIN_SIZE_WIDTH,
332           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
333   g_object_class_install_property (gobject_class, PROP_MIN_SIZE_HEIGHT,
334       g_param_spec_int ("min-size-height", "Minimum face height",
335           "Minimum area height to be recognized as a face", 0, G_MAXINT,
336           DEFAULT_MIN_SIZE_HEIGHT,
337           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
338   g_object_class_install_property (gobject_class, PROP_UPDATES,
339       g_param_spec_enum ("updates", "Updates",
340           "When send update bus messages, if at all",
341           GST_TYPE_FACE_DETECT_UPDATES, GST_FACEDETECT_UPDATES_EVERY_FRAME,
342           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
343   g_object_class_install_property (gobject_class, PROP_MIN_STDDEV,
344       g_param_spec_int ("min-stddev", "Minimum image standard deviation",
345           "Minimum image average standard deviation: on images with standard "
346           "deviation lesser than this value facedetection will not be "
347           "performed. Setting this property help to save cpu and reduce "
348           "false positives not performing face detection on images with "
349           "little changes", 0, 255, DEFAULT_MIN_STDDEV,
350           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
351 
352   gst_element_class_set_static_metadata (element_class,
353       "facedetect",
354       "Filter/Effect/Video",
355       "Performs face detection on videos and images, providing detected positions via bus messages",
356       "Michael Sheldon <mike@mikeasoft.com>");
357 
358   gst_element_class_add_static_pad_template (element_class, &src_factory);
359   gst_element_class_add_static_pad_template (element_class, &sink_factory);
360 }
361 
362 /* initialize the new element
363  * initialize instance structure
364  */
365 static void
gst_face_detect_init(GstFaceDetect * filter)366 gst_face_detect_init (GstFaceDetect * filter)
367 {
368   filter->face_profile = g_strdup (DEFAULT_FACE_PROFILE);
369   filter->nose_profile = g_strdup (DEFAULT_NOSE_PROFILE);
370   filter->mouth_profile = g_strdup (DEFAULT_MOUTH_PROFILE);
371   filter->eyes_profile = g_strdup (DEFAULT_EYES_PROFILE);
372   filter->display = TRUE;
373   filter->face_detected = FALSE;
374   filter->scale_factor = DEFAULT_SCALE_FACTOR;
375   filter->min_neighbors = DEFAULT_MIN_NEIGHBORS;
376   filter->flags = DEFAULT_FLAGS;
377   filter->min_size_width = DEFAULT_MIN_SIZE_WIDTH;
378   filter->min_size_height = DEFAULT_MIN_SIZE_HEIGHT;
379   filter->min_stddev = DEFAULT_MIN_STDDEV;
380   filter->cvFaceDetect =
381       gst_face_detect_load_profile (filter, filter->face_profile);
382   filter->cvNoseDetect =
383       gst_face_detect_load_profile (filter, filter->nose_profile);
384   filter->cvMouthDetect =
385       gst_face_detect_load_profile (filter, filter->mouth_profile);
386   filter->cvEyesDetect =
387       gst_face_detect_load_profile (filter, filter->eyes_profile);
388 
389   gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER_CAST (filter),
390       TRUE);
391   filter->updates = GST_FACEDETECT_UPDATES_EVERY_FRAME;
392 }
393 
394 static void
gst_face_detect_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)395 gst_face_detect_set_property (GObject * object, guint prop_id,
396     const GValue * value, GParamSpec * pspec)
397 {
398   GstFaceDetect *filter = GST_FACE_DETECT (object);
399 
400   switch (prop_id) {
401     case PROP_FACE_PROFILE:
402       g_free (filter->face_profile);
403       if (filter->cvFaceDetect)
404         delete (filter->cvFaceDetect);
405       filter->face_profile = g_value_dup_string (value);
406       filter->cvFaceDetect =
407           gst_face_detect_load_profile (filter, filter->face_profile);
408       break;
409     case PROP_NOSE_PROFILE:
410       g_free (filter->nose_profile);
411       if (filter->cvNoseDetect)
412         delete (filter->cvNoseDetect);
413       filter->nose_profile = g_value_dup_string (value);
414       filter->cvNoseDetect =
415           gst_face_detect_load_profile (filter, filter->nose_profile);
416       break;
417     case PROP_MOUTH_PROFILE:
418       g_free (filter->mouth_profile);
419       if (filter->cvMouthDetect)
420         delete (filter->cvMouthDetect);
421       filter->mouth_profile = g_value_dup_string (value);
422       filter->cvMouthDetect =
423           gst_face_detect_load_profile (filter, filter->mouth_profile);
424       break;
425     case PROP_EYES_PROFILE:
426       g_free (filter->eyes_profile);
427       if (filter->cvEyesDetect)
428         delete (filter->cvEyesDetect);
429       filter->eyes_profile = g_value_dup_string (value);
430       filter->cvEyesDetect =
431           gst_face_detect_load_profile (filter, filter->eyes_profile);
432       break;
433     case PROP_DISPLAY:
434       filter->display = g_value_get_boolean (value);
435       break;
436     case PROP_SCALE_FACTOR:
437       filter->scale_factor = g_value_get_double (value);
438       break;
439     case PROP_MIN_NEIGHBORS:
440       filter->min_neighbors = g_value_get_int (value);
441       break;
442     case PROP_MIN_SIZE_WIDTH:
443       filter->min_size_width = g_value_get_int (value);
444       break;
445     case PROP_MIN_SIZE_HEIGHT:
446       filter->min_size_height = g_value_get_int (value);
447       break;
448     case PROP_MIN_STDDEV:
449       filter->min_stddev = g_value_get_int (value);
450       break;
451     case PROP_FLAGS:
452       filter->flags = g_value_get_flags (value);
453       break;
454     case PROP_UPDATES:
455       filter->updates = g_value_get_enum (value);
456       break;
457     default:
458       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
459       break;
460   }
461 }
462 
463 static void
gst_face_detect_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)464 gst_face_detect_get_property (GObject * object, guint prop_id,
465     GValue * value, GParamSpec * pspec)
466 {
467   GstFaceDetect *filter = GST_FACE_DETECT (object);
468 
469   switch (prop_id) {
470     case PROP_FACE_PROFILE:
471       g_value_set_string (value, filter->face_profile);
472       break;
473     case PROP_NOSE_PROFILE:
474       g_value_set_string (value, filter->nose_profile);
475       break;
476     case PROP_MOUTH_PROFILE:
477       g_value_set_string (value, filter->mouth_profile);
478       break;
479     case PROP_EYES_PROFILE:
480       g_value_set_string (value, filter->eyes_profile);
481       break;
482     case PROP_DISPLAY:
483       g_value_set_boolean (value, filter->display);
484       break;
485     case PROP_SCALE_FACTOR:
486       g_value_set_double (value, filter->scale_factor);
487       break;
488     case PROP_MIN_NEIGHBORS:
489       g_value_set_int (value, filter->min_neighbors);
490       break;
491     case PROP_MIN_SIZE_WIDTH:
492       g_value_set_int (value, filter->min_size_width);
493       break;
494     case PROP_MIN_SIZE_HEIGHT:
495       g_value_set_int (value, filter->min_size_height);
496       break;
497     case PROP_MIN_STDDEV:
498       g_value_set_int (value, filter->min_stddev);
499       break;
500     case PROP_FLAGS:
501       g_value_set_flags (value, filter->flags);
502       break;
503     case PROP_UPDATES:
504       g_value_set_enum (value, filter->updates);
505       break;
506     default:
507       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
508       break;
509   }
510 }
511 
512 /* GstElement vmethod implementations */
513 
514 /* this function handles the link with other elements */
515 static gboolean
gst_face_detect_set_caps(GstOpencvVideoFilter * transform,gint in_width,gint in_height,int in_cv_type,gint out_width,gint out_height,int out_cv_type)516 gst_face_detect_set_caps (GstOpencvVideoFilter * transform, gint in_width,
517     gint in_height, int in_cv_type,
518     gint out_width, gint out_height, int out_cv_type)
519 {
520   GstFaceDetect *filter;
521 
522   filter = GST_FACE_DETECT (transform);
523 
524   filter->cvGray.create (Size (in_width, in_height), CV_8UC1);
525 
526   return TRUE;
527 }
528 
529 static GstMessage *
gst_face_detect_message_new(GstFaceDetect * filter,GstBuffer * buf)530 gst_face_detect_message_new (GstFaceDetect * filter, GstBuffer * buf)
531 {
532   GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (filter);
533   GstStructure *s;
534   GstClockTime running_time, stream_time;
535 
536   running_time = gst_segment_to_running_time (&trans->segment, GST_FORMAT_TIME,
537       GST_BUFFER_TIMESTAMP (buf));
538   stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
539       GST_BUFFER_TIMESTAMP (buf));
540 
541   s = gst_structure_new ("facedetect",
542       "timestamp", G_TYPE_UINT64, GST_BUFFER_TIMESTAMP (buf),
543       "stream-time", G_TYPE_UINT64, stream_time,
544       "running-time", G_TYPE_UINT64, running_time,
545       "duration", G_TYPE_UINT64, GST_BUFFER_DURATION (buf), NULL);
546 
547   return gst_message_new_element (GST_OBJECT (filter), s);
548 }
549 
550 static void
gst_face_detect_run_detector(GstFaceDetect * filter,CascadeClassifier * detector,gint min_size_width,gint min_size_height,Rect r,vector<Rect> & faces)551 gst_face_detect_run_detector (GstFaceDetect * filter,
552     CascadeClassifier * detector, gint min_size_width,
553     gint min_size_height, Rect r, vector < Rect > &faces)
554 {
555   double img_stddev = 0;
556   if (filter->min_stddev > 0) {
557     Scalar mean, stddev;
558     meanStdDev (filter->cvGray, mean, stddev);
559     img_stddev = stddev.val[0];
560   }
561   if (img_stddev >= filter->min_stddev) {
562     Mat roi (filter->cvGray, r);
563     detector->detectMultiScale (roi, faces, filter->scale_factor,
564         filter->min_neighbors, filter->flags, Size (min_size_width,
565             min_size_height), Size (0, 0));
566   } else {
567     GST_LOG_OBJECT (filter,
568         "Calculated stddev %f lesser than min_stddev %d, detection not performed",
569         img_stddev, filter->min_stddev);
570   }
571 }
572 
573 /*
574  * Performs the face detection
575  */
576 static GstFlowReturn
gst_face_detect_transform_ip(GstOpencvVideoFilter * base,GstBuffer * buf,Mat img)577 gst_face_detect_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf,
578     Mat img)
579 {
580   GstFaceDetect *filter = GST_FACE_DETECT (base);
581 
582   if (filter->cvFaceDetect) {
583     GstMessage *msg = NULL;
584     GstStructure *s;
585     GValue facelist = { 0 };
586     GValue facedata = { 0 };
587     vector < Rect > faces;
588     vector < Rect > mouth;
589     vector < Rect > nose;
590     vector < Rect > eyes;
591     gboolean post_msg = FALSE;
592 
593     cvtColor (img, filter->cvGray, COLOR_RGB2GRAY);
594 
595     gst_face_detect_run_detector (filter, filter->cvFaceDetect,
596         filter->min_size_width, filter->min_size_height,
597         Rect (0, 0,
598             filter->cvGray.size ().width, filter->cvGray.size ().height),
599         faces);
600 
601     switch (filter->updates) {
602       case GST_FACEDETECT_UPDATES_EVERY_FRAME:
603         post_msg = TRUE;
604         break;
605       case GST_FACEDETECT_UPDATES_ON_CHANGE:
606         if (!faces.empty ()) {
607           if (!filter->face_detected)
608             post_msg = TRUE;
609         } else {
610           if (filter->face_detected) {
611             post_msg = TRUE;
612           }
613         }
614         break;
615       case GST_FACEDETECT_UPDATES_ON_FACE:
616         if (!faces.empty ()) {
617           post_msg = TRUE;
618         } else {
619           post_msg = FALSE;
620         }
621         break;
622       case GST_FACEDETECT_UPDATES_NONE:
623         post_msg = FALSE;
624         break;
625       default:
626         post_msg = TRUE;
627         break;
628     }
629 
630     filter->face_detected = !faces.empty ()? TRUE : FALSE;
631 
632     if (post_msg) {
633       msg = gst_face_detect_message_new (filter, buf);
634       g_value_init (&facelist, GST_TYPE_LIST);
635     }
636 
637     for (unsigned int i = 0; i < faces.size (); ++i) {
638       Rect r = faces[i];
639       guint mw = filter->min_size_width / 8;
640       guint mh = filter->min_size_height / 8;
641       guint rnx = 0, rny = 0, rnw, rnh;
642       guint rmx = 0, rmy = 0, rmw, rmh;
643       guint rex = 0, rey = 0, rew, reh;
644       guint rhh = r.height / 2;
645       gboolean have_nose, have_mouth, have_eyes;
646 
647       /* detect face features */
648 
649       if (filter->cvNoseDetect) {
650         rnx = r.x + r.width / 4;
651         rny = r.y + r.height / 4;
652         rnw = r.width / 2;
653         rnh = rhh;
654         gst_face_detect_run_detector (filter, filter->cvNoseDetect, mw, mh,
655             Rect (rnx, rny, rnw, rnh), nose);
656         have_nose = !nose.empty ();
657       } else {
658         have_nose = FALSE;
659       }
660 
661       if (filter->cvMouthDetect) {
662         rmx = r.x;
663         rmy = r.y + r.height / 2;
664         rmw = r.width;
665         rmh = rhh;
666         gst_face_detect_run_detector (filter, filter->cvMouthDetect, mw,
667             mh, Rect (rmx, rmy, rmw, rmh), mouth);
668         have_mouth = !mouth.empty ();
669       } else {
670         have_mouth = FALSE;
671       }
672 
673       if (filter->cvEyesDetect) {
674         rex = r.x;
675         rey = r.y;
676         rew = r.width;
677         reh = rhh;
678         gst_face_detect_run_detector (filter, filter->cvEyesDetect, mw, mh,
679             Rect (rex, rey, rew, reh), eyes);
680         have_eyes = !eyes.empty ();
681       } else {
682         have_eyes = FALSE;
683       }
684 
685       GST_LOG_OBJECT (filter,
686           "%2d/%2" G_GSIZE_FORMAT
687           ": x,y = %4u,%4u: w.h = %4u,%4u : features(e,n,m) = %d,%d,%d", i,
688           faces.size (), r.x, r.y, r.width, r.height, have_eyes, have_nose,
689           have_mouth);
690       if (post_msg) {
691         s = gst_structure_new ("face",
692             "x", G_TYPE_UINT, r.x,
693             "y", G_TYPE_UINT, r.y,
694             "width", G_TYPE_UINT, r.width,
695             "height", G_TYPE_UINT, r.height, NULL);
696         if (have_nose)
697           structure_and_message (nose, "nose", rnx, rny, filter, s);
698         if (have_mouth)
699           structure_and_message (mouth, "mouth", rmx, rmy, filter, s);
700         if (have_eyes)
701           structure_and_message (eyes, "eyes", rex, rey, filter, s);
702 
703         g_value_init (&facedata, GST_TYPE_STRUCTURE);
704         g_value_take_boxed (&facedata, s);
705         gst_value_list_append_value (&facelist, &facedata);
706         g_value_unset (&facedata);
707         s = NULL;
708       }
709 
710       if (filter->display) {
711         Point center;
712         Size axes;
713         gdouble w, h;
714         gint cb = 255 - ((i & 3) << 7);
715         gint cg = 255 - ((i & 12) << 5);
716         gint cr = 255 - ((i & 48) << 3);
717 
718         w = r.width / 2;
719         h = r.height / 2;
720         center.x = cvRound ((r.x + w));
721         center.y = cvRound ((r.y + h));
722         axes.width = w;
723         axes.height = h * 1.25; /* tweak for face form */
724         ellipse (img, center, axes, 0, 0, 360, Scalar (cr, cg, cb), 3, 8, 0);
725 
726         if (have_nose) {
727           Rect sr = nose[0];
728 
729           w = sr.width / 2;
730           h = sr.height / 2;
731           center.x = cvRound ((rnx + sr.x + w));
732           center.y = cvRound ((rny + sr.y + h));
733           axes.width = w;
734           axes.height = h * 1.25;       /* tweak for nose form */
735           ellipse (img, center, axes, 0, 0, 360, Scalar (cr, cg, cb), 1, 8, 0);
736         }
737         if (have_mouth) {
738           Rect sr = mouth[0];
739 
740           w = sr.width / 2;
741           h = sr.height / 2;
742           center.x = cvRound ((rmx + sr.x + w));
743           center.y = cvRound ((rmy + sr.y + h));
744           axes.width = w * 1.5; /* tweak for mouth form */
745           axes.height = h;
746           ellipse (img, center, axes, 0, 0, 360, Scalar (cr, cg, cb), 1, 8, 0);
747         }
748         if (have_eyes) {
749           Rect sr = eyes[0];
750 
751           w = sr.width / 2;
752           h = sr.height / 2;
753           center.x = cvRound ((rex + sr.x + w));
754           center.y = cvRound ((rey + sr.y + h));
755           axes.width = w * 1.5; /* tweak for eyes form */
756           axes.height = h;
757           ellipse (img, center, axes, 0, 0, 360, Scalar (cr, cg, cb), 1, 8, 0);
758         }
759       }
760       gst_buffer_add_video_region_of_interest_meta (buf, "face",
761           (guint) r.x, (guint) r.y, (guint) r.width, (guint) r.height);
762     }
763 
764     if (post_msg) {
765       gst_structure_set_value ((GstStructure *) gst_message_get_structure (msg),
766           "faces", &facelist);
767       g_value_unset (&facelist);
768       gst_element_post_message (GST_ELEMENT (filter), msg);
769     }
770   }
771 
772   return GST_FLOW_OK;
773 }
774 
775 
776 static CascadeClassifier *
gst_face_detect_load_profile(GstFaceDetect * filter,gchar * profile)777 gst_face_detect_load_profile (GstFaceDetect * filter, gchar * profile)
778 {
779   CascadeClassifier *cascade;
780 
781   cascade = new CascadeClassifier (profile);
782   if (cascade->empty ()) {
783     GST_ERROR_OBJECT (filter, "Invalid profile file: %s", profile);
784     delete cascade;
785     return NULL;
786   }
787 
788   return cascade;
789 }
790 
791 
792 /* entry point to initialize the plug-in
793  * initialize the plug-in itself
794  * register the element factories and other features
795  */
796 gboolean
gst_face_detect_plugin_init(GstPlugin * plugin)797 gst_face_detect_plugin_init (GstPlugin * plugin)
798 {
799   /* debug category for fltering log messages */
800   GST_DEBUG_CATEGORY_INIT (gst_face_detect_debug, "facedetect",
801       0,
802       "Performs face detection on videos and images, providing detected positions via bus messages");
803 
804   return gst_element_register (plugin, "facedetect", GST_RANK_NONE,
805       GST_TYPE_FACE_DETECT);
806 }
807