• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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