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