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