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