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-retinex
46 *
47 * Basic and multiscale retinex for colour image enhancement, see article:
48 *
49 * Rahman, Zia-ur, Daniel J. Jobson, and Glenn A. Woodell. "Multi-scale retinex for
50 * color image enhancement." Image Processing, 1996. Proceedings., International
51 * Conference on. Vol. 3. IEEE, 1996.
52 *
53 * ## Example launch line
54 *
55 * |[
56 * gst-launch-1.0 videotestsrc ! videoconvert ! retinex ! videoconvert ! xvimagesink
57 * ]|
58 */
59
60 #ifdef HAVE_CONFIG_H
61 #include <config.h>
62 #endif
63
64 #include "gstretinex.h"
65 #include <opencv2/imgproc.hpp>
66
67 GST_DEBUG_CATEGORY_STATIC (gst_retinex_debug);
68 #define GST_CAT_DEFAULT gst_retinex_debug
69
70 using namespace cv;
71 /* Filter signals and args */
72 enum
73 {
74 /* FILL ME */
75 LAST_SIGNAL
76 };
77
78 enum
79 {
80 PROP_0,
81 PROP_METHOD,
82 PROP_SCALES,
83 PROP_SIGMA,
84 PROP_GAIN,
85 PROP_OFFSET,
86 };
87 typedef enum
88 {
89 METHOD_BASIC,
90 METHOD_MULTISCALE
91 } GstRetinexMethod;
92
93 #define DEFAULT_METHOD METHOD_BASIC
94 #define DEFAULT_SCALES 3
95 #define DEFAULT_SIGMA 14.0
96 #define DEFAULT_GAIN 128
97 #define DEFAULT_OFFSET 128
98
99 #define GST_TYPE_RETINEX_METHOD (gst_retinex_method_get_type ())
100 static GType
gst_retinex_method_get_type(void)101 gst_retinex_method_get_type (void)
102 {
103 static GType etype = 0;
104 if (etype == 0) {
105 static const GEnumValue values[] = {
106 {METHOD_BASIC, "Basic retinex restoration", "basic"},
107 {METHOD_MULTISCALE, "Mutiscale retinex restoration", "multiscale"},
108 {0, NULL, NULL},
109 };
110 etype = g_enum_register_static ("GstRetinexMethod", values);
111 }
112 return etype;
113 }
114
115 G_DEFINE_TYPE_WITH_CODE (GstRetinex, gst_retinex, GST_TYPE_OPENCV_VIDEO_FILTER,
116 GST_DEBUG_CATEGORY_INIT (gst_retinex_debug, "retinex", 0,
117 "Multiscale retinex for colour image enhancement");
118 );
119 GST_ELEMENT_REGISTER_DEFINE (retinex, "retinex", GST_RANK_NONE,
120 GST_TYPE_RETINEX);
121
122 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
123 GST_PAD_SINK,
124 GST_PAD_ALWAYS,
125 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB")));
126
127 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
128 GST_PAD_SRC,
129 GST_PAD_ALWAYS,
130 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB")));
131
132
133 static void gst_retinex_set_property (GObject * object, guint prop_id,
134 const GValue * value, GParamSpec * pspec);
135 static void gst_retinex_get_property (GObject * object, guint prop_id,
136 GValue * value, GParamSpec * pspec);
137
138 static GstFlowReturn gst_retinex_transform_ip (GstOpencvVideoFilter * filter,
139 GstBuffer * buff, Mat img);
140 static gboolean gst_retinex_set_caps (GstOpencvVideoFilter * btrans,
141 gint in_width, gint in_height, int in_cv_type,
142 gint out_width, gint out_height, int out_cv_type);
143
144 static void gst_retinex_finalize (GObject * object);
145
146 /* initialize the retinex's class */
147 static void
gst_retinex_class_init(GstRetinexClass * klass)148 gst_retinex_class_init (GstRetinexClass * klass)
149 {
150 GObjectClass *gobject_class = (GObjectClass *) klass;
151 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
152 GstOpencvVideoFilterClass *cvbasefilter_class =
153 (GstOpencvVideoFilterClass *) klass;
154
155 gobject_class->finalize = gst_retinex_finalize;
156 gobject_class->set_property = gst_retinex_set_property;
157 gobject_class->get_property = gst_retinex_get_property;
158
159 cvbasefilter_class->cv_trans_ip_func = gst_retinex_transform_ip;
160 cvbasefilter_class->cv_set_caps = gst_retinex_set_caps;
161
162 g_object_class_install_property (gobject_class, PROP_METHOD,
163 g_param_spec_enum ("method",
164 "Retinex method to use",
165 "Retinex method to use",
166 GST_TYPE_RETINEX_METHOD, DEFAULT_METHOD,
167 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
168
169 g_object_class_install_property (gobject_class, PROP_SCALES,
170 g_param_spec_int ("scales", "scales",
171 "Amount of gaussian filters (scales) used in multiscale retinex", 1,
172 4, DEFAULT_SCALES,
173 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
174
175 /**
176 * GstRetinex:sigma:
177 *
178 * Sigma
179 *
180 * Since: 1.20
181 */
182 g_object_class_install_property (gobject_class, PROP_SIGMA,
183 g_param_spec_double ("sigma", "Sigma",
184 "Sigma", 0.0, G_MAXDOUBLE, DEFAULT_SIGMA,
185 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
186
187 /**
188 * GstRetinex:gain:
189 *
190 * Gain
191 *
192 * Since: 1.20
193 */
194 g_object_class_install_property (gobject_class, PROP_GAIN,
195 g_param_spec_int ("gain", "gain",
196 "Gain", 0, G_MAXINT, DEFAULT_GAIN,
197 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
198
199 /**
200 * GstRetinex:offset:
201 *
202 * Offset
203 *
204 * Since: 1.20
205 */
206 g_object_class_install_property (gobject_class, PROP_OFFSET,
207 g_param_spec_int ("offset", "Offset",
208 "Offset", 0, G_MAXINT, DEFAULT_OFFSET,
209 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
210
211 gst_element_class_set_static_metadata (element_class,
212 "Retinex image colour enhancement", "Filter/Effect/Video",
213 "Multiscale retinex for colour image enhancement",
214 "Miguel Casas-Sanchez <miguelecasassanchez@gmail.com>");
215
216 gst_element_class_add_static_pad_template (element_class, &src_factory);
217 gst_element_class_add_static_pad_template (element_class, &sink_factory);
218
219 gst_type_mark_as_plugin_api (GST_TYPE_RETINEX_METHOD, (GstPluginAPIFlags) 0);
220 }
221
222 /* initialize the new element
223 * instantiate pads and add them to element
224 * set pad callback functions
225 * initialize instance structure
226 */
227 static void
gst_retinex_init(GstRetinex * filter)228 gst_retinex_init (GstRetinex * filter)
229 {
230 filter->method = DEFAULT_METHOD;
231 filter->scales = DEFAULT_SCALES;
232 filter->current_scales = 0;
233 filter->gain = DEFAULT_GAIN;
234 filter->offset = DEFAULT_OFFSET;
235 filter->sigma = DEFAULT_SIGMA;
236 gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER_CAST (filter),
237 TRUE);
238 }
239
240 static void
gst_retinex_finalize(GObject * object)241 gst_retinex_finalize (GObject * object)
242 {
243 GstRetinex *filter;
244 filter = GST_RETINEX (object);
245
246 filter->cvA.release ();
247 filter->cvB.release ();
248 filter->cvC.release ();
249 filter->cvD.release ();
250 g_free (filter->weights);
251 filter->weights = NULL;
252 g_free (filter->sigmas);
253 filter->sigmas = NULL;
254
255 G_OBJECT_CLASS (gst_retinex_parent_class)->finalize (object);
256 }
257
258 static void
gst_retinex_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)259 gst_retinex_set_property (GObject * object, guint prop_id,
260 const GValue * value, GParamSpec * pspec)
261 {
262 GstRetinex *retinex = GST_RETINEX (object);
263
264 switch (prop_id) {
265 case PROP_METHOD:
266 retinex->method = g_value_get_enum (value);
267 break;
268 case PROP_SCALES:
269 retinex->scales = g_value_get_int (value);
270 break;
271 case PROP_SIGMA:
272 retinex->sigma = g_value_get_double (value);
273 break;
274 case PROP_GAIN:
275 retinex->gain = g_value_get_int (value);
276 break;
277 case PROP_OFFSET:
278 retinex->offset = g_value_get_int (value);
279 break;
280 default:
281 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
282 break;
283 }
284 }
285
286 static void
gst_retinex_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)287 gst_retinex_get_property (GObject * object, guint prop_id,
288 GValue * value, GParamSpec * pspec)
289 {
290 GstRetinex *filter = GST_RETINEX (object);
291
292 switch (prop_id) {
293 case PROP_METHOD:
294 g_value_set_enum (value, filter->method);
295 break;
296 case PROP_SCALES:
297 g_value_set_int (value, filter->scales);
298 break;
299 case PROP_SIGMA:
300 g_value_set_double (value, filter->sigma);
301 break;
302 case PROP_GAIN:
303 g_value_set_int (value, filter->gain);
304 break;
305 case PROP_OFFSET:
306 g_value_set_int (value, filter->offset);
307 break;
308 default:
309 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
310 break;
311 }
312 }
313
314 static gboolean
gst_retinex_set_caps(GstOpencvVideoFilter * filter,gint in_width,gint in_height,int in_cv_type,gint out_width,gint out_height,int out_cv_type)315 gst_retinex_set_caps (GstOpencvVideoFilter * filter, gint in_width,
316 gint in_height, int in_cv_type, gint out_width,
317 gint out_height, int out_cv_type)
318 {
319 GstRetinex *retinex = GST_RETINEX (filter);
320 Size size;
321
322 size = Size (in_width, in_height);
323
324 retinex->cvA.create (size, CV_32FC3);
325 retinex->cvB.create (size, CV_32FC3);
326 retinex->cvC.create (size, CV_32FC3);
327 retinex->cvD.create (size, CV_32FC3);
328
329 return TRUE;
330 }
331
332 static GstFlowReturn
gst_retinex_transform_ip(GstOpencvVideoFilter * filter,GstBuffer * buf,Mat img)333 gst_retinex_transform_ip (GstOpencvVideoFilter * filter, GstBuffer * buf,
334 Mat img)
335 {
336 GstRetinex *retinex = GST_RETINEX (filter);
337 int filter_size;
338
339 /* Basic retinex restoration. The image and a filtered image are converted
340 to the log domain and subtracted.
341 O = Log(I) - Log(H(I))
342 where O is the output, H is a gaussian 2d filter and I is the input image. */
343 if (METHOD_BASIC == retinex->method) {
344 /* Compute log image */
345 img.convertTo (retinex->cvA, retinex->cvA.type ());
346 log (retinex->cvA, retinex->cvB);
347
348 /* Compute log of blurred image */
349 filter_size = (int) floor (retinex->sigma * 6) / 2;
350 filter_size = filter_size * 2 + 1;
351
352 img.convertTo (retinex->cvD, retinex->cvD.type ());
353 GaussianBlur (retinex->cvD, retinex->cvD, Size (filter_size, filter_size),
354 0.0, 0.0);
355 log (retinex->cvD, retinex->cvC);
356
357 /* Compute difference */
358 subtract (retinex->cvB, retinex->cvC, retinex->cvA);
359
360 /* Restore */
361 retinex->cvA.convertTo (img, img.type (), (float) retinex->gain, (float) retinex->offset);
362 }
363 /* Multiscale retinex restoration. The image and a set of filtered images are
364 converted to the log domain and subtracted from the original with some set
365 of weights. Typically called with three equally weighted scales of fine,
366 medium and wide standard deviations.
367 O = Log(I) - sum_i [ wi * Log(H(I)) ]
368 where O is the output, H is a gaussian 2d filter and I is the input image
369 sum_i means summatory on var i with i in [0..scales) and wi are the weights */
370 else if (METHOD_MULTISCALE == retinex->method) {
371 int i;
372
373 /* allocate or reallocate the weights and sigmas according to scales */
374 if (retinex->current_scales != retinex->scales || !retinex->sigmas) {
375 retinex->weights =
376 (double *) g_realloc (retinex->weights,
377 sizeof (double) * retinex->scales);
378 retinex->sigmas =
379 (double *) g_realloc (retinex->sigmas,
380 sizeof (double) * retinex->scales);
381 for (i = 0; i < retinex->scales; i++) {
382 retinex->weights[i] = 1.0 / (double) retinex->scales;
383 retinex->sigmas[i] = 10.0 + 4.0 * (double) retinex->scales;
384 }
385 retinex->current_scales = retinex->scales;
386 }
387
388 /* Compute log image */
389 img.convertTo (retinex->cvA, retinex->cvA.type ());
390 log (retinex->cvA, retinex->cvB);
391
392 /* Filter at each scale */
393 for (i = 0; i < retinex->scales; i++) {
394 filter_size = (int) floor (retinex->sigmas[i] * 6) / 2;
395 filter_size = filter_size * 2 + 1;
396
397 img.convertTo (retinex->cvD, retinex->cvD.type ());
398 GaussianBlur (retinex->cvD, retinex->cvD, Size (filter_size, filter_size),
399 0.0, 0.0);
400 log (retinex->cvD, retinex->cvC);
401
402 /* Compute weighted difference */
403 retinex->cvC.convertTo (retinex->cvC, -1, retinex->weights[i], 0.0);
404 subtract (retinex->cvB, retinex->cvC, retinex->cvB);
405 }
406
407 /* Restore */
408 retinex->cvB.convertTo (img, img.type (), (float) retinex->gain, (float) retinex->offset);
409 }
410
411 return GST_FLOW_OK;
412 }
413