/src/leptonica/src/bilateral.c
Line | Count | Source |
1 | | /*====================================================================* |
2 | | - Copyright (C) 2001 Leptonica. All rights reserved. |
3 | | - |
4 | | - Redistribution and use in source and binary forms, with or without |
5 | | - modification, are permitted provided that the following conditions |
6 | | - are met: |
7 | | - 1. Redistributions of source code must retain the above copyright |
8 | | - notice, this list of conditions and the following disclaimer. |
9 | | - 2. Redistributions in binary form must reproduce the above |
10 | | - copyright notice, this list of conditions and the following |
11 | | - disclaimer in the documentation and/or other materials |
12 | | - provided with the distribution. |
13 | | - |
14 | | - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
15 | | - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
16 | | - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
17 | | - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY |
18 | | - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | | - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | | - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | | - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | | - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
23 | | - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
24 | | - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | | *====================================================================*/ |
26 | | |
27 | | /*! |
28 | | * \file bilateral.c |
29 | | * <pre> |
30 | | * |
31 | | * Top level approximate separable grayscale or color bilateral filtering |
32 | | * PIX *pixBilateral() |
33 | | * PIX *pixBilateralGray() |
34 | | * |
35 | | * Implementation of approximate separable bilateral filter |
36 | | * static L_BILATERAL *bilateralCreate() |
37 | | * static void *bilateralDestroy() |
38 | | * static PIX *bilateralApply() |
39 | | * |
40 | | * Slow, exact implementation of grayscale or color bilateral filtering |
41 | | * PIX *pixBilateralExact() |
42 | | * PIX *pixBilateralGrayExact() |
43 | | * PIX *pixBlockBilateralExact() |
44 | | * |
45 | | * Kernel helper function |
46 | | * L_KERNEL *makeRangeKernel() |
47 | | * |
48 | | * This includes both a slow, exact implementation of the bilateral |
49 | | * filter algorithm (given by Sylvain Paris and Frédo Durand), |
50 | | * and a fast, approximate and separable implementation (following |
51 | | * Yang, Tan and Ahuja). See bilateral.h for algorithmic details. |
52 | | * |
53 | | * The bilateral filter has the nice property of applying a gaussian |
54 | | * filter to smooth parts of the image that don't vary too quickly, |
55 | | * while at the same time preserving edges. The filter is nonlinear |
56 | | * and cannot be decomposed into two separable filters; however, |
57 | | * there exists an approximate method that is separable. To further |
58 | | * speed up the separable implementation, you can generate the |
59 | | * intermediate data at reduced resolution. |
60 | | * |
61 | | * The full kernel is composed of two parts: a spatial gaussian filter |
62 | | * and a nonlinear "range" filter that depends on the intensity difference |
63 | | * between the reference pixel at the spatial kernel origin and any other |
64 | | * pixel within the kernel support. |
65 | | * |
66 | | * In our implementations, the range filter is a parameterized, |
67 | | * one-sided, 256-element, monotonically decreasing gaussian function |
68 | | * of the absolute value of the difference between pixel values; namely, |
69 | | * abs(I2 - I1). In general, any decreasing function can be used, |
70 | | * and more generally, any two-dimensional kernel can be used if |
71 | | * you wish to relax the 'abs' condition. (In that case, the range |
72 | | * filter can be 256 x 256). |
73 | | * </pre> |
74 | | */ |
75 | | |
76 | | #ifdef HAVE_CONFIG_H |
77 | | #include <config_auto.h> |
78 | | #endif /* HAVE_CONFIG_H */ |
79 | | |
80 | | #include <math.h> |
81 | | #include "allheaders.h" |
82 | | #include "bilateral.h" |
83 | | |
84 | | static L_BILATERAL *bilateralCreate(PIX *pixs, l_float32 spatial_stdev, |
85 | | l_float32 range_stdev, l_int32 ncomps, |
86 | | l_int32 reduction); |
87 | | static PIX *bilateralApply(L_BILATERAL *bil); |
88 | | static void bilateralDestroy(L_BILATERAL **pbil); |
89 | | |
90 | | |
91 | | #ifndef NO_CONSOLE_IO |
92 | | #define DEBUG_BILATERAL 0 |
93 | | #endif /* ~NO_CONSOLE_IO */ |
94 | | |
95 | | /*--------------------------------------------------------------------------* |
96 | | * Top level approximate separable grayscale or color bilateral filtering * |
97 | | *--------------------------------------------------------------------------*/ |
98 | | /*! |
99 | | * \brief pixBilateral() |
100 | | * |
101 | | * \param[in] pixs 8 bpp gray or 32 bpp rgb, no colormap |
102 | | * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5 |
103 | | * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0 |
104 | | * \param[in] ncomps number of intermediate sums J(k,x); |
105 | | * in [4 ... 30] |
106 | | * \param[in] reduction 1, 2 or 4 |
107 | | * \return pixd bilateral filtered image, or NULL on error |
108 | | * |
109 | | * <pre> |
110 | | * Notes: |
111 | | * (1) This performs a relatively fast, separable bilateral |
112 | | * filtering operation. The time is proportional to ncomps |
113 | | * and varies inversely approximately as the cube of the |
114 | | * reduction factor. See bilateral.h for algorithm details. |
115 | | * (2) We impose minimum values for range_stdev and ncomps to |
116 | | * avoid nasty artifacts when either are too small. We also |
117 | | * impose a constraint on their product: |
118 | | * ncomps * range_stdev >= 100. |
119 | | * So for values of range_stdev >= 25, ncomps can be as small as 4. |
120 | | * Here is a qualitative, intuitive explanation for this constraint. |
121 | | * Call the difference in k values between the J(k) == 'delta', where |
122 | | * 'delta' ~ 200 / ncomps |
123 | | * Then this constraint is roughly equivalent to the condition: |
124 | | * 'delta' < 2 * range_stdev |
125 | | * Note that at an intensity difference of (2 * range_stdev), the |
126 | | * range part of the kernel reduces the effect by the factor 0.14. |
127 | | * This constraint requires that we have a sufficient number of |
128 | | * PCBs (i.e, a small enough 'delta'), so that for any value of |
129 | | * image intensity I, there exists a k (and a PCB, J(k), such that |
130 | | * |I - k| < range_stdev |
131 | | * Any fewer PCBs and we don't have enough to support this condition. |
132 | | * (3) The upper limit of 30 on ncomps is imposed because the |
133 | | * gain in accuracy is not worth the extra computation. |
134 | | * (4) The size of the gaussian kernel is twice the spatial_stdev |
135 | | * on each side of the origin. The minimum value of |
136 | | * spatial_stdev, 0.5, is required to have a finite sized |
137 | | * spatial kernel. In practice, a much larger value is used. |
138 | | * (5) Computation of the intermediate images goes inversely |
139 | | * as the cube of the reduction factor. If you can use a |
140 | | * reduction of 2 or 4, it is well-advised. |
141 | | * (6) The range kernel is defined over the absolute value of pixel |
142 | | * grayscale differences, and hence must have size 256 x 1. |
143 | | * Values in the array represent the multiplying weight |
144 | | * depending on the absolute gray value difference between |
145 | | * the source pixel and the neighboring pixel, and should |
146 | | * be monotonically decreasing. |
147 | | * (7) Interesting observation. Run this on prog/fish24.jpg, with |
148 | | * range_stdev = 60, ncomps = 6, and spatial_dev = {10, 30, 50}. |
149 | | * As spatial_dev gets larger, we get the counter-intuitive |
150 | | * result that the body of the red fish becomes less blurry. |
151 | | * (8) The image must be sufficiently big to get reasonable results. |
152 | | * This requires the dimensions to be at least twice the filter size. |
153 | | * Otherwise, return a copy of the input with warning. |
154 | | * </pre> |
155 | | */ |
156 | | PIX * |
157 | | pixBilateral(PIX *pixs, |
158 | | l_float32 spatial_stdev, |
159 | | l_float32 range_stdev, |
160 | | l_int32 ncomps, |
161 | | l_int32 reduction) |
162 | 284 | { |
163 | 284 | l_int32 w, h, d, filtersize; |
164 | 284 | l_float32 sstdev; /* scaled spatial stdev */ |
165 | 284 | PIX *pixt, *pixr, *pixg, *pixb, *pixd; |
166 | | |
167 | 284 | if (!pixs || pixGetColormap(pixs)) |
168 | 74 | return (PIX *)ERROR_PTR("pixs not defined or cmapped", __func__, NULL); |
169 | 210 | pixGetDimensions(pixs, &w, &h, &d); |
170 | 210 | if (d != 8 && d != 32) |
171 | 8 | return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL); |
172 | 202 | if (reduction != 1 && reduction != 2 && reduction != 4) |
173 | 0 | return (PIX *)ERROR_PTR("reduction invalid", __func__, NULL); |
174 | 202 | filtersize = (l_int32)(2.0 * spatial_stdev + 1.0 + 0.5); |
175 | 202 | if (w < 2 * filtersize || h < 2 * filtersize) { |
176 | 77 | L_WARNING("w = %d, h = %d; w or h < 2 * filtersize = %d; " |
177 | 77 | "returning copy\n", __func__, w, h, 2 * filtersize); |
178 | 77 | return pixCopy(NULL, pixs); |
179 | 77 | } |
180 | 125 | sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */ |
181 | 125 | if (sstdev < 0.5) |
182 | 0 | return (PIX *)ERROR_PTR("sstdev < 0.5", __func__, NULL); |
183 | 125 | if (range_stdev <= 5.0) |
184 | 0 | return (PIX *)ERROR_PTR("range_stdev <= 5.0", __func__, NULL); |
185 | 125 | if (ncomps < 4 || ncomps > 30) |
186 | 0 | return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", __func__, NULL); |
187 | 125 | if (ncomps * range_stdev < 100.0) |
188 | 0 | return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", __func__, NULL); |
189 | | |
190 | 125 | if (d == 8) |
191 | 118 | return pixBilateralGray(pixs, spatial_stdev, range_stdev, |
192 | 118 | ncomps, reduction); |
193 | | |
194 | 7 | pixt = pixGetRGBComponent(pixs, COLOR_RED); |
195 | 7 | pixr = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps, |
196 | 7 | reduction); |
197 | 7 | pixDestroy(&pixt); |
198 | 7 | pixt = pixGetRGBComponent(pixs, COLOR_GREEN); |
199 | 7 | pixg = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps, |
200 | 7 | reduction); |
201 | 7 | pixDestroy(&pixt); |
202 | 7 | pixt = pixGetRGBComponent(pixs, COLOR_BLUE); |
203 | 7 | pixb = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps, |
204 | 7 | reduction); |
205 | 7 | pixDestroy(&pixt); |
206 | 7 | pixd = pixCreateRGBImage(pixr, pixg, pixb); |
207 | 7 | pixDestroy(&pixr); |
208 | 7 | pixDestroy(&pixg); |
209 | 7 | pixDestroy(&pixb); |
210 | 7 | return pixd; |
211 | 125 | } |
212 | | |
213 | | |
214 | | /*! |
215 | | * \brief pixBilateralGray() |
216 | | * |
217 | | * \param[in] pixs 8 bpp gray |
218 | | * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5 |
219 | | * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0 |
220 | | * \param[in] ncomps number of intermediate sums J(k,x); |
221 | | * in [4 ... 30] |
222 | | * \param[in] reduction 1, 2 or 4 |
223 | | * \return pixd 8 bpp bilateral filtered image, or NULL on error |
224 | | * |
225 | | * <pre> |
226 | | * Notes: |
227 | | * (1) See pixBilateral() for constraints on the input parameters. |
228 | | * (2) See pixBilateral() for algorithm details. |
229 | | * </pre> |
230 | | */ |
231 | | PIX * |
232 | | pixBilateralGray(PIX *pixs, |
233 | | l_float32 spatial_stdev, |
234 | | l_float32 range_stdev, |
235 | | l_int32 ncomps, |
236 | | l_int32 reduction) |
237 | 139 | { |
238 | 139 | l_float32 sstdev; /* scaled spatial stdev */ |
239 | 139 | PIX *pixd; |
240 | 139 | L_BILATERAL *bil; |
241 | | |
242 | 139 | if (!pixs || pixGetColormap(pixs)) |
243 | 0 | return (PIX *)ERROR_PTR("pixs not defined or cmapped", __func__, NULL); |
244 | 139 | if (pixGetDepth(pixs) != 8) |
245 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp gray", __func__, NULL); |
246 | 139 | if (reduction != 1 && reduction != 2 && reduction != 4) |
247 | 0 | return (PIX *)ERROR_PTR("reduction invalid", __func__, NULL); |
248 | 139 | sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */ |
249 | 139 | if (sstdev < 0.5) |
250 | 0 | return (PIX *)ERROR_PTR("sstdev < 0.5", __func__, NULL); |
251 | 139 | if (range_stdev <= 5.0) |
252 | 0 | return (PIX *)ERROR_PTR("range_stdev <= 5.0", __func__, NULL); |
253 | 139 | if (ncomps < 4 || ncomps > 30) |
254 | 0 | return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", __func__, NULL); |
255 | 139 | if (ncomps * range_stdev < 100.0) |
256 | 0 | return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", __func__, NULL); |
257 | | |
258 | 139 | bil = bilateralCreate(pixs, spatial_stdev, range_stdev, ncomps, reduction); |
259 | 139 | if (!bil) return (PIX *)ERROR_PTR("bil not made", __func__, NULL); |
260 | 139 | pixd = bilateralApply(bil); |
261 | 139 | bilateralDestroy(&bil); |
262 | 139 | return pixd; |
263 | 139 | } |
264 | | |
265 | | |
266 | | /*----------------------------------------------------------------------* |
267 | | * Implementation of approximate separable bilateral filter * |
268 | | *----------------------------------------------------------------------*/ |
269 | | /*! |
270 | | * \brief bilateralCreate() |
271 | | * |
272 | | * \param[in] pixs 8 bpp gray, no colormap |
273 | | * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5 |
274 | | * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0 |
275 | | * \param[in] ncomps number of intermediate sums J(k,x); |
276 | | * in [4 ... 30] |
277 | | * \param[in] reduction 1, 2 or 4 |
278 | | * \return bil, or NULL on error |
279 | | * |
280 | | * <pre> |
281 | | * Notes: |
282 | | * (1) This initializes a bilateral filtering operation, generating all |
283 | | * the data required. It takes most of the time in the bilateral |
284 | | * filtering operation. |
285 | | * (2) See bilateral.h for details of the algorithm. |
286 | | * (3) See pixBilateral() for constraints on input parameters, which |
287 | | * are not checked here. |
288 | | * </pre> |
289 | | */ |
290 | | static L_BILATERAL * |
291 | | bilateralCreate(PIX *pixs, |
292 | | l_float32 spatial_stdev, |
293 | | l_float32 range_stdev, |
294 | | l_int32 ncomps, |
295 | | l_int32 reduction) |
296 | 139 | { |
297 | 139 | l_int32 w, ws, wd, h, hs, hd, i, j, k, index; |
298 | 139 | l_int32 border, minval, maxval, spatial_size; |
299 | 139 | l_int32 halfwidth, wpls, wplt, wpld, kval, nval, dval; |
300 | 139 | l_float32 sstdev, fval1, fval2, denom, sum, norm, kern; |
301 | 139 | l_int32 *nc, *kindex; |
302 | 139 | l_float32 *kfract, *range, *spatial; |
303 | 139 | l_uint32 *datas, *datat, *datad, *lines, *linet, *lined; |
304 | 139 | L_BILATERAL *bil; |
305 | 139 | PIX *pix1, *pix2, *pixt, *pixsc, *pixd; |
306 | 139 | PIXA *pixac; |
307 | | |
308 | 139 | if (reduction == 1) { |
309 | 139 | pix1 = pixClone(pixs); |
310 | 139 | } else if (reduction == 2) { |
311 | 0 | pix1 = pixScaleAreaMap2(pixs); |
312 | 0 | } else { /* reduction == 4) */ |
313 | 0 | pix2 = pixScaleAreaMap2(pixs); |
314 | 0 | pix1 = pixScaleAreaMap2(pix2); |
315 | 0 | pixDestroy(&pix2); |
316 | 0 | } |
317 | 139 | if (!pix1) |
318 | 0 | return (L_BILATERAL *)ERROR_PTR("pix1 not made", __func__, NULL); |
319 | | |
320 | 139 | sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */ |
321 | 139 | border = (l_int32)(2 * sstdev + 1); |
322 | 139 | pixsc = pixAddMirroredBorder(pix1, border, border, border, border); |
323 | 139 | pixGetExtremeValue(pix1, 1, L_SELECT_MIN, NULL, NULL, NULL, &minval); |
324 | 139 | pixGetExtremeValue(pix1, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxval); |
325 | 139 | pixDestroy(&pix1); |
326 | 139 | if (!pixsc) |
327 | 0 | return (L_BILATERAL *)ERROR_PTR("pixsc not made", __func__, NULL); |
328 | | |
329 | 139 | bil = (L_BILATERAL *)LEPT_CALLOC(1, sizeof(L_BILATERAL)); |
330 | 139 | bil->spatial_stdev = sstdev; |
331 | 139 | bil->range_stdev = range_stdev; |
332 | 139 | bil->reduction = reduction; |
333 | 139 | bil->ncomps = ncomps; |
334 | 139 | bil->minval = minval; |
335 | 139 | bil->maxval = maxval; |
336 | 139 | bil->pixsc = pixsc; |
337 | 139 | bil->pixs = pixClone(pixs); |
338 | | |
339 | | /* -------------------------------------------------------------------- * |
340 | | * Generate arrays for interpolation of J(k,x): |
341 | | * (1.0 - kfract[.]) * J(kindex[.], x) + kfract[.] * J(kindex[.] + 1, x), |
342 | | * where I(x) is the index into kfract[] and kindex[], |
343 | | * and x is an index into the 2D image array. |
344 | | * -------------------------------------------------------------------- */ |
345 | | /* nc is the set of k values to be used in J(k,x) */ |
346 | 139 | nc = (l_int32 *)LEPT_CALLOC(ncomps, sizeof(l_int32)); |
347 | 1.52k | for (i = 0; i < ncomps; i++) |
348 | 1.39k | nc[i] = minval + i * (maxval - minval) / (ncomps - 1); |
349 | 139 | bil->nc = nc; |
350 | | |
351 | | /* kindex maps from intensity I(x) to the lower k index for J(k,x) */ |
352 | 139 | kindex = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); |
353 | 1.39k | for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) { |
354 | 1.25k | fval2 = nc[k + 1]; |
355 | 31.7k | while (i < fval2) { |
356 | 30.5k | kindex[i] = k; |
357 | 30.5k | i++; |
358 | 30.5k | } |
359 | 1.25k | } |
360 | 139 | kindex[maxval] = ncomps - 2; |
361 | 139 | bil->kindex = kindex; |
362 | | |
363 | | /* kfract maps from intensity I(x) to the fraction of J(k+1,x) used */ |
364 | 139 | kfract = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32)); /* from lower */ |
365 | 1.39k | for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) { |
366 | 1.25k | fval1 = nc[k]; |
367 | 1.25k | fval2 = nc[k + 1]; |
368 | 31.7k | while (i < fval2) { |
369 | 30.5k | kfract[i] = (l_float32)(i - fval1) / (l_float32)(fval2 - fval1); |
370 | 30.5k | i++; |
371 | 30.5k | } |
372 | 1.25k | } |
373 | 139 | kfract[maxval] = 1.0; |
374 | 139 | bil->kfract = kfract; |
375 | | |
376 | | #if DEBUG_BILATERAL |
377 | | for (i = minval; i <= maxval; i++) |
378 | | lept_stderr("kindex[%d] = %d; kfract[%d] = %5.3f\n", |
379 | | i, kindex[i], i, kfract[i]); |
380 | | for (i = 0; i < ncomps; i++) |
381 | | lept_stderr("nc[%d] = %d\n", i, nc[i]); |
382 | | #endif /* DEBUG_BILATERAL */ |
383 | | |
384 | | /* -------------------------------------------------------------------- * |
385 | | * Generate 1-D kernel arrays (spatial and range) * |
386 | | * -------------------------------------------------------------------- */ |
387 | 139 | spatial_size = 2 * sstdev + 1; /* same as the added border */ |
388 | 139 | spatial = (l_float32 *)LEPT_CALLOC(spatial_size, sizeof(l_float32)); |
389 | 139 | denom = 2. * sstdev * sstdev; |
390 | 1.66k | for (i = 0; i < spatial_size; i++) |
391 | 1.52k | spatial[i] = expf(-(l_float32)(i * i) / denom); |
392 | 139 | bil->spatial = spatial; |
393 | | |
394 | 139 | range = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32)); |
395 | 139 | denom = 2. * range_stdev * range_stdev; |
396 | 35.7k | for (i = 0; i < 256; i++) |
397 | 35.5k | range[i] = expf(-(l_float32)(i * i) / denom); |
398 | 139 | bil->range = range; |
399 | | |
400 | | /* -------------------------------------------------------------------- * |
401 | | * Generate principal bilateral component images * |
402 | | * -------------------------------------------------------------------- */ |
403 | 139 | pixac = pixaCreate(ncomps); |
404 | 139 | pixGetDimensions(pixsc, &ws, &hs, NULL); |
405 | 139 | datas = pixGetData(pixsc); |
406 | 139 | wpls = pixGetWpl(pixsc); |
407 | 139 | pixGetDimensions(pixs, &w, &h, NULL); |
408 | 139 | wd = (w + reduction - 1) / reduction; |
409 | 139 | hd = (h + reduction - 1) / reduction; |
410 | 139 | halfwidth = (l_int32)(2.0 * sstdev); |
411 | 1.52k | for (index = 0; index < ncomps; index++) { |
412 | 1.39k | pixt = pixCopy(NULL, pixsc); |
413 | 1.39k | datat = pixGetData(pixt); |
414 | 1.39k | wplt = pixGetWpl(pixt); |
415 | 1.39k | kval = nc[index]; |
416 | | /* Separable convolutions: horizontal first */ |
417 | 89.8k | for (i = 0; i < hd; i++) { |
418 | 88.4k | lines = datas + (border + i) * wpls; |
419 | 88.4k | linet = datat + (border + i) * wplt; |
420 | 8.40M | for (j = 0; j < wd; j++) { |
421 | 8.31M | sum = 0.0; |
422 | 8.31M | norm = 0.0; |
423 | 182M | for (k = -halfwidth; k <= halfwidth; k++) { |
424 | 174M | nval = GET_DATA_BYTE(lines, border + j + k); |
425 | 174M | kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)]; |
426 | 174M | sum += kern * nval; |
427 | 174M | norm += kern; |
428 | 174M | } |
429 | 8.31M | if (norm > 0.0) { |
430 | 7.02M | dval = (l_int32)((sum / norm) + 0.5); |
431 | 7.02M | SET_DATA_BYTE(linet, border + j, dval); |
432 | 7.02M | } |
433 | 8.31M | } |
434 | 88.4k | } |
435 | | /* Vertical convolution */ |
436 | 1.39k | pixd = pixCreate(wd, hd, 8); |
437 | 1.39k | datad = pixGetData(pixd); |
438 | 1.39k | wpld = pixGetWpl(pixd); |
439 | 89.8k | for (i = 0; i < hd; i++) { |
440 | 88.4k | linet = datat + (border + i) * wplt; |
441 | 88.4k | lined = datad + i * wpld; |
442 | 8.40M | for (j = 0; j < wd; j++) { |
443 | 8.31M | sum = 0.0; |
444 | 8.31M | norm = 0.0; |
445 | 182M | for (k = -halfwidth; k <= halfwidth; k++) { |
446 | 174M | nval = GET_DATA_BYTE(linet + k * wplt, border + j); |
447 | 174M | kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)]; |
448 | 174M | sum += kern * nval; |
449 | 174M | norm += kern; |
450 | 174M | } |
451 | 8.31M | if (norm > 0.0) |
452 | 8.26M | dval = (l_int32)((sum / norm) + 0.5); |
453 | 50.2k | else |
454 | 50.2k | dval = GET_DATA_BYTE(linet, border + j); |
455 | 8.31M | SET_DATA_BYTE(lined, j, dval); |
456 | 8.31M | } |
457 | 88.4k | } |
458 | 1.39k | pixDestroy(&pixt); |
459 | 1.39k | pixaAddPix(pixac, pixd, L_INSERT); |
460 | 1.39k | } |
461 | 139 | bil->pixac = pixac; |
462 | 139 | bil->lineset = (l_uint32 ***)pixaGetLinePtrs(pixac, NULL); |
463 | 139 | return bil; |
464 | 139 | } |
465 | | |
466 | | |
467 | | /*! |
468 | | * \brief bilateralApply() |
469 | | * |
470 | | * \param[in] bil |
471 | | * \return pixd |
472 | | */ |
473 | | static PIX * |
474 | | bilateralApply(L_BILATERAL *bil) |
475 | 139 | { |
476 | 139 | l_int32 i, j, k, ired, jred, w, h, wpls, wpld, ncomps, reduction; |
477 | 139 | l_int32 vals, vald, lowval, hival; |
478 | 139 | l_int32 *kindex; |
479 | 139 | l_float32 fract; |
480 | 139 | l_float32 *kfract; |
481 | 139 | l_uint32 *lines, *lined, *datas, *datad; |
482 | 139 | l_uint32 ***lineset = NULL; /* for set of PBC */ |
483 | 139 | PIX *pixs, *pixd; |
484 | 139 | PIXA *pixac; |
485 | | |
486 | 139 | if (!bil) |
487 | 0 | return (PIX *)ERROR_PTR("bil not defined", __func__, NULL); |
488 | 139 | pixs = bil->pixs; |
489 | 139 | ncomps = bil->ncomps; |
490 | 139 | kindex = bil->kindex; |
491 | 139 | kfract = bil->kfract; |
492 | 139 | reduction = bil->reduction; |
493 | 139 | pixac = bil->pixac; |
494 | 139 | lineset = bil->lineset; |
495 | 139 | if (pixaGetCount(pixac) != ncomps) |
496 | 0 | return (PIX *)ERROR_PTR("PBC images do not exist", __func__, NULL); |
497 | | |
498 | 139 | if ((pixd = pixCreateTemplate(pixs)) == NULL) |
499 | 0 | return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); |
500 | 139 | datas = pixGetData(pixs); |
501 | 139 | wpls = pixGetWpl(pixs); |
502 | 139 | datad = pixGetData(pixd); |
503 | 139 | wpld = pixGetWpl(pixd); |
504 | 139 | pixGetDimensions(pixs, &w, &h, NULL); |
505 | 8.98k | for (i = 0; i < h; i++) { |
506 | 8.84k | lines = datas + i * wpls; |
507 | 8.84k | lined = datad + i * wpld; |
508 | 8.84k | ired = i / reduction; |
509 | 840k | for (j = 0; j < w; j++) { |
510 | 831k | jred = j / reduction; |
511 | 831k | vals = GET_DATA_BYTE(lines, j); |
512 | 831k | k = kindex[vals]; |
513 | 831k | lowval = GET_DATA_BYTE(lineset[k][ired], jred); |
514 | 831k | hival = GET_DATA_BYTE(lineset[k + 1][ired], jred); |
515 | 831k | fract = kfract[vals]; |
516 | 831k | vald = (l_int32)((1.0 - fract) * lowval + fract * hival + 0.5); |
517 | 831k | SET_DATA_BYTE(lined, j, vald); |
518 | 831k | } |
519 | 8.84k | } |
520 | | |
521 | 139 | return pixd; |
522 | 139 | } |
523 | | |
524 | | |
525 | | /*! |
526 | | * \brief bilateralDestroy() |
527 | | * |
528 | | * \param[in,out] pbil will be set to null before returning |
529 | | */ |
530 | | static void |
531 | | bilateralDestroy(L_BILATERAL **pbil) |
532 | 139 | { |
533 | 139 | l_int32 i; |
534 | 139 | L_BILATERAL *bil; |
535 | | |
536 | 139 | if (pbil == NULL) { |
537 | 0 | L_WARNING("ptr address is null!\n", __func__); |
538 | 0 | return; |
539 | 0 | } |
540 | | |
541 | 139 | if ((bil = *pbil) == NULL) |
542 | 0 | return; |
543 | | |
544 | 139 | pixDestroy(&bil->pixs); |
545 | 139 | pixDestroy(&bil->pixsc); |
546 | 139 | pixaDestroy(&bil->pixac); |
547 | 139 | LEPT_FREE(bil->spatial); |
548 | 139 | LEPT_FREE(bil->range); |
549 | 139 | LEPT_FREE(bil->nc); |
550 | 139 | LEPT_FREE(bil->kindex); |
551 | 139 | LEPT_FREE(bil->kfract); |
552 | 1.52k | for (i = 0; i < bil->ncomps; i++) |
553 | 1.39k | LEPT_FREE(bil->lineset[i]); |
554 | 139 | LEPT_FREE(bil->lineset); |
555 | 139 | LEPT_FREE(bil); |
556 | 139 | *pbil = NULL; |
557 | 139 | } |
558 | | |
559 | | |
560 | | /*----------------------------------------------------------------------* |
561 | | * Exact implementation of grayscale or color bilateral filtering * |
562 | | *----------------------------------------------------------------------*/ |
563 | | /*! |
564 | | * \brief pixBilateralExact() |
565 | | * |
566 | | * \param[in] pixs 8 bpp gray or 32 bpp rgb |
567 | | * \param[in] spatial_kel gaussian kernel |
568 | | * \param[in] range_kel [optional] 256 x 1, monotonically decreasing |
569 | | * \return pixd 8 bpp bilateral filtered image |
570 | | * |
571 | | * <pre> |
572 | | * Notes: |
573 | | * (1) The spatial_kel is a conventional smoothing kernel, typically a |
574 | | * 2-d Gaussian kernel or other block kernel. It can be either |
575 | | * normalized or not, but must be everywhere positive. |
576 | | * (2) The range_kel is defined over the absolute value of pixel |
577 | | * grayscale differences, and hence must have size 256 x 1. |
578 | | * Values in the array represent the multiplying weight for each |
579 | | * gray value difference between the target pixel and center of the |
580 | | * kernel, and should be monotonically decreasing. |
581 | | * (3) If range_kel == NULL, a constant weight is applied regardless |
582 | | * of the range value difference. This degenerates to a regular |
583 | | * pixConvolve() with a normalized kernel. |
584 | | * </pre> |
585 | | */ |
586 | | PIX * |
587 | | pixBilateralExact(PIX *pixs, |
588 | | L_KERNEL *spatial_kel, |
589 | | L_KERNEL *range_kel) |
590 | 202 | { |
591 | 202 | l_int32 d; |
592 | 202 | PIX *pixt, *pixr, *pixg, *pixb, *pixd; |
593 | | |
594 | 202 | if (!pixs) |
595 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
596 | 202 | if (pixGetColormap(pixs) != NULL) |
597 | 0 | return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL); |
598 | 202 | d = pixGetDepth(pixs); |
599 | 202 | if (d != 8 && d != 32) |
600 | 0 | return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL); |
601 | 202 | if (!spatial_kel) |
602 | 0 | return (PIX *)ERROR_PTR("spatial_ke not defined", __func__, NULL); |
603 | | |
604 | 202 | if (d == 8) { |
605 | 161 | return pixBilateralGrayExact(pixs, spatial_kel, range_kel); |
606 | 161 | } else { /* d == 32 */ |
607 | 41 | pixt = pixGetRGBComponent(pixs, COLOR_RED); |
608 | 41 | pixr = pixBilateralGrayExact(pixt, spatial_kel, range_kel); |
609 | 41 | pixDestroy(&pixt); |
610 | 41 | pixt = pixGetRGBComponent(pixs, COLOR_GREEN); |
611 | 41 | pixg = pixBilateralGrayExact(pixt, spatial_kel, range_kel); |
612 | 41 | pixDestroy(&pixt); |
613 | 41 | pixt = pixGetRGBComponent(pixs, COLOR_BLUE); |
614 | 41 | pixb = pixBilateralGrayExact(pixt, spatial_kel, range_kel); |
615 | 41 | pixDestroy(&pixt); |
616 | 41 | pixd = pixCreateRGBImage(pixr, pixg, pixb); |
617 | | |
618 | 41 | pixDestroy(&pixr); |
619 | 41 | pixDestroy(&pixg); |
620 | 41 | pixDestroy(&pixb); |
621 | 41 | return pixd; |
622 | 41 | } |
623 | 202 | } |
624 | | |
625 | | |
626 | | /*! |
627 | | * \brief pixBilateralGrayExact() |
628 | | * |
629 | | * \param[in] pixs 8 bpp gray |
630 | | * \param[in] spatial_kel gaussian kernel |
631 | | * \param[in] range_kel [optional] 256 x 1, monotonically decreasing |
632 | | * \return pixd 8 bpp bilateral filtered image |
633 | | * |
634 | | * <pre> |
635 | | * Notes: |
636 | | * (1) See pixBilateralExact(). |
637 | | * </pre> |
638 | | */ |
639 | | PIX * |
640 | | pixBilateralGrayExact(PIX *pixs, |
641 | | L_KERNEL *spatial_kel, |
642 | | L_KERNEL *range_kel) |
643 | 284 | { |
644 | 284 | l_int32 i, j, id, jd, k, m, w, h, d, sx, sy, cx, cy, wplt, wpld; |
645 | 284 | l_int32 val, center_val; |
646 | 284 | l_uint32 *datat, *datad, *linet, *lined; |
647 | 284 | l_float32 sum, weight_sum, weight; |
648 | 284 | L_KERNEL *keli; |
649 | 284 | PIX *pixt, *pixd; |
650 | | |
651 | 284 | if (!pixs) |
652 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
653 | 284 | if (pixGetDepth(pixs) != 8) |
654 | 0 | return (PIX *)ERROR_PTR("pixs must be gray", __func__, NULL); |
655 | 284 | pixGetDimensions(pixs, &w, &h, &d); |
656 | 284 | if (!spatial_kel) |
657 | 0 | return (PIX *)ERROR_PTR("spatial kel not defined", __func__, NULL); |
658 | 284 | kernelGetParameters(spatial_kel, &sy, &sx, NULL, NULL); |
659 | 284 | if (w < 2 * sx + 1 || h < 2 * sy + 1) { |
660 | 267 | L_WARNING("w = %d < 2 * sx + 1 = %d, or h = %d < 2 * sy + 1 = %d; " |
661 | 267 | "returning copy\n", __func__, w, 2 * sx + 1, h, 2 * sy + 1); |
662 | 267 | return pixCopy(NULL, pixs); |
663 | 267 | } |
664 | 17 | if (!range_kel) |
665 | 0 | return pixConvolve(pixs, spatial_kel, 8, 1); |
666 | 17 | if (range_kel->sx != 256 || range_kel->sy != 1) |
667 | 0 | return (PIX *)ERROR_PTR("range kel not {256 x 1", __func__, NULL); |
668 | | |
669 | 17 | keli = kernelInvert(spatial_kel); |
670 | 17 | kernelGetParameters(keli, &sy, &sx, &cy, &cx); |
671 | 17 | if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL) { |
672 | 0 | kernelDestroy(&keli); |
673 | 0 | return (PIX *)ERROR_PTR("pixt not made", __func__, NULL); |
674 | 0 | } |
675 | | |
676 | 17 | pixd = pixCreate(w, h, 8); |
677 | 17 | datat = pixGetData(pixt); |
678 | 17 | datad = pixGetData(pixd); |
679 | 17 | wplt = pixGetWpl(pixt); |
680 | 17 | wpld = pixGetWpl(pixd); |
681 | 2.70k | for (i = 0, id = 0; id < h; i++, id++) { |
682 | 2.68k | lined = datad + id * wpld; |
683 | 475k | for (j = 0, jd = 0; jd < w; j++, jd++) { |
684 | 472k | center_val = GET_DATA_BYTE(datat + (i + cy) * wplt, j + cx); |
685 | 472k | weight_sum = 0.0; |
686 | 472k | sum = 0.0; |
687 | 19.8M | for (k = 0; k < sy; k++) { |
688 | 19.3M | linet = datat + (i + k) * wplt; |
689 | 813M | for (m = 0; m < sx; m++) { |
690 | 793M | val = GET_DATA_BYTE(linet, j + m); |
691 | 793M | weight = keli->data[k][m] * |
692 | 793M | range_kel->data[0][L_ABS(center_val - val)]; |
693 | 793M | weight_sum += weight; |
694 | 793M | sum += val * weight; |
695 | 793M | } |
696 | 19.3M | } |
697 | 472k | SET_DATA_BYTE(lined, jd, (l_int32)(sum / weight_sum + 0.5)); |
698 | 472k | } |
699 | 2.68k | } |
700 | | |
701 | 17 | kernelDestroy(&keli); |
702 | 17 | pixDestroy(&pixt); |
703 | 17 | return pixd; |
704 | 17 | } |
705 | | |
706 | | |
707 | | /*! |
708 | | * \brief pixBlockBilateralExact() |
709 | | * |
710 | | * \param[in] pixs 8 bpp gray or 32 bpp rgb |
711 | | * \param[in] spatial_stdev must be > 0.0 |
712 | | * \param[in] range_stdev must be > 0.0 |
713 | | * \return pixd 8 bpp or 32 bpp bilateral filtered image |
714 | | * |
715 | | * <pre> |
716 | | * Notes: |
717 | | * (1) See pixBilateralExact(). This provides an interface using |
718 | | * the standard deviations of the spatial and range filters. |
719 | | * (2) The convolution window halfwidth is 2 * spatial_stdev, |
720 | | * and the square filter size is 4 * spatial_stdev + 1. |
721 | | * The kernel captures 95% of total energy. This is compensated |
722 | | * by normalization. |
723 | | * (3) The range_stdev is analogous to spatial_halfwidth in the |
724 | | * grayscale domain [0...255], and determines how much damping of the |
725 | | * smoothing operation is applied across edges. The larger this |
726 | | * value is, the smaller the damping. The smaller the value, the |
727 | | * more edge details are preserved. These approximations are useful |
728 | | * for deciding the appropriate cutoff. |
729 | | * kernel[1 * stdev] ~= 0.6 * kernel[0] |
730 | | * kernel[2 * stdev] ~= 0.14 * kernel[0] |
731 | | * kernel[3 * stdev] ~= 0.01 * kernel[0] |
732 | | * If range_stdev is infinite there is no damping, and this |
733 | | * becomes a conventional gaussian smoothing. |
734 | | * This value does not affect the run time. |
735 | | * (4) If range_stdev is negative or zero, the range kernel is |
736 | | * ignored and this degenerates to a straight gaussian convolution. |
737 | | * (5) This is very slow for large spatial filters. The time |
738 | | * on a 3GHz pentium is roughly |
739 | | * T = 1.2 * 10^-8 * (A * sh^2) sec |
740 | | * where A = # of pixels, sh = spatial halfwidth of filter. |
741 | | * </pre> |
742 | | */ |
743 | | PIX* |
744 | | pixBlockBilateralExact(PIX *pixs, |
745 | | l_float32 spatial_stdev, |
746 | | l_float32 range_stdev) |
747 | 284 | { |
748 | 284 | l_int32 d, halfwidth; |
749 | 284 | L_KERNEL *spatial_kel, *range_kel; |
750 | 284 | PIX *pixd; |
751 | | |
752 | 284 | if (!pixs) |
753 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
754 | 284 | d = pixGetDepth(pixs); |
755 | 284 | if (d != 8 && d != 32) |
756 | 67 | return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL); |
757 | 217 | if (pixGetColormap(pixs) != NULL) |
758 | 15 | return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL); |
759 | 202 | if (spatial_stdev <= 0.0) |
760 | 0 | return (PIX *)ERROR_PTR("invalid spatial stdev", __func__, NULL); |
761 | 202 | if (range_stdev <= 0.0) |
762 | 0 | return (PIX *)ERROR_PTR("invalid range stdev", __func__, NULL); |
763 | | |
764 | 202 | halfwidth = 2 * spatial_stdev; |
765 | 202 | spatial_kel = makeGaussianKernel(halfwidth, halfwidth, spatial_stdev, 1.0); |
766 | 202 | range_kel = makeRangeKernel(range_stdev); |
767 | 202 | pixd = pixBilateralExact(pixs, spatial_kel, range_kel); |
768 | 202 | kernelDestroy(&spatial_kel); |
769 | 202 | kernelDestroy(&range_kel); |
770 | 202 | return pixd; |
771 | 202 | } |
772 | | |
773 | | |
774 | | /*----------------------------------------------------------------------* |
775 | | * Kernel helper function * |
776 | | *----------------------------------------------------------------------*/ |
777 | | /*! |
778 | | * \brief makeRangeKernel() |
779 | | * |
780 | | * \param[in] range_stdev must be > 0.0 |
781 | | * \return kel, or NULL on error |
782 | | * |
783 | | * <pre> |
784 | | * Notes: |
785 | | * (1) Creates a one-sided Gaussian kernel with the given |
786 | | * standard deviation. At grayscale difference of one stdev, |
787 | | * the kernel falls to 0.6, and to 0.01 at three stdev. |
788 | | * (2) A typical input number might be 20. Then pixels whose |
789 | | * value differs by 60 from the center pixel have their |
790 | | * weight in the convolution reduced by a factor of about 0.01. |
791 | | * </pre> |
792 | | */ |
793 | | L_KERNEL * |
794 | | makeRangeKernel(l_float32 range_stdev) |
795 | 202 | { |
796 | 202 | l_int32 x; |
797 | 202 | l_float32 val, denom; |
798 | 202 | L_KERNEL *kel; |
799 | | |
800 | 202 | if (range_stdev <= 0.0) |
801 | 0 | return (L_KERNEL *)ERROR_PTR("invalid stdev <= 0", __func__, NULL); |
802 | | |
803 | 202 | denom = 2. * range_stdev * range_stdev; |
804 | 202 | if ((kel = kernelCreate(1, 256)) == NULL) |
805 | 0 | return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL); |
806 | 202 | kernelSetOrigin(kel, 0, 0); |
807 | 51.9k | for (x = 0; x < 256; x++) { |
808 | 51.7k | val = expf(-(l_float32)(x * x) / denom); |
809 | 51.7k | kernelSetElement(kel, 0, x, val); |
810 | 51.7k | } |
811 | 202 | return kel; |
812 | 202 | } |