Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
eigf.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2019-2020 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#pragma once
20
22#include "common/gaussian.h"
23
24/***
25 * DOCUMENTATION
26 *
27 * Exposure-Independent Guided Filter (EIGF)
28 *
29 * This filter is a modification of guided filter to make it exposure independent
30 * As variance depends on the exposure, the original guided filter preserves
31 * much better the edges in the highlights than in the shadows.
32 * In particular doing:
33 * (1) increase exposure by 1EV
34 * (2) guided filtering
35 * (3) decrease exposure by 1EV
36 * is NOT equivalent to doing the guided filtering only.
37 *
38 * To overcome this, instead of using variance directly to determine "a",
39 * we use a ratio:
40 * variance / (pixel_value)^2
41 * we tried also the following ratios:
42 * - variance / average^2
43 * - variance / (pixel_value * average)
44 * we kept variance / (pixel_value)^2 as it seemed to behave a bit better than
45 * the other (dividing by average^2 smoothed too much dark details surrounded
46 * by bright pixels).
47 *
48 * This modification makes the filter exposure-independent.
49 * However, due to the fact that the average advantages the bright pixels
50 * compared to dark pixels if we consider that human eye sees in log,
51 * we get strong bright halos.
52 * These are due to the spatial averaging of "a" and "b" that is performed at
53 * the end of the filter, especially due to the spatial averaging of "b".
54 * We decided to remove this final spatial averaging, as it is very hard
55 * to keep it without having either large unsmoothed regions or halos.
56 * Although the filter may blur a bit less without it, it remains sufficiently
57 * good at smoothing the image, and there are much less halos.
58 *
59 * The implementation EIGF uses downscaling to speed-up the filtering,
60 * just like what is done in fast_guided_filter.h
61**/
62
63/* computes average and variance of guide and mask, and put them in out.
64 * out has 4 channels:
65 * - average of guide
66 * - variance of guide
67 * - average of mask
68 * - covariance of mask and guide. */
69static inline void eigf_variance_analysis(const float *const restrict guide, // I
70 const float *const restrict mask, //p
71 float *const restrict out,
72 const size_t width, const size_t height,
73 const float sigma)
74{
75 // We also use gaussian blurs instead of the square blurs of the guided filter
76 const size_t Ndim = width * height;
77 float *const restrict in = dt_alloc_sse_ps(Ndim * 4);
78 dt_gaussian_t *g = NULL;
79 if(in == NULL) goto error;
80
81 float ming = 10000000.0f;
82 float maxg = 0.0f;
83 float minm = 10000000.0f;
84 float maxm = 0.0f;
85 float ming2 = 10000000.0f;
86 float maxg2 = 0.0f;
87 float minmg = 10000000.0f;
88 float maxmg = 0.0f;
89#ifdef _OPENMP
90#pragma omp parallel for default(none) \
91dt_omp_firstprivate(guide, mask, in, Ndim) \
92 schedule(simd:static) \
93 reduction(max:maxg, maxm, maxg2, maxmg)\
94 reduction(min:ming, minm, ming2, minmg)
95#endif
96 for(size_t k = 0; k < Ndim; k++)
97 {
98 const float pixelg = guide[k];
99 const float pixelm = mask[k];
100 const float pixelg2 = pixelg * pixelg;
101 const float pixelmg = pixelm * pixelg;
102 in[k * 4] = pixelg;
103 in[k * 4 + 1] = pixelg2;
104 in[k * 4 + 2] = pixelm;
105 in[k * 4 + 3] = pixelmg;
106 ming = MIN(ming,pixelg);
107 maxg = MAX(maxg,pixelg);
108 minm = MIN(minm,pixelm);
109 maxm = MAX(maxm,pixelm);
110 ming2 = MIN(ming2,pixelg2);
111 maxg2 = MAX(maxg2,pixelg2);
112 minmg = MIN(minmg,pixelmg);
113 maxmg = MAX(maxmg,pixelmg);
114 }
115
116 dt_aligned_pixel_t max = {maxg, maxg2, maxm, maxmg};
117 dt_aligned_pixel_t min = {ming, ming2, minm, minmg};
118 g = dt_gaussian_init(width, height, 4, max, min, sigma, 0);
119 if(g == NULL) goto error;
120 dt_gaussian_blur_4c(g, in, out);
121
122#ifdef _OPENMP
123#pragma omp parallel for simd default(none) \
124dt_omp_firstprivate(out, Ndim) \
125 schedule(simd:static) aligned(out:64)
126#endif
127 for(size_t k = 0; k < Ndim; k++)
128 {
129 out[4 * k + 1] -= out[4 * k] * out[4 * k];
130 out[4 * k + 3] -= out[4 * k] * out[4 * k + 2];
131 }
132
133error:;
134 if(g) dt_gaussian_free(g);
135 if(in) dt_free_align(in);
136}
137
138// same function as above, but specialized for the case where guide == mask
139// for increased performance
140static inline void eigf_variance_analysis_no_mask(const float *const restrict guide, // I
141 float *const restrict out,
142 const size_t width, const size_t height,
143 const float sigma)
144{
145 // We also use gaussian blurs instead of the square blurs of the guided filter
146 const size_t Ndim = width * height;
147 float *const restrict in = dt_alloc_sse_ps(Ndim * 2);
148 dt_gaussian_t *g = NULL;
149 if(in == NULL) goto error;
150
151 float ming = 10000000.0f;
152 float maxg = 0.0f;
153 float ming2 = 10000000.0f;
154 float maxg2 = 0.0f;
155#ifdef _OPENMP
156#pragma omp parallel for default(none) \
157dt_omp_firstprivate(guide, in, Ndim) \
158 schedule(simd:static) \
159 reduction(max:maxg, maxg2)\
160 reduction(min:ming, ming2)
161#endif
162 for(size_t k = 0; k < Ndim; k++)
163 {
164 const float pixelg = guide[k];
165 const float pixelg2 = pixelg * pixelg;
166 in[2 * k] = pixelg;
167 in[2 * k + 1] = pixelg2;
168 ming = MIN(ming,pixelg);
169 maxg = MAX(maxg,pixelg);
170 ming2 = MIN(ming2,pixelg2);
171 maxg2 = MAX(maxg2,pixelg2);
172 }
173
174 float max[2] = {maxg, maxg2};
175 float min[2] = {ming, ming2};
176 g = dt_gaussian_init(width, height, 2, max, min, sigma, 0);
177 if(!g) goto error;
178 dt_gaussian_blur(g, in, out);
179
180#ifdef _OPENMP
181#pragma omp parallel for simd default(none) \
182dt_omp_firstprivate(out, Ndim) \
183 schedule(simd:static) aligned(out:64)
184#endif
185 for(size_t k = 0; k < Ndim; k++)
186 {
187 const float avg = out[2 * k];
188 out[2 * k + 1] -= avg * avg;
189 }
190
191error:;
192 if(g) dt_gaussian_free(g);
193 if(in) dt_free_align(in);
194}
195
196static inline void eigf_blending(float *const restrict image, const float *const restrict mask,
197 const float *const restrict av, const size_t Ndim,
198 const dt_iop_guided_filter_blending_t filter, const float feathering)
199{
200#ifdef _OPENMP
201#pragma omp parallel for simd default(none) \
202 dt_omp_firstprivate(image, mask, av, Ndim, feathering, filter) \
203 schedule(simd:static) aligned(image, mask, av:64)
204#endif
205 for(size_t k = 0; k < Ndim; k++)
206 {
207 const float avg_g = av[k * 4];
208 const float avg_m = av[k * 4 + 2];
209 const float var_g = av[k * 4 + 1];
210 const float covar_mg = av[k * 4 + 3];
211 const float norm_g = fmaxf(avg_g * image[k], 1E-6);
212 const float norm_m = fmaxf(avg_m * mask[k], 1E-6);
213 const float normalized_var_guide = var_g / norm_g;
214 const float normalized_covar = covar_mg / sqrtf(norm_g * norm_m);
215 const float a = normalized_covar / (normalized_var_guide + feathering);
216 const float b = avg_m - a * avg_g;
217 if(filter == DT_GF_BLENDING_LINEAR)
218 {
219 image[k] = fmaxf(image[k] * a + b, MIN_FLOAT);
220 }
221 else
222 {
223 // filter == DT_GF_BLENDING_GEOMEAN
224 image[k] *= fmaxf(image[k] * a + b, MIN_FLOAT);
225 image[k] = sqrtf(image[k]);
226 }
227 }
228}
229
230// same function as above, but specialized for the case where guide == mask
231// for increased performance
232static inline void eigf_blending_no_mask(float *const restrict image, const float *const restrict av,
233 const size_t Ndim, const dt_iop_guided_filter_blending_t filter,
234 const float feathering)
235{
236#ifdef _OPENMP
237#pragma omp parallel for simd default(none) \
238 dt_omp_firstprivate(image, av, Ndim, feathering, filter) \
239 schedule(simd:static) aligned(image, av:64)
240#endif
241 for(size_t k = 0; k < Ndim; k++)
242 {
243 const float avg_g = av[k * 2];
244 const float var_g = av[k * 2 + 1];
245 const float norm_g = fmaxf(avg_g * image[k], 1E-6);
246 const float normalized_var_guide = var_g / norm_g;
247 const float a = normalized_var_guide / (normalized_var_guide + feathering);
248 const float b = avg_g - a * avg_g;
249 if(filter == DT_GF_BLENDING_LINEAR)
250 {
251 image[k] = fmaxf(image[k] * a + b, MIN_FLOAT);
252 }
253 else
254 {
255 // filter == DT_GF_BLENDING_GEOMEAN
256 image[k] *= fmaxf(image[k] * a + b, MIN_FLOAT);
257 image[k] = sqrtf(image[k]);
258 }
259 }
260}
261
263static inline void fast_eigf_surface_blur(float *const restrict image,
264 const size_t width, const size_t height,
265 const float sigma, float feathering, const int iterations,
266 const dt_iop_guided_filter_blending_t filter, const float scale,
267 const float quantization, const float quantize_min, const float quantize_max)
268{
269 // Works in-place on a grey image
270 // mostly similar with fast_surface_blur from fast_guided_filter.h
271
272 // A down-scaling of 4 seems empirically safe and consistent no matter the image zoom level
273 // see reference paper above for proof.
274 const float scaling = fmaxf(fminf(sigma, 4.0f), 1.0f);
275 const float ds_sigma = fmaxf(sigma / scaling, 1.0f);
276
277 const size_t ds_height = height / scaling;
278 const size_t ds_width = width / scaling;
279
280 const size_t num_elem_ds = ds_width * ds_height;
281 const size_t num_elem = width * height;
282
283 float *const restrict mask = dt_alloc_sse_ps(dt_round_size_sse(num_elem));
284 float *const restrict ds_image = dt_alloc_sse_ps(dt_round_size_sse(num_elem_ds));
285 float *const restrict ds_mask = dt_alloc_sse_ps(dt_round_size_sse(num_elem_ds));
286 // average - variance arrays: store the guide and mask averages and variances
287 float *const restrict ds_av = dt_alloc_sse_ps(dt_round_size_sse(num_elem_ds * 4));
288 float *const restrict av = dt_alloc_sse_ps(dt_round_size_sse(num_elem * 4));
289
290 if(!ds_image || !ds_mask || !ds_av || !av || !mask)
291 {
292 dt_control_log(_("fast exposure independent guided filter failed to allocate memory, check your RAM settings"));
293 goto clean;
294 }
295
296 // Iterations of filter models the diffusion, sort of
297 for(int i = 0; i < iterations; i++)
298 {
299 // blend linear for all intermediate images
301 // use filter for last iteration
302 if(i == iterations - 1)
303 blend = filter;
304
305 interpolate_bilinear(image, width, height, ds_image, ds_width, ds_height, 1);
306 if(quantization != 0.0f)
307 {
308 // (Re)build the mask from the quantized image to help guiding
309 quantize(image, mask, width * height, quantization, quantize_min, quantize_max);
310 // Downsample the image for speed-up
311 interpolate_bilinear(mask, width, height, ds_mask, ds_width, ds_height, 1);
312 eigf_variance_analysis(ds_mask, ds_image, ds_av, ds_width, ds_height, ds_sigma);
313 // Upsample the variances and averages
314 interpolate_bilinear(ds_av, ds_width, ds_height, av, width, height, 4);
315 // Blend the guided image
316 eigf_blending(image, mask, av, num_elem, blend, feathering);
317 }
318 else
319 {
320 // no need to build a mask.
321 eigf_variance_analysis_no_mask(ds_image, ds_av, ds_width, ds_height, ds_sigma);
322 // Upsample the variances and averages
323 interpolate_bilinear(ds_av, ds_width, ds_height, av, width, height, 2);
324 // Blend the guided image
325 eigf_blending_no_mask(image, av, num_elem, blend, feathering);
326 }
327 }
328
329clean:
330 if(av) dt_free_align(av);
331 if(ds_av) dt_free_align(ds_av);
332 if(ds_mask) dt_free_align(ds_mask);
333 if(ds_image) dt_free_align(ds_image);
334 if(mask) dt_free_align(mask);
335}
336// clang-format off
337// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
338// vim: shiftwidth=2 expandtab tabstop=2 cindent
339// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
340// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:191
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
void dt_control_log(const char *msg,...)
Definition control.c:424
static size_t dt_round_size_sse(const size_t size)
Definition darktable.h:285
#define __DT_CLONE_TARGETS__
Definition darktable.h:249
#define dt_free_align(A)
Definition darktable.h:334
static void * dt_alloc_sse_ps(size_t pixels)
Definition darktable.h:356
static void eigf_blending_no_mask(float *const restrict image, const float *const restrict av, const size_t Ndim, const dt_iop_guided_filter_blending_t filter, const float feathering)
Definition eigf.h:232
static void eigf_variance_analysis(const float *const restrict guide, const float *const restrict mask, float *const restrict out, const size_t width, const size_t height, const float sigma)
Definition eigf.h:69
static void eigf_variance_analysis_no_mask(const float *const restrict guide, float *const restrict out, const size_t width, const size_t height, const float sigma)
Definition eigf.h:140
static __DT_CLONE_TARGETS__ void fast_eigf_surface_blur(float *const restrict image, const size_t width, const size_t height, const float sigma, float feathering, const int iterations, const dt_iop_guided_filter_blending_t filter, const float scale, const float quantization, const float quantize_min, const float quantize_max)
Definition eigf.h:263
static void eigf_blending(float *const restrict image, const float *const restrict mask, const float *const restrict av, const size_t Ndim, const dt_iop_guided_filter_blending_t filter, const float feathering)
Definition eigf.h:196
dt_iop_guided_filter_blending_t
Definition fast_guided_filter.h:44
@ DT_GF_BLENDING_LINEAR
Definition fast_guided_filter.h:45
static __DT_CLONE_TARGETS__ void quantize(const float *const restrict image, float *const restrict out, const size_t num_elem, const float sampling, const float clip_min, const float clip_max)
Definition fast_guided_filter.h:254
#define MIN_FLOAT
Definition fast_guided_filter.h:40
static __DT_CLONE_TARGETS__ void interpolate_bilinear(const float *const restrict in, const size_t width_in, const size_t height_in, float *const restrict out, const size_t width_out, const size_t height_out, const size_t ch)
Definition fast_guided_filter.h:97
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:497
void dt_gaussian_blur(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:156
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:486
dt_gaussian_t * dt_gaussian_init(const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:114
Definition gaussian.h:34
#define E
Definition test_filmicrgb.c:48
#define MIN(a, b)
Definition thinplate.c:23
#define MAX(a, b)
Definition thinplate.c:20