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) 2009 Noam Lewis <jones.noamle@gmail.com>
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  *
26  * Alternatively, the contents of this file may be used under the
27  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
28  * which case the following provisions apply instead of the ones
29  * mentioned above:
30  *
31  * This library is free software; you can redistribute it and/or
32  * modify it under the terms of the GNU Library General Public
33  * License as published by the Free Software Foundation; either
34  * version 2 of the License, or (at your option) any later version.
35  *
36  * This library is distributed in the hope that it will be useful,
37  * but WITHOUT ANY WARRANTY; without even the implied warranty of
38  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
39  * Library General Public License for more details.
40  *
41  * You should have received a copy of the GNU Library General Public
42  * License along with this library; if not, write to the
43  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
44  * Boston, MA 02110-1301, USA.
45  */
46 
47 /**
48  * SECTION:element-templatematch
49  *
50  * Performs template matching on videos and images, providing detected positions via bus messages.
51  *
52  * <refsect2>
53  * <title>Example launch line</title>
54  * |[
55  * gst-launch-1.0 videotestsrc ! videoconvert ! templatematch template=/path/to/file.jpg ! videoconvert ! xvimagesink
56  * ]|
57  * </refsect2>
58  */
59 
60 #ifdef HAVE_CONFIG_H
61 #  include <config.h>
62 #endif
63 
64 #include <gst/gst-i18n-plugin.h>
65 #include "gsttemplatematch.h"
66 #include <opencv2/imgproc.hpp>
67 #include <opencv2/imgcodecs.hpp>
68 
69 GST_DEBUG_CATEGORY_STATIC (gst_template_match_debug);
70 #define GST_CAT_DEFAULT gst_template_match_debug
71 
72 #define DEFAULT_METHOD (3)
73 
74 /* Filter signals and args */
75 enum
76 {
77   /* FILL ME */
78   LAST_SIGNAL
79 };
80 
81 enum
82 {
83   PROP_0,
84   PROP_METHOD,
85   PROP_TEMPLATE,
86   PROP_DISPLAY,
87 };
88 
89 /* the capabilities of the inputs and outputs.
90  */
91 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
92     GST_PAD_SINK,
93     GST_PAD_ALWAYS,
94     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGR"))
95     );
96 
97 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
98     GST_PAD_SRC,
99     GST_PAD_ALWAYS,
100     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGR"))
101     );
102 
103 G_DEFINE_TYPE (GstTemplateMatch, gst_template_match,
104     GST_TYPE_OPENCV_VIDEO_FILTER);
105 
106 static void gst_template_match_finalize (GObject * object);
107 static void gst_template_match_set_property (GObject * object, guint prop_id,
108     const GValue * value, GParamSpec * pspec);
109 static void gst_template_match_get_property (GObject * object, guint prop_id,
110     GValue * value, GParamSpec * pspec);
111 
112 static GstFlowReturn gst_template_match_transform_ip (GstOpencvVideoFilter *
113     filter, GstBuffer * buf, cv::Mat img);
114 
115 /* initialize the templatematch's class */
116 static void
gst_template_match_class_init(GstTemplateMatchClass * klass)117 gst_template_match_class_init (GstTemplateMatchClass * klass)
118 {
119   GObjectClass *gobject_class;
120   GstOpencvVideoFilterClass *gstopencvbasefilter_class;
121   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
122 
123   gobject_class = (GObjectClass *) klass;
124   gstopencvbasefilter_class = (GstOpencvVideoFilterClass *) klass;
125 
126   gobject_class->finalize = gst_template_match_finalize;
127   gobject_class->set_property = gst_template_match_set_property;
128   gobject_class->get_property = gst_template_match_get_property;
129 
130   gstopencvbasefilter_class->cv_trans_ip_func = gst_template_match_transform_ip;
131 
132   g_object_class_install_property (gobject_class, PROP_METHOD,
133       g_param_spec_int ("method", "Method",
134           "Specifies the way the template must be compared with image regions. 0=SQDIFF, 1=SQDIFF_NORMED, 2=CCOR, 3=CCOR_NORMED, 4=CCOEFF, 5=CCOEFF_NORMED.",
135           0, 5, DEFAULT_METHOD,
136           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
137   g_object_class_install_property (gobject_class, PROP_TEMPLATE,
138       g_param_spec_string ("template", "Template", "Filename of template image",
139           NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
140   g_object_class_install_property (gobject_class, PROP_DISPLAY,
141       g_param_spec_boolean ("display", "Display",
142           "Sets whether the detected template should be highlighted in the output",
143           TRUE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
144 
145   gst_element_class_set_static_metadata (element_class,
146       "templatematch",
147       "Filter/Effect/Video",
148       "Performs template matching on videos and images, providing detected positions via bus messages.",
149       "Noam Lewis <jones.noamle@gmail.com>");
150 
151   gst_element_class_add_static_pad_template (element_class, &src_factory);
152   gst_element_class_add_static_pad_template (element_class, &sink_factory);
153 }
154 
155 /* initialize the new element
156  * instantiate pads and add them to element
157  * set pad callback functions
158  * initialize instance structure
159  */
160 static void
gst_template_match_init(GstTemplateMatch * filter)161 gst_template_match_init (GstTemplateMatch * filter)
162 {
163   filter->templ = NULL;
164   filter->display = TRUE;
165   filter->reload_dist_image = TRUE;
166   filter->method = DEFAULT_METHOD;
167 
168   gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER_CAST (filter),
169       TRUE);
170 }
171 
172 /* We take ownership of template here */
173 static void
gst_template_match_load_template(GstTemplateMatch * filter,gchar * templ)174 gst_template_match_load_template (GstTemplateMatch * filter, gchar * templ)
175 {
176   cv::Mat newTemplateImage;
177 
178   if (templ) {
179     newTemplateImage = cv::imread (templ);
180 
181     if (newTemplateImage.empty ()) {
182       /* Unfortunately OpenCV doesn't seem to provide any way of finding out
183          why the image load failed, so we can't be more specific than FAILED: */
184       GST_ELEMENT_WARNING (filter, RESOURCE, FAILED,
185           (_("OpenCV failed to load template image")),
186           ("While attempting to load template '%s'", templ));
187       g_free (templ);
188       templ = NULL;
189     }
190   }
191 
192   GST_OBJECT_LOCK (filter);
193   g_free (filter->templ);
194   filter->templ = templ;
195   filter->cvTemplateImage = cv::Mat (newTemplateImage);
196   filter->reload_dist_image = TRUE;
197   GST_OBJECT_UNLOCK (filter);
198 
199 }
200 
201 static void
gst_template_match_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)202 gst_template_match_set_property (GObject * object, guint prop_id,
203     const GValue * value, GParamSpec * pspec)
204 {
205   GstTemplateMatch *filter = GST_TEMPLATE_MATCH (object);
206 
207   switch (prop_id) {
208     case PROP_METHOD:
209       GST_OBJECT_LOCK (filter);
210       switch (g_value_get_int (value)) {
211         case 0:
212           filter->method = cv::TM_SQDIFF;
213           break;
214         case 1:
215           filter->method = cv::TM_SQDIFF_NORMED;
216           break;
217         case 2:
218           filter->method = cv::TM_CCORR;
219           break;
220         case 3:
221           filter->method = cv::TM_CCORR_NORMED;
222           break;
223         case 4:
224           filter->method = cv::TM_CCOEFF;
225           break;
226         case 5:
227           filter->method = cv::TM_CCOEFF_NORMED;
228           break;
229       }
230       GST_OBJECT_UNLOCK (filter);
231       break;
232     case PROP_TEMPLATE:
233       gst_template_match_load_template (filter, g_value_dup_string (value));
234       break;
235     case PROP_DISPLAY:
236       GST_OBJECT_LOCK (filter);
237       filter->display = g_value_get_boolean (value);
238       GST_OBJECT_UNLOCK (filter);
239       break;
240     default:
241       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
242       break;
243   }
244 }
245 
246 static void
gst_template_match_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)247 gst_template_match_get_property (GObject * object, guint prop_id,
248     GValue * value, GParamSpec * pspec)
249 {
250   GstTemplateMatch *filter = GST_TEMPLATE_MATCH (object);
251 
252   switch (prop_id) {
253     case PROP_METHOD:
254       g_value_set_int (value, filter->method);
255       break;
256     case PROP_TEMPLATE:
257       g_value_set_string (value, filter->templ);
258       break;
259     case PROP_DISPLAY:
260       g_value_set_boolean (value, filter->display);
261       break;
262     default:
263       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
264       break;
265   }
266 }
267 
268 /* GstElement vmethod implementations */
269 
270 static void
gst_template_match_finalize(GObject * object)271 gst_template_match_finalize (GObject * object)
272 {
273   GstTemplateMatch *filter;
274   filter = GST_TEMPLATE_MATCH (object);
275 
276   g_free (filter->templ);
277 
278   filter->cvDistImage.release ();
279   filter->cvTemplateImage.release ();
280 
281   G_OBJECT_CLASS (gst_template_match_parent_class)->finalize (object);
282 }
283 
284 static void
gst_template_match_match(cv::Mat input,cv::Mat templ,cv::Mat dist_image,double * best_res,cv::Point * best_pos,int method)285 gst_template_match_match (cv::Mat input, cv::Mat templ,
286     cv::Mat dist_image, double *best_res, cv::Point * best_pos, int method)
287 {
288   double dist_min = 0, dist_max = 0;
289   cv::Point min_pos, max_pos;
290   matchTemplate (input, templ, dist_image, method);
291   minMaxLoc (dist_image, &dist_min, &dist_max, &min_pos, &max_pos);
292   if ((cv::TM_SQDIFF_NORMED == method) || (cv::TM_SQDIFF == method)) {
293     *best_res = dist_min;
294     *best_pos = min_pos;
295     if (cv::TM_SQDIFF_NORMED == method) {
296       *best_res = 1 - *best_res;
297     }
298   } else {
299     *best_res = dist_max;
300     *best_pos = max_pos;
301   }
302 }
303 
304 /* chain function
305  * this function does the actual processing
306  */
307 static GstFlowReturn
gst_template_match_transform_ip(GstOpencvVideoFilter * base,GstBuffer * buf,cv::Mat img)308 gst_template_match_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf,
309     cv::Mat img)
310 {
311   GstTemplateMatch *filter;
312   cv::Point best_pos;
313   double best_res;
314   GstMessage *m = NULL;
315 
316   filter = GST_TEMPLATE_MATCH (base);
317 
318   GST_LOG_OBJECT (filter, "Buffer size %u", (guint) gst_buffer_get_size (buf));
319 
320   GST_OBJECT_LOCK (filter);
321   if (!filter->cvTemplateImage.empty () && filter->reload_dist_image) {
322     if (filter->cvTemplateImage.size ().width > img.size ().width) {
323       GST_WARNING ("Template Image is wider than input image");
324     } else if (filter->cvTemplateImage.size ().height > img.size ().height) {
325       GST_WARNING ("Template Image is taller than input image");
326     } else {
327 
328       GST_DEBUG_OBJECT (filter, "cv create (Size(%d-%d+1,%d) %d)",
329           img.size ().width, filter->cvTemplateImage.size ().width,
330           img.size ().height - filter->cvTemplateImage.size ().height + 1,
331           CV_32FC1);
332       filter->cvDistImage.create (cv::Size (img.size ().width -
333               filter->cvTemplateImage.size ().width + 1,
334               img.size ().height - filter->cvTemplateImage.size ().height + 1),
335           CV_32FC1);
336       filter->reload_dist_image = FALSE;
337     }
338   }
339   if (!filter->cvTemplateImage.empty () && !filter->reload_dist_image) {
340     GstStructure *s;
341 
342     gst_template_match_match (img, filter->cvTemplateImage,
343         filter->cvDistImage, &best_res, &best_pos, filter->method);
344 
345     s = gst_structure_new ("template_match",
346         "x", G_TYPE_UINT, best_pos.x,
347         "y", G_TYPE_UINT, best_pos.y,
348         "width", G_TYPE_UINT, filter->cvTemplateImage.size ().width,
349         "height", G_TYPE_UINT, filter->cvTemplateImage.size ().height,
350         "result", G_TYPE_DOUBLE, best_res, NULL);
351 
352     m = gst_message_new_element (GST_OBJECT (filter), s);
353 
354     if (filter->display) {
355       cv::Point corner = best_pos;
356       cv::Scalar color;
357       if (filter->method == cv::TM_SQDIFF_NORMED
358           || filter->method == cv::TM_CCORR_NORMED
359           || filter->method == cv::TM_CCOEFF_NORMED) {
360         /* Yellow growing redder as match certainty approaches 1.0.  This can
361            only be applied with method == *_NORMED as the other match methods
362            aren't normalized to be in range 0.0 - 1.0 */
363         color = CV_RGB (255, 255 - pow (255, best_res), 32);
364       } else {
365         color = CV_RGB (255, 32, 32);
366       }
367 
368       buf = gst_buffer_make_writable (buf);
369 
370       corner.x += filter->cvTemplateImage.size ().width;
371       corner.y += filter->cvTemplateImage.size ().height;
372       cv::rectangle (img, best_pos, corner, color, 3, 8, 0);
373     }
374 
375   }
376   GST_OBJECT_UNLOCK (filter);
377 
378   if (m) {
379     gst_element_post_message (GST_ELEMENT (filter), m);
380   }
381   return GST_FLOW_OK;
382 }
383 
384 /* entry point to initialize the plug-in
385  * initialize the plug-in itself
386  * register the element factories and other features
387  */
388 gboolean
gst_template_match_plugin_init(GstPlugin * templatematch)389 gst_template_match_plugin_init (GstPlugin * templatematch)
390 {
391   /* debug category for fltering log messages */
392   GST_DEBUG_CATEGORY_INIT (gst_template_match_debug, "templatematch",
393       0,
394       "Performs template matching on videos and images, providing detected positions via bus messages");
395 
396   return gst_element_register (templatematch, "templatematch", GST_RANK_NONE,
397       GST_TYPE_TEMPLATE_MATCH);
398 }
399