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